LCOV - code coverage report
Current view: top level - source/ps/XML - RelaxNG.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 78 80 97.5 %
Date: 2023-01-19 00:18:29 Functions: 14 14 100.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "RelaxNG.h"
      21             : 
      22             : #include "lib/timer.h"
      23             : #include "lib/utf8.h"
      24             : #include "ps/CLogger.h"
      25             : #include "ps/CStr.h"
      26             : #include "ps/Filesystem.h"
      27             : 
      28             : #include <libxml/relaxng.h>
      29             : #include <map>
      30             : #include <mutex>
      31             : 
      32           1 : TIMER_ADD_CLIENT(xml_validation);
      33             : 
      34             : /*
      35             :  * libxml2 leaks memory when parsing schemas: https://bugzilla.gnome.org/show_bug.cgi?id=615767
      36             :  * To minimise that problem, keep a global cache of parsed schemas, so we don't
      37             :  * leak an indefinitely large amount of memory when repeatedly restarting the simulation.
      38             :  */
      39             : class RelaxNGSchema;
      40           1 : static std::map<std::string, std::shared_ptr<RelaxNGSchema>> g_SchemaCache;
      41             : static std::mutex g_SchemaCacheLock;
      42             : 
      43          90 : void ClearSchemaCache()
      44             : {
      45         180 :     std::lock_guard<std::mutex> lock(g_SchemaCacheLock);
      46          90 :     g_SchemaCache.clear();
      47          90 : }
      48             : 
      49          15 : static void relaxNGErrorHandler(void* UNUSED(userData), xmlErrorPtr error)
      50             : {
      51             :     // Strip a trailing newline
      52          30 :     std::string message = error->message;
      53          15 :     if (message.length() > 0 && message[message.length()-1] == '\n')
      54          15 :         message.erase(message.length()-1);
      55             : 
      56          15 :     LOGERROR("RelaxNGValidator: Validation %s: %s:%d: %s",
      57             :         error->level == XML_ERR_WARNING ? "warning" : "error",
      58             :         error->file, error->line, message.c_str());
      59          15 : }
      60             : 
      61             : class RelaxNGSchema
      62             : {
      63             : public:
      64             :     xmlRelaxNGPtr m_Schema;
      65             : 
      66          28 :     RelaxNGSchema(const std::string& grammar)
      67          28 :     {
      68          28 :         xmlRelaxNGParserCtxtPtr ctxt = xmlRelaxNGNewMemParserCtxt(grammar.c_str(), (int)grammar.size());
      69          28 :         m_Schema = xmlRelaxNGParse(ctxt);
      70          28 :         xmlRelaxNGFreeParserCtxt(ctxt);
      71             : 
      72          28 :         if (m_Schema == NULL)
      73           1 :             LOGERROR("RelaxNGValidator: Failed to compile schema");
      74          28 :     }
      75             : 
      76          28 :     ~RelaxNGSchema()
      77          28 :     {
      78          28 :         if (m_Schema)
      79          27 :             xmlRelaxNGFree(m_Schema);
      80          28 :     }
      81             : };
      82             : 
      83         125 : RelaxNGValidator::RelaxNGValidator() :
      84         125 :     m_Schema(NULL)
      85             : {
      86         125 : }
      87             : 
      88         329 : RelaxNGValidator::~RelaxNGValidator()
      89             : {
      90         329 : }
      91             : 
      92          29 : bool RelaxNGValidator::LoadGrammar(const std::string& grammar)
      93             : {
      94          58 :     std::shared_ptr<RelaxNGSchema> schema;
      95             : 
      96             :     {
      97          58 :         std::lock_guard<std::mutex> lock(g_SchemaCacheLock);
      98          29 :         std::map<std::string, std::shared_ptr<RelaxNGSchema>>::iterator it = g_SchemaCache.find(grammar);
      99          29 :         if (it == g_SchemaCache.end())
     100             :         {
     101          28 :             schema = std::make_shared<RelaxNGSchema>(grammar);
     102          28 :             g_SchemaCache[grammar] = schema;
     103             :         }
     104             :         else
     105             :         {
     106           1 :             schema = it->second;
     107             :         }
     108             :     }
     109             : 
     110          29 :     m_Schema = schema->m_Schema;
     111          29 :     if (!m_Schema)
     112           1 :         return false;
     113             : 
     114          28 :     MD5 hash;
     115          28 :     hash.Update((const u8*)grammar.c_str(), grammar.length());
     116          28 :     m_Hash = hash;
     117             : 
     118          28 :     return true;
     119             : }
     120             : 
     121          18 : bool RelaxNGValidator::LoadGrammarFile(const PIVFS& vfs, const VfsPath& grammarPath)
     122             : {
     123          36 :     CVFSFile file;
     124          18 :     if (file.Load(vfs, grammarPath) != PSRETURN_OK)
     125           6 :         return false;
     126             : 
     127          12 :     return LoadGrammar(file.DecodeUTF8());
     128             : }
     129             : 
     130          29 : bool RelaxNGValidator::Validate(const std::string& filename, const std::string& document) const
     131             : {
     132          58 :     std::string docutf8 = "<?xml version='1.0' encoding='utf-8'?>" + document;
     133             : 
     134          58 :     return ValidateEncoded(filename, docutf8);
     135             : }
     136             : 
     137          29 : bool RelaxNGValidator::ValidateEncoded(const std::string& filename, const std::string& document) const
     138             : {
     139          58 :     TIMER_ACCRUE(xml_validation);
     140             : 
     141          29 :     if (!m_Schema)
     142             :     {
     143           1 :         LOGERROR("RelaxNGValidator: No grammar loaded");
     144           1 :         return false;
     145             :     }
     146             : 
     147          28 :     xmlDocPtr doc = xmlReadMemory(document.c_str(), (int)document.size(), filename.c_str(), NULL, XML_PARSE_NONET);
     148          28 :     if (doc == NULL)
     149             :     {
     150           1 :         LOGERROR("RelaxNGValidator: Failed to parse document '%s'", filename.c_str());
     151           1 :         return false;
     152             :     }
     153             : 
     154          27 :     bool ret = ValidateEncoded(doc);
     155          27 :     xmlFreeDoc(doc);
     156          27 :     return ret;
     157             : }
     158             : 
     159          42 : bool RelaxNGValidator::ValidateEncoded(xmlDocPtr doc) const
     160             : {
     161          42 :     xmlRelaxNGValidCtxtPtr ctxt = xmlRelaxNGNewValidCtxt(m_Schema);
     162          42 :     xmlRelaxNGSetValidStructuredErrors(ctxt, &relaxNGErrorHandler, NULL);
     163          42 :     int ret = xmlRelaxNGValidateDoc(ctxt, doc);
     164          42 :     xmlRelaxNGFreeValidCtxt(ctxt);
     165             : 
     166          42 :     if (ret == 0)
     167             :     {
     168          35 :         return true;
     169             :     }
     170           7 :     else if (ret > 0)
     171             :     {
     172           7 :         LOGERROR("RelaxNGValidator: Validation failed for '%s'", doc->name);
     173           7 :         return false;
     174             :     }
     175             :     else
     176             :     {
     177           0 :         LOGERROR("RelaxNGValidator: Internal error %d", ret);
     178           0 :         return false;
     179             :     }
     180             : }
     181             : 
     182         101 : bool RelaxNGValidator::CanValidate() const
     183             : {
     184         101 :     return m_Schema != NULL;
     185           3 : }

Generated by: LCOV version 1.13