LCOV - code coverage report
Current view: top level - port - cpl_xml_validate.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 117 426 27.5 %
Date: 2025-01-18 12:42:00 Functions: 7 13 53.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Purpose:  Implement XML validation against XSD schema
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2012-2014, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "cpl_conv.h"
      15             : #include "cpl_error.h"
      16             : 
      17             : #ifdef HAVE_LIBXML2
      18             : #include <libxml/xmlversion.h>
      19             : #if defined(LIBXML_VERSION) && LIBXML_VERSION >= 20622
      20             : // We need at least 2.6.20 for xmlSchemaValidateDoc
      21             : // and xmlParseDoc to accept a const xmlChar*
      22             : // We could workaround it, but likely not worth the effort for now.
      23             : // Actually, we need at least 2.6.22, at runtime, to be
      24             : // able to parse the OGC GML schemas
      25             : #define HAVE_RECENT_LIBXML2
      26             : 
      27             : // libxml2 before 2.8.0 had a bug to parse the OGC GML schemas
      28             : // We have a workaround for that for versions >= 2.6.20 and < 2.8.0.
      29             : #if defined(LIBXML_VERSION) && LIBXML_VERSION < 20800
      30             : #define HAS_VALIDATION_BUG
      31             : #endif
      32             : 
      33             : #else
      34             : #warning "Not recent enough libxml2 version"
      35             : #endif
      36             : #endif
      37             : 
      38             : #ifdef HAVE_RECENT_LIBXML2
      39             : #include <string.h>
      40             : 
      41             : #if defined(__GNUC__)
      42             : #pragma GCC diagnostic push
      43             : #pragma GCC diagnostic ignored "-Wold-style-cast"
      44             : #endif
      45             : #if defined(__clang__)
      46             : #pragma clang diagnostic push
      47             : #pragma clang diagnostic ignored "-Wunknown-pragmas"
      48             : #pragma clang diagnostic ignored "-Wdocumentation"
      49             : #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
      50             : #endif
      51             : 
      52             : #include <libxml/xmlschemas.h>
      53             : #include <libxml/parserInternals.h>
      54             : #include <libxml/catalog.h>
      55             : 
      56             : #if defined(__clang__)
      57             : #pragma clang diagnostic pop
      58             : #endif
      59             : #if defined(__GNUC__)
      60             : #pragma GCC diagnostic pop
      61             : #endif
      62             : 
      63             : #include "cpl_string.h"
      64             : #include "cpl_hash_set.h"
      65             : #include "cpl_minixml.h"
      66             : 
      67             : static xmlExternalEntityLoader pfnLibXMLOldExtranerEntityLoader = nullptr;
      68             : 
      69             : /************************************************************************/
      70             : /*                            CPLFixPath()                              */
      71             : /************************************************************************/
      72             : 
      73             : // Replace \ by / to make libxml2 happy on Windows and
      74             : // replace "a/b/../c" pattern by "a/c".
      75           0 : static void CPLFixPath(char *pszPath)
      76             : {
      77           0 :     for (int i = 0; pszPath[i] != '\0'; ++i)
      78             :     {
      79           0 :         if (pszPath[i] == '\\')
      80           0 :             pszPath[i] = '/';
      81             :     }
      82             : 
      83           0 :     std::string osRet(pszPath);
      84             :     while (true)
      85             :     {
      86           0 :         size_t nSlashDotDot = osRet.find("/../");
      87           0 :         if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
      88             :             break;
      89           0 :         size_t nPos = nSlashDotDot - 1;
      90           0 :         while (nPos > 0 && osRet[nPos] != '/')
      91           0 :             --nPos;
      92           0 :         if (nPos == 0)
      93           0 :             break;
      94           0 :         osRet = osRet.substr(0, nPos + 1) +
      95           0 :                 osRet.substr(nSlashDotDot + strlen("/../"));
      96           0 :     }
      97           0 :     memcpy(pszPath, osRet.data(), osRet.size() + 1);
      98           0 : }
      99             : 
     100             : #ifdef HAS_VALIDATION_BUG
     101             : 
     102             : /************************************************************************/
     103             : /*                  CPLHasLibXMLBugWarningCallback()                    */
     104             : /************************************************************************/
     105             : 
     106             : static void CPLHasLibXMLBugWarningCallback(void * /*ctx*/, const char * /*msg*/,
     107             :                                            ...)
     108             : {
     109             : }
     110             : 
     111             : /************************************************************************/
     112             : /*                          CPLHasLibXMLBug()                           */
     113             : /************************************************************************/
     114             : 
     115             : static bool CPLHasLibXMLBug()
     116             : {
     117             :     static bool bHasLibXMLBug = false;
     118             :     static bool bLibXMLBugChecked = false;
     119             :     if (bLibXMLBugChecked)
     120             :         return bHasLibXMLBug;
     121             : 
     122             :     constexpr char szLibXMLBugTester[] =
     123             :         "<schema targetNamespace=\"http://foo\" "
     124             :         "xmlns:foo=\"http://foo\" xmlns=\"http://www.w3.org/2001/XMLSchema\">"
     125             :         "<simpleType name=\"t1\">"
     126             :         "<list itemType=\"double\"/>"
     127             :         "</simpleType>"
     128             :         "<complexType name=\"t2\">"
     129             :         "<simpleContent>"
     130             :         "<extension base=\"foo:t1\"/>"
     131             :         "</simpleContent>"
     132             :         "</complexType>"
     133             :         "<complexType name=\"t3\">"
     134             :         "<simpleContent>"
     135             :         "<restriction base=\"foo:t2\">"
     136             :         "<length value=\"2\"/>"
     137             :         "</restriction>"
     138             :         "</simpleContent>"
     139             :         "</complexType>"
     140             :         "</schema>";
     141             : 
     142             :     xmlSchemaParserCtxtPtr pSchemaParserCtxt =
     143             :         xmlSchemaNewMemParserCtxt(szLibXMLBugTester, strlen(szLibXMLBugTester));
     144             : 
     145             :     xmlSchemaSetParserErrors(pSchemaParserCtxt, CPLHasLibXMLBugWarningCallback,
     146             :                              CPLHasLibXMLBugWarningCallback, nullptr);
     147             : 
     148             :     xmlSchemaPtr pSchema = xmlSchemaParse(pSchemaParserCtxt);
     149             :     xmlSchemaFreeParserCtxt(pSchemaParserCtxt);
     150             : 
     151             :     bHasLibXMLBug = pSchema == nullptr;
     152             :     bLibXMLBugChecked = true;
     153             : 
     154             :     if (pSchema)
     155             :         xmlSchemaFree(pSchema);
     156             : 
     157             :     if (bHasLibXMLBug)
     158             :     {
     159             :         CPLDebug("CPL",
     160             :                  "LibXML bug found "
     161             :                  "(cf https://bugzilla.gnome.org/show_bug.cgi?id=630130). "
     162             :                  "Will try to workaround for GML schemas.");
     163             :     }
     164             : 
     165             :     return bHasLibXMLBug;
     166             : }
     167             : 
     168             : #endif
     169             : 
     170             : /************************************************************************/
     171             : /*                         CPLExtractSubSchema()                        */
     172             : /************************************************************************/
     173             : 
     174           0 : static CPLXMLNode *CPLExtractSubSchema(CPLXMLNode *psSubXML,
     175             :                                        CPLXMLNode *psMainSchema)
     176             : {
     177           0 :     if (psSubXML->eType == CXT_Element &&
     178           0 :         strcmp(psSubXML->pszValue, "?xml") == 0)
     179             :     {
     180           0 :         CPLXMLNode *psNext = psSubXML->psNext;
     181           0 :         psSubXML->psNext = nullptr;
     182           0 :         CPLDestroyXMLNode(psSubXML);
     183           0 :         psSubXML = psNext;
     184             :     }
     185             : 
     186           0 :     if (psSubXML != nullptr && psSubXML->eType == CXT_Comment)
     187             :     {
     188           0 :         CPLXMLNode *psNext = psSubXML->psNext;
     189           0 :         psSubXML->psNext = nullptr;
     190           0 :         CPLDestroyXMLNode(psSubXML);
     191           0 :         psSubXML = psNext;
     192             :     }
     193             : 
     194           0 :     if (psSubXML != nullptr && psSubXML->eType == CXT_Element &&
     195           0 :         (strcmp(psSubXML->pszValue, "schema") == 0 ||
     196           0 :          strcmp(psSubXML->pszValue, "xs:schema") == 0 ||
     197           0 :          strcmp(psSubXML->pszValue, "xsd:schema") == 0) &&
     198           0 :         psSubXML->psNext == nullptr)
     199             :     {
     200           0 :         CPLXMLNode *psNext = psSubXML->psChild;
     201           0 :         while (psNext != nullptr && psNext->eType != CXT_Element &&
     202           0 :                psNext->psNext != nullptr &&
     203           0 :                psNext->psNext->eType != CXT_Element)
     204             :         {
     205             :             // Add xmlns: from subschema to main schema if missing.
     206           0 :             if (psNext->eType == CXT_Attribute &&
     207           0 :                 STARTS_WITH(psNext->pszValue, "xmlns:") &&
     208           0 :                 CPLGetXMLValue(psMainSchema, psNext->pszValue, nullptr) ==
     209             :                     nullptr)
     210             :             {
     211             :                 CPLXMLNode *psAttr =
     212           0 :                     CPLCreateXMLNode(nullptr, CXT_Attribute, psNext->pszValue);
     213           0 :                 CPLCreateXMLNode(psAttr, CXT_Text, psNext->psChild->pszValue);
     214             : 
     215           0 :                 psAttr->psNext = psMainSchema->psChild;
     216           0 :                 psMainSchema->psChild = psAttr;
     217             :             }
     218           0 :             psNext = psNext->psNext;
     219             :         }
     220             : 
     221           0 :         if (psNext != nullptr && psNext->eType != CXT_Element &&
     222           0 :             psNext->psNext != nullptr && psNext->psNext->eType == CXT_Element)
     223             :         {
     224           0 :             CPLXMLNode *psNext2 = psNext->psNext;
     225           0 :             psNext->psNext = nullptr;
     226           0 :             CPLDestroyXMLNode(psSubXML);
     227           0 :             psSubXML = psNext2;
     228             :         }
     229             :     }
     230             : 
     231           0 :     return psSubXML;
     232             : }
     233             : 
     234             : #ifdef HAS_VALIDATION_BUG
     235             : /************************************************************************/
     236             : /*                        CPLWorkaroundLibXMLBug()                      */
     237             : /************************************************************************/
     238             : 
     239             : // Return TRUE if the current node must be destroyed.
     240             : static bool CPLWorkaroundLibXMLBug(CPLXMLNode *psIter)
     241             : {
     242             :     if (psIter->eType == CXT_Element &&
     243             :         strcmp(psIter->pszValue, "element") == 0 &&
     244             :         strcmp(CPLGetXMLValue(psIter, "name", ""), "QuantityExtent") == 0 &&
     245             :         strcmp(CPLGetXMLValue(psIter, "type", ""), "gml:QuantityExtentType") ==
     246             :             0)
     247             :     {
     248             :         CPLXMLNode *psIter2 = psIter->psChild;
     249             :         while (psIter2)
     250             :         {
     251             :             if (psIter2->eType == CXT_Attribute &&
     252             :                 strcmp(psIter2->pszValue, "type") == 0)
     253             :             {
     254             :                 CPLFree(psIter2->psChild->pszValue);
     255             :                 if (strcmp(CPLGetXMLValue(psIter, "substitutionGroup", ""),
     256             :                            "gml:AbstractValue") == 0)
     257             :                     // GML 3.2.1.
     258             :                     psIter2->psChild->pszValue =
     259             :                         CPLStrdup("gml:MeasureOrNilReasonListType");
     260             :                 else
     261             :                     psIter2->psChild->pszValue =
     262             :                         CPLStrdup("gml:MeasureOrNullListType");
     263             :             }
     264             :             psIter2 = psIter2->psNext;
     265             :         }
     266             :     }
     267             : 
     268             :     else if (psIter->eType == CXT_Element &&
     269             :              strcmp(psIter->pszValue, "element") == 0 &&
     270             :              strcmp(CPLGetXMLValue(psIter, "name", ""), "CategoryExtent") ==
     271             :                  0 &&
     272             :              strcmp(CPLGetXMLValue(psIter, "type", ""),
     273             :                     "gml:CategoryExtentType") == 0)
     274             :     {
     275             :         CPLXMLNode *psIter2 = psIter->psChild;
     276             :         while (psIter2)
     277             :         {
     278             :             if (psIter2->eType == CXT_Attribute &&
     279             :                 strcmp(psIter2->pszValue, "type") == 0)
     280             :             {
     281             :                 CPLFree(psIter2->psChild->pszValue);
     282             :                 if (strcmp(CPLGetXMLValue(psIter, "substitutionGroup", ""),
     283             :                            "gml:AbstractValue") == 0)
     284             :                     // GML 3.2.1
     285             :                     psIter2->psChild->pszValue =
     286             :                         CPLStrdup("gml:CodeOrNilReasonListType");
     287             :                 else
     288             :                     psIter2->psChild->pszValue =
     289             :                         CPLStrdup("gml:CodeOrNullListType");
     290             :             }
     291             :             psIter2 = psIter2->psNext;
     292             :         }
     293             :     }
     294             : 
     295             :     else if (CPLHasLibXMLBug() && psIter->eType == CXT_Element &&
     296             :              strcmp(psIter->pszValue, "complexType") == 0 &&
     297             :              (strcmp(CPLGetXMLValue(psIter, "name", ""),
     298             :                      "QuantityExtentType") == 0 ||
     299             :               strcmp(CPLGetXMLValue(psIter, "name", ""),
     300             :                      "CategoryExtentType") == 0))
     301             :     {
     302             :         // Destroy this element.
     303             :         return true;
     304             :     }
     305             : 
     306             :     // For GML 3.2.1
     307             :     else if (psIter->eType == CXT_Element &&
     308             :              strcmp(psIter->pszValue, "complexType") == 0 &&
     309             :              strcmp(CPLGetXMLValue(psIter, "name", ""), "VectorType") == 0)
     310             :     {
     311             :         CPLXMLNode *psSimpleContent =
     312             :             CPLCreateXMLNode(nullptr, CXT_Element, "simpleContent");
     313             :         CPLXMLNode *psExtension =
     314             :             CPLCreateXMLNode(psSimpleContent, CXT_Element, "extension");
     315             :         CPLXMLNode *psExtensionBase =
     316             :             CPLCreateXMLNode(psExtension, CXT_Attribute, "base");
     317             :         CPLCreateXMLNode(psExtensionBase, CXT_Text, "gml:doubleList");
     318             :         CPLXMLNode *psAttributeGroup =
     319             :             CPLCreateXMLNode(psExtension, CXT_Element, "attributeGroup");
     320             :         CPLXMLNode *psAttributeGroupRef =
     321             :             CPLCreateXMLNode(psAttributeGroup, CXT_Attribute, "ref");
     322             :         CPLCreateXMLNode(psAttributeGroupRef, CXT_Text,
     323             :                          "gml:SRSReferenceGroup");
     324             : 
     325             :         CPLXMLNode *psName = CPLCreateXMLNode(nullptr, CXT_Attribute, "name");
     326             :         CPLCreateXMLNode(psName, CXT_Text, "VectorType");
     327             : 
     328             :         CPLDestroyXMLNode(psIter->psChild);
     329             :         psIter->psChild = psName;
     330             :         psIter->psChild->psNext = psSimpleContent;
     331             :     }
     332             : 
     333             :     else if (psIter->eType == CXT_Element &&
     334             :              strcmp(psIter->pszValue, "element") == 0 &&
     335             :              (strcmp(CPLGetXMLValue(psIter, "name", ""), "domainOfValidity") ==
     336             :                   0 ||
     337             :               strcmp(CPLGetXMLValue(psIter, "name", ""),
     338             :                      "coordinateOperationAccuracy") == 0 ||
     339             :               strcmp(CPLGetXMLValue(psIter, "name", ""), "formulaCitation") ==
     340             :                   0))
     341             :     {
     342             :         CPLXMLNode *psComplexType =
     343             :             CPLCreateXMLNode(nullptr, CXT_Element, "complexType");
     344             :         CPLXMLNode *psSequence =
     345             :             CPLCreateXMLNode(psComplexType, CXT_Element, "sequence");
     346             :         CPLXMLNode *psSequenceMinOccurs =
     347             :             CPLCreateXMLNode(psSequence, CXT_Attribute, "minOccurs");
     348             :         CPLCreateXMLNode(psSequenceMinOccurs, CXT_Text, "0");
     349             :         CPLXMLNode *psAny = CPLCreateXMLNode(psSequence, CXT_Element, "any");
     350             :         CPLXMLNode *psAnyMinOccurs =
     351             :             CPLCreateXMLNode(psAny, CXT_Attribute, "minOccurs");
     352             :         CPLCreateXMLNode(psAnyMinOccurs, CXT_Text, "0");
     353             :         CPLXMLNode *psAnyProcessContents =
     354             :             CPLCreateXMLNode(psAny, CXT_Attribute, " processContents");
     355             :         CPLCreateXMLNode(psAnyProcessContents, CXT_Text, "lax");
     356             : 
     357             :         CPLXMLNode *psName = CPLCreateXMLNode(nullptr, CXT_Attribute, "name");
     358             :         CPLCreateXMLNode(psName, CXT_Text, CPLGetXMLValue(psIter, "name", ""));
     359             : 
     360             :         CPLDestroyXMLNode(psIter->psChild);
     361             :         psIter->psChild = psName;
     362             :         psIter->psChild->psNext = psComplexType;
     363             :     }
     364             : 
     365             :     return false;
     366             : }
     367             : #endif
     368             : 
     369             : /************************************************************************/
     370             : /*                       CPLLoadSchemaStrInternal()                     */
     371             : /************************************************************************/
     372             : 
     373         486 : static CPLXMLNode *CPLLoadSchemaStrInternal(CPLHashSet *hSetSchemas,
     374             :                                             const char *pszFile)
     375             : {
     376         486 :     if (CPLHashSetLookup(hSetSchemas, pszFile))
     377           0 :         return nullptr;
     378             : 
     379         486 :     CPLHashSetInsert(hSetSchemas, CPLStrdup(pszFile));
     380             : 
     381         486 :     CPLDebug("CPL", "Parsing %s", pszFile);
     382             : 
     383         486 :     CPLXMLNode *psXML = CPLParseXMLFile(pszFile);
     384         486 :     if (psXML == nullptr)
     385             :     {
     386           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s", pszFile);
     387           0 :         return nullptr;
     388             :     }
     389             : 
     390         486 :     CPLXMLNode *psSchema = CPLGetXMLNode(psXML, "=schema");
     391         486 :     if (psSchema == nullptr)
     392             :     {
     393         486 :         psSchema = CPLGetXMLNode(psXML, "=xs:schema");
     394             :     }
     395         486 :     if (psSchema == nullptr)
     396             :     {
     397           0 :         psSchema = CPLGetXMLNode(psXML, "=xsd:schema");
     398             :     }
     399         486 :     if (psSchema == nullptr)
     400             :     {
     401           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find schema node in %s",
     402             :                  pszFile);
     403           0 :         CPLDestroyXMLNode(psXML);
     404           0 :         return nullptr;
     405             :     }
     406             : 
     407         486 :     CPLXMLNode *psPrev = nullptr;
     408         486 :     CPLXMLNode *psIter = psSchema->psChild;
     409       13064 :     while (psIter)
     410             :     {
     411       12578 :         bool bDestroyCurrentNode = false;
     412             : 
     413             : #ifdef HAS_VALIDATION_BUG
     414             :         if (CPLHasLibXMLBug())
     415             :             bDestroyCurrentNode = CPLWorkaroundLibXMLBug(psIter);
     416             : #endif
     417             : 
     418             :         // Load the referenced schemas, and integrate them in the main schema.
     419       12578 :         if (psIter->eType == CXT_Element &&
     420       11120 :             (strcmp(psIter->pszValue, "include") == 0 ||
     421       11120 :              strcmp(psIter->pszValue, "xs:include") == 0 ||
     422       11120 :              strcmp(psIter->pszValue, "xsd:include") == 0) &&
     423           0 :             psIter->psChild != nullptr &&
     424           0 :             psIter->psChild->eType == CXT_Attribute &&
     425           0 :             strcmp(psIter->psChild->pszValue, "schemaLocation") == 0)
     426             :         {
     427           0 :             const char *pszIncludeSchema = psIter->psChild->psChild->pszValue;
     428             :             char *pszFullFilename =
     429           0 :                 CPLStrdup(CPLFormFilenameSafe(CPLGetPathSafe(pszFile).c_str(),
     430             :                                               pszIncludeSchema, nullptr)
     431             :                               .c_str());
     432             : 
     433           0 :             CPLFixPath(pszFullFilename);
     434             : 
     435           0 :             CPLXMLNode *psSubXML = nullptr;
     436             : 
     437             :             // If we haven't yet loaded that schema, do it now.
     438           0 :             if (!CPLHashSetLookup(hSetSchemas, pszFullFilename))
     439             :             {
     440             :                 psSubXML =
     441           0 :                     CPLLoadSchemaStrInternal(hSetSchemas, pszFullFilename);
     442           0 :                 if (psSubXML == nullptr)
     443             :                 {
     444           0 :                     CPLFree(pszFullFilename);
     445           0 :                     CPLDestroyXMLNode(psXML);
     446           0 :                     return nullptr;
     447             :                 }
     448             :             }
     449           0 :             CPLFree(pszFullFilename);
     450           0 :             pszFullFilename = nullptr;
     451             : 
     452           0 :             if (psSubXML)
     453             :             {
     454           0 :                 CPLXMLNode *psNext = psIter->psNext;
     455             : 
     456           0 :                 psSubXML = CPLExtractSubSchema(psSubXML, psSchema);
     457           0 :                 if (psSubXML == nullptr)
     458             :                 {
     459           0 :                     CPLDestroyXMLNode(psXML);
     460           0 :                     return nullptr;
     461             :                 }
     462             : 
     463             :                 // Replace <include/> node by the subXML.
     464           0 :                 CPLXMLNode *psIter2 = psSubXML;
     465           0 :                 while (psIter2->psNext)
     466           0 :                     psIter2 = psIter2->psNext;
     467           0 :                 psIter2->psNext = psNext;
     468             : 
     469           0 :                 if (psPrev == nullptr)
     470           0 :                     psSchema->psChild = psSubXML;
     471             :                 else
     472           0 :                     psPrev->psNext = psSubXML;
     473             : 
     474           0 :                 psIter->psNext = nullptr;
     475           0 :                 CPLDestroyXMLNode(psIter);
     476             : 
     477           0 :                 psPrev = psIter2;
     478           0 :                 psIter = psNext;
     479           0 :                 continue;
     480             :             }
     481             :             else
     482             :             {
     483             :                 // We have already included that file,
     484             :                 // so just remove the <include/> node
     485           0 :                 bDestroyCurrentNode = true;
     486           0 :             }
     487             :         }
     488             : 
     489             :         // Patch the schemaLocation of <import/>.
     490       12578 :         else if (psIter->eType == CXT_Element &&
     491       11120 :                  (strcmp(psIter->pszValue, "import") == 0 ||
     492       11120 :                   strcmp(psIter->pszValue, "xs:import") == 0 ||
     493       11120 :                   strcmp(psIter->pszValue, "xsd:import") == 0))
     494             :         {
     495           0 :             CPLXMLNode *psIter2 = psIter->psChild;
     496           0 :             while (psIter2)
     497             :             {
     498           0 :                 if (psIter2->eType == CXT_Attribute &&
     499           0 :                     strcmp(psIter2->pszValue, "schemaLocation") == 0 &&
     500           0 :                     psIter2->psChild != nullptr &&
     501           0 :                     !STARTS_WITH(psIter2->psChild->pszValue, "http://") &&
     502           0 :                     !STARTS_WITH(psIter2->psChild->pszValue, "ftp://") &&
     503             :                     // If the top file is our warping file, don't alter the path
     504             :                     // of the import.
     505           0 :                     strstr(pszFile, "/vsimem/CPLValidateXML_") == nullptr)
     506             :                 {
     507           0 :                     char *pszFullFilename = CPLStrdup(
     508           0 :                         CPLFormFilenameSafe(CPLGetPathSafe(pszFile).c_str(),
     509           0 :                                             psIter2->psChild->pszValue, nullptr)
     510             :                             .c_str());
     511           0 :                     CPLFixPath(pszFullFilename);
     512           0 :                     CPLFree(psIter2->psChild->pszValue);
     513           0 :                     psIter2->psChild->pszValue = pszFullFilename;
     514             :                 }
     515           0 :                 psIter2 = psIter2->psNext;
     516             :             }
     517             :         }
     518             : 
     519       12578 :         if (bDestroyCurrentNode)
     520             :         {
     521           0 :             CPLXMLNode *psNext = psIter->psNext;
     522           0 :             if (psPrev == nullptr)
     523           0 :                 psSchema->psChild = psNext;
     524             :             else
     525           0 :                 psPrev->psNext = psNext;
     526             : 
     527           0 :             psIter->psNext = nullptr;
     528           0 :             CPLDestroyXMLNode(psIter);
     529             : 
     530           0 :             psIter = psNext;
     531           0 :             continue;
     532             :         }
     533             : 
     534       12578 :         psPrev = psIter;
     535       12578 :         psIter = psIter->psNext;
     536             :     }
     537             : 
     538         486 :     return psXML;
     539             : }
     540             : 
     541             : /************************************************************************/
     542             : /*                       CPLMoveImportAtBeginning()                     */
     543             : /************************************************************************/
     544             : 
     545         486 : static void CPLMoveImportAtBeginning(CPLXMLNode *psXML)
     546             : {
     547         486 :     CPLXMLNode *psSchema = CPLGetXMLNode(psXML, "=schema");
     548         486 :     if (psSchema == nullptr)
     549         486 :         psSchema = CPLGetXMLNode(psXML, "=xs:schema");
     550         486 :     if (psSchema == nullptr)
     551           0 :         psSchema = CPLGetXMLNode(psXML, "=xsd:schema");
     552         486 :     if (psSchema == nullptr)
     553           0 :         return;
     554             : 
     555         486 :     CPLXMLNode *psPrev = nullptr;
     556         486 :     CPLXMLNode *psIter = psSchema->psChild;
     557       13064 :     while (psIter)
     558             :     {
     559       12578 :         if (psPrev != nullptr && psIter->eType == CXT_Element &&
     560       11120 :             (strcmp(psIter->pszValue, "import") == 0 ||
     561       11120 :              strcmp(psIter->pszValue, "xs:import") == 0 ||
     562       11120 :              strcmp(psIter->pszValue, "xsd:import") == 0))
     563             :         {
     564             :             // Reorder at the beginning.
     565           0 :             CPLXMLNode *psNext = psIter->psNext;
     566             : 
     567           0 :             psPrev->psNext = psNext;
     568             : 
     569           0 :             CPLXMLNode *psFirstChild = psSchema->psChild;
     570           0 :             psSchema->psChild = psIter;
     571           0 :             psIter->psNext = psFirstChild;
     572             : 
     573           0 :             psIter = psNext;
     574           0 :             continue;
     575             :         }
     576             : 
     577       12578 :         psPrev = psIter;
     578       12578 :         psIter = psIter->psNext;
     579             :     }
     580             : }
     581             : 
     582             : /************************************************************************/
     583             : /*                           CPLLoadSchemaStr()                         */
     584             : /************************************************************************/
     585             : 
     586         486 : static char *CPLLoadSchemaStr(const char *pszXSDFilename)
     587             : {
     588             : #ifdef HAS_VALIDATION_BUG
     589             :     CPLHasLibXMLBug();
     590             : #endif
     591             : 
     592             :     CPLHashSet *hSetSchemas =
     593         486 :         CPLHashSetNew(CPLHashSetHashStr, CPLHashSetEqualStr, CPLFree);
     594             :     CPLXMLNode *psSchema =
     595         486 :         CPLLoadSchemaStrInternal(hSetSchemas, pszXSDFilename);
     596             : 
     597         486 :     char *pszStr = nullptr;
     598         486 :     if (psSchema)
     599             :     {
     600         486 :         CPLMoveImportAtBeginning(psSchema);
     601         486 :         pszStr = CPLSerializeXMLTree(psSchema);
     602         486 :         CPLDestroyXMLNode(psSchema);
     603             :     }
     604         486 :     CPLHashSetDestroy(hSetSchemas);
     605         486 :     return pszStr;
     606             : }
     607             : 
     608             : /************************************************************************/
     609             : /*                     CPLLibXMLInputStreamCPLFree()                    */
     610             : /************************************************************************/
     611             : 
     612           0 : static void CPLLibXMLInputStreamCPLFree(xmlChar *pszBuffer)
     613             : {
     614           0 :     CPLFree(pszBuffer);
     615           0 : }
     616             : 
     617             : /************************************************************************/
     618             : /*                           CPLFindLocalXSD()                          */
     619             : /************************************************************************/
     620             : 
     621           0 : static CPLString CPLFindLocalXSD(const char *pszXSDFilename)
     622             : {
     623           0 :     CPLString osTmp;
     624             :     const char *pszSchemasOpenGIS =
     625           0 :         CPLGetConfigOption("GDAL_OPENGIS_SCHEMAS", nullptr);
     626           0 :     if (pszSchemasOpenGIS != nullptr)
     627             :     {
     628           0 :         int nLen = static_cast<int>(strlen(pszSchemasOpenGIS));
     629           0 :         if (nLen > 0 && pszSchemasOpenGIS[nLen - 1] == '/')
     630             :         {
     631           0 :             osTmp = pszSchemasOpenGIS;
     632           0 :             osTmp += pszXSDFilename;
     633             :         }
     634             :         else
     635             :         {
     636           0 :             osTmp = pszSchemasOpenGIS;
     637           0 :             osTmp += "/";
     638           0 :             osTmp += pszXSDFilename;
     639             :         }
     640             :     }
     641           0 :     else if ((pszSchemasOpenGIS = CPLFindFile("gdal", "SCHEMAS_OPENGIS_NET")) !=
     642             :              nullptr)
     643             :     {
     644           0 :         osTmp = pszSchemasOpenGIS;
     645           0 :         osTmp += "/";
     646           0 :         osTmp += pszXSDFilename;
     647             :     }
     648             : 
     649             :     VSIStatBufL sStatBuf;
     650           0 :     if (VSIStatExL(osTmp, &sStatBuf, VSI_STAT_EXISTS_FLAG) == 0)
     651           0 :         return osTmp;
     652           0 :     return "";
     653             : }
     654             : 
     655             : /************************************************************************/
     656             : /*                      CPLExternalEntityLoader()                       */
     657             : /************************************************************************/
     658             : 
     659             : constexpr char szXML_XSD[] =
     660             :     "<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" "
     661             :     "targetNamespace=\"http://www.w3.org/XML/1998/namespace\">"
     662             :     "<attribute name=\"lang\">"
     663             :     "<simpleType>"
     664             :     "<union memberTypes=\"language\">"
     665             :     "<simpleType>"
     666             :     "<restriction base=\"string\">"
     667             :     "<enumeration value=\"\"/>"
     668             :     "</restriction>"
     669             :     "</simpleType>"
     670             :     "</union>"
     671             :     "</simpleType>"
     672             :     "</attribute>"
     673             :     "<attribute name=\"space\">"
     674             :     "<simpleType>"
     675             :     "<restriction base=\"NCName\">"
     676             :     "<enumeration value=\"default\"/>"
     677             :     "<enumeration value=\"preserve\"/>"
     678             :     "</restriction>"
     679             :     "</simpleType>"
     680             :     "</attribute>"
     681             :     "<attribute name=\"base\" type=\"anyURI\"/>"
     682             :     "<attribute name=\"id\" type=\"ID\"/>"
     683             :     "<attributeGroup name=\"specialAttrs\">"
     684             :     "<attribute ref=\"xml:base\"/>"
     685             :     "<attribute ref=\"xml:lang\"/>"
     686             :     "<attribute ref=\"xml:space\"/>"
     687             :     "<attribute ref=\"xml:id\"/>"
     688             :     "</attributeGroup>"
     689             :     "</schema>";
     690             : 
     691             : // Simplified (and truncated) version of http://www.w3.org/1999/xlink.xsd
     692             : // (sufficient for GML schemas).
     693             : constexpr char szXLINK_XSD[] =
     694             :     "<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" "
     695             :     "targetNamespace=\"http://www.w3.org/1999/xlink\" "
     696             :     "xmlns:xlink=\"http://www.w3.org/1999/xlink\">"
     697             :     "<attribute name=\"type\" type=\"string\"/>"
     698             :     "<attribute name=\"href\" type=\"anyURI\"/>"
     699             :     "<attribute name=\"role\" type=\"anyURI\"/>"
     700             :     "<attribute name=\"arcrole\" type=\"anyURI\"/>"
     701             :     "<attribute name=\"title\" type=\"string\"/>"
     702             :     "<attribute name=\"show\" type=\"string\"/>"
     703             :     "<attribute name=\"actuate\" type=\"string\"/>"
     704             :     "<attribute name=\"label\" type=\"NCName\"/>"
     705             :     "<attribute name=\"from\" type=\"NCName\"/>"
     706             :     "<attribute name=\"to\" type=\"NCName\"/>"
     707             :     "<attributeGroup name=\"simpleAttrs\">"
     708             :     "<attribute ref=\"xlink:type\" fixed=\"simple\"/>"
     709             :     "<attribute ref=\"xlink:href\"/>"
     710             :     "<attribute ref=\"xlink:role\"/>"
     711             :     "<attribute ref=\"xlink:arcrole\"/>"
     712             :     "<attribute ref=\"xlink:title\"/>"
     713             :     "<attribute ref=\"xlink:show\"/>"
     714             :     "<attribute ref=\"xlink:actuate\"/>"
     715             :     "</attributeGroup>"
     716             :     "</schema>";
     717             : 
     718           0 : static xmlParserInputPtr CPLExternalEntityLoader(const char *URL,
     719             :                                                  const char *ID,
     720             :                                                  xmlParserCtxtPtr context)
     721             : {
     722             : #if DEBUG_VERBOSE
     723             :     CPLDebug("CPL", "CPLExternalEntityLoader(%s)", URL);
     724             : #endif
     725             :     // Use libxml2 catalog mechanism to resolve the URL to something else.
     726             :     // xmlChar* pszResolved = xmlCatalogResolveSystem((const xmlChar*)URL);
     727             :     xmlChar *pszResolved =
     728           0 :         xmlCatalogResolveSystem(reinterpret_cast<const xmlChar *>(URL));
     729           0 :     if (pszResolved == nullptr)
     730             :         pszResolved =
     731           0 :             xmlCatalogResolveURI(reinterpret_cast<const xmlChar *>(URL));
     732           0 :     CPLString osURL;
     733           0 :     if (pszResolved)
     734             :     {
     735           0 :         CPLDebug("CPL", "Resolving %s in %s", URL,
     736             :                  reinterpret_cast<const char *>(pszResolved));
     737           0 :         osURL = reinterpret_cast<const char *>(pszResolved);
     738           0 :         URL = osURL.c_str();
     739           0 :         xmlFree(pszResolved);
     740           0 :         pszResolved = nullptr;
     741             :     }
     742             : 
     743           0 :     if (STARTS_WITH(URL, "http://"))
     744             :     {
     745             :         // Make sure to use http://schemas.opengis.net/
     746             :         // when gml/2 or gml/3 is detected.
     747           0 :         const char *pszGML = strstr(URL, "gml/2");
     748           0 :         if (pszGML == nullptr)
     749           0 :             pszGML = strstr(URL, "gml/3");
     750           0 :         if (pszGML != nullptr)
     751             :         {
     752           0 :             osURL = "http://schemas.opengis.net/";
     753           0 :             osURL += pszGML;
     754           0 :             URL = osURL.c_str();
     755             :         }
     756           0 :         else if (strcmp(URL, "http://www.w3.org/2001/xml.xsd") == 0)
     757             :         {
     758           0 :             std::string osTmp = CPLFindLocalXSD("xml.xsd");
     759           0 :             if (!osTmp.empty())
     760             :             {
     761           0 :                 osURL = std::move(osTmp);
     762           0 :                 URL = osURL.c_str();
     763             :             }
     764             :             else
     765             :             {
     766           0 :                 CPLDebug("CPL", "Resolving %s to local definition",
     767             :                          "http://www.w3.org/2001/xml.xsd");
     768           0 :                 return xmlNewStringInputStream(
     769           0 :                     context, reinterpret_cast<const xmlChar *>(szXML_XSD));
     770             :             }
     771             :         }
     772           0 :         else if (strcmp(URL, "http://www.w3.org/1999/xlink.xsd") == 0)
     773             :         {
     774           0 :             std::string osTmp = CPLFindLocalXSD("xlink.xsd");
     775           0 :             if (!osTmp.empty())
     776             :             {
     777           0 :                 osURL = std::move(osTmp);
     778           0 :                 URL = osURL.c_str();
     779             :             }
     780             :             else
     781             :             {
     782           0 :                 CPLDebug("CPL", "Resolving %s to local definition",
     783             :                          "http://www.w3.org/1999/xlink.xsd");
     784           0 :                 return xmlNewStringInputStream(
     785           0 :                     context, reinterpret_cast<const xmlChar *>(szXLINK_XSD));
     786             :             }
     787             :         }
     788           0 :         else if (!STARTS_WITH(URL, "http://schemas.opengis.net/"))
     789             :         {
     790           0 :             CPLDebug("CPL", "Loading %s", URL);
     791           0 :             return pfnLibXMLOldExtranerEntityLoader(URL, ID, context);
     792             :         }
     793             :     }
     794           0 :     else if (STARTS_WITH(URL, "ftp://"))
     795             :     {
     796           0 :         return pfnLibXMLOldExtranerEntityLoader(URL, ID, context);
     797             :     }
     798           0 :     else if (STARTS_WITH(URL, "file://"))
     799             :     {
     800             :         // Parse file:// URI so as to be able to open them with VSI*L API.
     801           0 :         if (STARTS_WITH(URL, "file://localhost/"))
     802           0 :             URL += 16;
     803             :         else
     804           0 :             URL += 7;
     805             : 
     806           0 :         if (URL[0] == '/' && URL[1] != '\0' && URL[2] == ':' && URL[3] == '/')
     807             :         {
     808             :             // Windows.
     809           0 :             ++URL;
     810             :         }
     811           0 :         else if (URL[0] == '/')
     812             :         {
     813             :             // Unix.
     814             :         }
     815             :         else
     816             :         {
     817           0 :             return pfnLibXMLOldExtranerEntityLoader(URL, ID, context);
     818             :         }
     819             :     }
     820             : 
     821           0 :     CPLString osModURL;
     822           0 :     if (STARTS_WITH(URL, "/vsizip/vsicurl/http%3A//"))
     823             :     {
     824           0 :         osModURL = "/vsizip/vsicurl/http://";
     825           0 :         osModURL += URL + strlen("/vsizip/vsicurl/http%3A//");
     826             :     }
     827           0 :     else if (STARTS_WITH(URL, "/vsicurl/http%3A//"))
     828             :     {
     829           0 :         osModURL = "vsicurl/http://";
     830           0 :         osModURL += URL + strlen("/vsicurl/http%3A//");
     831             :     }
     832           0 :     else if (STARTS_WITH(URL, "http://schemas.opengis.net/"))
     833             :     {
     834           0 :         const char *pszAfterOpenGIS =
     835             :             URL + strlen("http://schemas.opengis.net/");
     836             : 
     837             :         const char *pszSchemasOpenGIS =
     838           0 :             CPLGetConfigOption("GDAL_OPENGIS_SCHEMAS", nullptr);
     839           0 :         if (pszSchemasOpenGIS != nullptr)
     840             :         {
     841           0 :             const int nLen = static_cast<int>(strlen(pszSchemasOpenGIS));
     842           0 :             if (nLen > 0 && pszSchemasOpenGIS[nLen - 1] == '/')
     843             :             {
     844           0 :                 osModURL = pszSchemasOpenGIS;
     845           0 :                 osModURL += pszAfterOpenGIS;
     846             :             }
     847             :             else
     848             :             {
     849           0 :                 osModURL = pszSchemasOpenGIS;
     850           0 :                 osModURL += "/";
     851           0 :                 osModURL += pszAfterOpenGIS;
     852             :             }
     853             :         }
     854           0 :         else if ((pszSchemasOpenGIS =
     855           0 :                       CPLFindFile("gdal", "SCHEMAS_OPENGIS_NET")) != nullptr)
     856             :         {
     857           0 :             osModURL = pszSchemasOpenGIS;
     858           0 :             osModURL += "/";
     859           0 :             osModURL += pszAfterOpenGIS;
     860             :         }
     861           0 :         else if ((pszSchemasOpenGIS = CPLFindFile(
     862           0 :                       "gdal", "SCHEMAS_OPENGIS_NET.zip")) != nullptr)
     863             :         {
     864           0 :             osModURL = "/vsizip/";
     865           0 :             osModURL += pszSchemasOpenGIS;
     866           0 :             osModURL += "/";
     867           0 :             osModURL += pszAfterOpenGIS;
     868             :         }
     869             :         else
     870             :         {
     871             :             osModURL = "/vsizip/vsicurl/"
     872           0 :                        "http://schemas.opengis.net/SCHEMAS_OPENGIS_NET.zip/";
     873           0 :             osModURL += pszAfterOpenGIS;
     874             :         }
     875             :     }
     876             :     else
     877             :     {
     878           0 :         osModURL = URL;
     879             :     }
     880             : 
     881             :     xmlChar *pszBuffer =
     882           0 :         reinterpret_cast<xmlChar *>(CPLLoadSchemaStr(osModURL));
     883           0 :     if (pszBuffer == nullptr)
     884           0 :         return nullptr;
     885             : 
     886             :     xmlParserInputPtr poInputStream =
     887           0 :         xmlNewStringInputStream(context, pszBuffer);
     888           0 :     if (poInputStream != nullptr)
     889           0 :         poInputStream->free = CPLLibXMLInputStreamCPLFree;
     890           0 :     return poInputStream;
     891             : }
     892             : 
     893             : /************************************************************************/
     894             : /*                    CPLLibXMLWarningErrorCallback()                   */
     895             : /************************************************************************/
     896             : 
     897          82 : static void CPLLibXMLWarningErrorCallback(void *ctx, const char *msg, ...)
     898             : {
     899             :     va_list varg;
     900          82 :     va_start(varg, msg);
     901             : 
     902          82 :     char *pszStr = reinterpret_cast<char *>(va_arg(varg, char *));
     903             : 
     904          82 :     if (strstr(pszStr, "since this namespace was already imported") == nullptr)
     905             :     {
     906          82 :         const xmlError *pErrorPtr = xmlGetLastError();
     907          82 :         const char *pszFilename = static_cast<char *>(ctx);
     908          82 :         char *pszStrDup = CPLStrdup(pszStr);
     909          82 :         int nLen = static_cast<int>(strlen(pszStrDup));
     910          82 :         if (nLen > 0 && pszStrDup[nLen - 1] == '\n')
     911          82 :             pszStrDup[nLen - 1] = '\0';
     912          82 :         if (pszFilename != nullptr && pszFilename[0] != '<')
     913             :         {
     914           0 :             CPLError(CE_Failure, CPLE_AppDefined, "libXML: %s:%d: %s",
     915             :                      pszFilename, pErrorPtr ? pErrorPtr->line : 0, pszStrDup);
     916             :         }
     917             :         else
     918             :         {
     919          82 :             CPLError(CE_Failure, CPLE_AppDefined, "libXML: %d: %s",
     920             :                      pErrorPtr ? pErrorPtr->line : 0, pszStrDup);
     921             :         }
     922          82 :         CPLFree(pszStrDup);
     923             :     }
     924             : 
     925          82 :     va_end(varg);
     926          82 : }
     927             : 
     928             : /************************************************************************/
     929             : /*                      CPLLoadContentFromFile()                        */
     930             : /************************************************************************/
     931             : 
     932           0 : static char *CPLLoadContentFromFile(const char *pszFilename)
     933             : {
     934           0 :     VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
     935           0 :     if (fp == nullptr)
     936           0 :         return nullptr;
     937           0 :     if (VSIFSeekL(fp, 0, SEEK_END) != 0)
     938             :     {
     939           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
     940           0 :         return nullptr;
     941             :     }
     942           0 :     vsi_l_offset nSize = VSIFTellL(fp);
     943           0 :     if (VSIFSeekL(fp, 0, SEEK_SET) != 0)
     944             :     {
     945           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
     946           0 :         return nullptr;
     947             :     }
     948           0 :     if (static_cast<vsi_l_offset>(static_cast<int>(nSize)) != nSize ||
     949             :         nSize > INT_MAX - 1)
     950             :     {
     951           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
     952           0 :         return nullptr;
     953             :     }
     954             :     char *pszBuffer =
     955           0 :         static_cast<char *>(VSIMalloc(static_cast<size_t>(nSize) + 1));
     956           0 :     if (pszBuffer == nullptr)
     957             :     {
     958           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
     959           0 :         return nullptr;
     960             :     }
     961           0 :     if (static_cast<size_t>(VSIFReadL(pszBuffer, 1, static_cast<size_t>(nSize),
     962           0 :                                       fp)) != static_cast<size_t>(nSize))
     963             :     {
     964           0 :         VSIFree(pszBuffer);
     965           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
     966           0 :         return nullptr;
     967             :     }
     968           0 :     pszBuffer[nSize] = '\0';
     969           0 :     CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
     970           0 :     return pszBuffer;
     971             : }
     972             : 
     973             : /************************************************************************/
     974             : /*                         CPLLoadXMLSchema()                           */
     975             : /************************************************************************/
     976             : 
     977             : typedef void *CPLXMLSchemaPtr;
     978             : 
     979             : /**
     980             :  * \brief Load a XSD schema.
     981             :  *
     982             :  * The return value should be freed with CPLFreeXMLSchema().
     983             :  *
     984             :  * @param pszXSDFilename XSD schema to load.
     985             :  * @return a handle to the parsed XML schema, or NULL in case of failure.
     986             :  *
     987             :  * @since GDAL 1.10.0
     988             :  */
     989             : 
     990         486 : static CPLXMLSchemaPtr CPLLoadXMLSchema(const char *pszXSDFilename)
     991             : {
     992         486 :     char *pszStr = CPLLoadSchemaStr(pszXSDFilename);
     993         486 :     if (pszStr == nullptr)
     994           0 :         return nullptr;
     995             : 
     996         486 :     xmlExternalEntityLoader pfnLibXMLOldExtranerEntityLoaderLocal = nullptr;
     997         486 :     pfnLibXMLOldExtranerEntityLoaderLocal = xmlGetExternalEntityLoader();
     998         486 :     pfnLibXMLOldExtranerEntityLoader = pfnLibXMLOldExtranerEntityLoaderLocal;
     999         486 :     xmlSetExternalEntityLoader(CPLExternalEntityLoader);
    1000             : 
    1001             :     xmlSchemaParserCtxtPtr pSchemaParserCtxt =
    1002         486 :         xmlSchemaNewMemParserCtxt(pszStr, static_cast<int>(strlen(pszStr)));
    1003             : 
    1004         486 :     xmlSchemaSetParserErrors(pSchemaParserCtxt, CPLLibXMLWarningErrorCallback,
    1005             :                              CPLLibXMLWarningErrorCallback, nullptr);
    1006             : 
    1007         486 :     xmlSchemaPtr pSchema = xmlSchemaParse(pSchemaParserCtxt);
    1008         486 :     xmlSchemaFreeParserCtxt(pSchemaParserCtxt);
    1009             : 
    1010         486 :     xmlSetExternalEntityLoader(pfnLibXMLOldExtranerEntityLoaderLocal);
    1011             : 
    1012         486 :     CPLFree(pszStr);
    1013             : 
    1014         486 :     return static_cast<CPLXMLSchemaPtr>(pSchema);
    1015             : }
    1016             : 
    1017             : /************************************************************************/
    1018             : /*                         CPLFreeXMLSchema()                           */
    1019             : /************************************************************************/
    1020             : 
    1021             : /**
    1022             :  * \brief Free a XSD schema.
    1023             :  *
    1024             :  * @param pSchema a handle to the parsed XML schema.
    1025             :  *
    1026             :  * @since GDAL 1.10.0
    1027             :  */
    1028             : 
    1029         486 : static void CPLFreeXMLSchema(CPLXMLSchemaPtr pSchema)
    1030             : {
    1031         486 :     if (pSchema)
    1032         486 :         xmlSchemaFree(static_cast<xmlSchemaPtr>(pSchema));
    1033         486 : }
    1034             : 
    1035             : /************************************************************************/
    1036             : /*                          CPLValidateXML()                            */
    1037             : /************************************************************************/
    1038             : 
    1039             : /**
    1040             :  * \brief Validate a XML file against a XML schema.
    1041             :  *
    1042             :  * @param pszXMLFilename the filename of the XML file to validate.
    1043             :  * @param pszXSDFilename the filename of the XSD schema.
    1044             :  * @param papszOptions unused for now. Set to NULL.
    1045             :  * @return TRUE if the XML file validates against the XML schema.
    1046             :  *
    1047             :  * @since GDAL 1.10.0
    1048             :  */
    1049             : 
    1050         486 : int CPLValidateXML(const char *pszXMLFilename, const char *pszXSDFilename,
    1051             :                    CPL_UNUSED CSLConstList papszOptions)
    1052             : {
    1053         486 :     char szHeader[2048] = {};  // TODO(schwehr): Get this off of the stack.
    1054         972 :     CPLString osTmpXSDFilename;
    1055             : 
    1056         486 :     if (pszXMLFilename[0] == '<')
    1057             :     {
    1058         481 :         strncpy(szHeader, pszXMLFilename, sizeof(szHeader));
    1059         481 :         szHeader[sizeof(szHeader) - 1] = '\0';
    1060             :     }
    1061             :     else
    1062             :     {
    1063           5 :         VSILFILE *fpXML = VSIFOpenL(pszXMLFilename, "rb");
    1064           5 :         if (fpXML == nullptr)
    1065             :         {
    1066           0 :             CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
    1067             :                      pszXMLFilename);
    1068           0 :             return FALSE;
    1069             :         }
    1070             :         const vsi_l_offset nRead =
    1071           5 :             VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fpXML);
    1072           5 :         szHeader[nRead] = '\0';
    1073           5 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fpXML));
    1074             :     }
    1075             : 
    1076             :     // Workaround following bug:
    1077             :     //
    1078             :     // "element FeatureCollection: Schemas validity error : Element
    1079             :     // '{http://www.opengis.net/wfs}FeatureCollection': No matching global
    1080             :     // declaration available for the validation root"
    1081             :     //
    1082             :     // We create a wrapping XSD that imports the WFS .xsd (and possibly the GML
    1083             :     // .xsd too) and the application schema.  This is a known libxml2
    1084             :     // limitation.
    1085         486 :     if (strstr(szHeader, "<wfs:FeatureCollection") ||
    1086         486 :         (strstr(szHeader, "<FeatureCollection") &&
    1087           0 :          strstr(szHeader, "xmlns:wfs=\"http://www.opengis.net/wfs\"")))
    1088             :     {
    1089           0 :         const char *pszWFSSchemaNamespace = "http://www.opengis.net/wfs";
    1090           0 :         const char *pszWFSSchemaLocation = nullptr;
    1091           0 :         const char *pszGMLSchemaLocation = nullptr;
    1092           0 :         if (strstr(szHeader, "wfs/1.0.0/WFS-basic.xsd"))
    1093             :         {
    1094           0 :             pszWFSSchemaLocation =
    1095             :                 "http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd";
    1096             :         }
    1097           0 :         else if (strstr(szHeader, "wfs/1.1.0/wfs.xsd"))
    1098             :         {
    1099           0 :             pszWFSSchemaLocation =
    1100             :                 "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";
    1101             :         }
    1102           0 :         else if (strstr(szHeader, "wfs/2.0/wfs.xsd"))
    1103             :         {
    1104           0 :             pszWFSSchemaNamespace = "http://www.opengis.net/wfs/2.0";
    1105           0 :             pszWFSSchemaLocation = "http://schemas.opengis.net/wfs/2.0/wfs.xsd";
    1106             :         }
    1107             : 
    1108           0 :         VSILFILE *fpXSD = VSIFOpenL(pszXSDFilename, "rb");
    1109           0 :         if (fpXSD == nullptr)
    1110             :         {
    1111           0 :             CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
    1112             :                      pszXSDFilename);
    1113           0 :             return FALSE;
    1114             :         }
    1115             :         const vsi_l_offset nRead =
    1116           0 :             VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fpXSD);
    1117           0 :         szHeader[nRead] = '\0';
    1118           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fpXSD));
    1119             : 
    1120           0 :         if (strstr(szHeader, "gml/3.1.1") != nullptr &&
    1121           0 :             strstr(szHeader, "gml/3.1.1/base/gml.xsd") == nullptr)
    1122             :         {
    1123           0 :             pszGMLSchemaLocation =
    1124             :                 "http://schemas.opengis.net/gml/3.1.1/base/gml.xsd";
    1125             :         }
    1126             : 
    1127           0 :         if (pszWFSSchemaLocation != nullptr)
    1128             :         {
    1129             :             osTmpXSDFilename = CPLSPrintf("/vsimem/CPLValidateXML_%p_%p.xsd",
    1130           0 :                                           pszXMLFilename, pszXSDFilename);
    1131             :             char *const pszEscapedXSDFilename =
    1132           0 :                 CPLEscapeString(pszXSDFilename, -1, CPLES_XML);
    1133           0 :             VSILFILE *const fpMEM = VSIFOpenL(osTmpXSDFilename, "wb");
    1134           0 :             CPL_IGNORE_RET_VAL(VSIFPrintfL(
    1135             :                 fpMEM,
    1136             :                 "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n"));
    1137           0 :             CPL_IGNORE_RET_VAL(VSIFPrintfL(
    1138             :                 fpMEM,
    1139             :                 "   <xs:import namespace=\"%s\" schemaLocation=\"%s\"/>\n",
    1140             :                 pszWFSSchemaNamespace, pszWFSSchemaLocation));
    1141           0 :             CPL_IGNORE_RET_VAL(VSIFPrintfL(
    1142             :                 fpMEM,
    1143             :                 "   <xs:import namespace=\"ignored\" schemaLocation=\"%s\"/>\n",
    1144             :                 pszEscapedXSDFilename));
    1145           0 :             if (pszGMLSchemaLocation)
    1146           0 :                 CPL_IGNORE_RET_VAL(VSIFPrintfL(
    1147             :                     fpMEM,
    1148             :                     "   <xs:import namespace=\"http://www.opengis.net/gml\" "
    1149             :                     "schemaLocation=\"%s\"/>\n",
    1150             :                     pszGMLSchemaLocation));
    1151           0 :             CPL_IGNORE_RET_VAL(VSIFPrintfL(fpMEM, "</xs:schema>\n"));
    1152           0 :             CPL_IGNORE_RET_VAL(VSIFCloseL(fpMEM));
    1153           0 :             CPLFree(pszEscapedXSDFilename);
    1154             :         }
    1155             :     }
    1156             : 
    1157         486 :     CPLXMLSchemaPtr pSchema = CPLLoadXMLSchema(
    1158         486 :         !osTmpXSDFilename.empty() ? osTmpXSDFilename.c_str() : pszXSDFilename);
    1159         486 :     if (!osTmpXSDFilename.empty())
    1160           0 :         VSIUnlink(osTmpXSDFilename);
    1161         486 :     if (pSchema == nullptr)
    1162           0 :         return FALSE;
    1163             : 
    1164             :     xmlSchemaValidCtxtPtr pSchemaValidCtxt =
    1165         486 :         xmlSchemaNewValidCtxt(static_cast<xmlSchemaPtr>(pSchema));
    1166             : 
    1167         486 :     if (pSchemaValidCtxt == nullptr)
    1168             :     {
    1169           0 :         CPLFreeXMLSchema(pSchema);
    1170           0 :         return FALSE;
    1171             :     }
    1172             : 
    1173         486 :     xmlSchemaSetValidErrors(pSchemaValidCtxt, CPLLibXMLWarningErrorCallback,
    1174             :                             CPLLibXMLWarningErrorCallback,
    1175             :                             const_cast<char *>(pszXMLFilename));
    1176             : 
    1177         486 :     bool bValid = false;
    1178         486 :     if (pszXMLFilename[0] == '<')
    1179             :     {
    1180             :         xmlDocPtr pDoc =
    1181         481 :             xmlParseDoc(reinterpret_cast<const xmlChar *>(pszXMLFilename));
    1182         481 :         if (pDoc != nullptr)
    1183             :         {
    1184         481 :             bValid = xmlSchemaValidateDoc(pSchemaValidCtxt, pDoc) == 0;
    1185             :         }
    1186         481 :         xmlFreeDoc(pDoc);
    1187             :     }
    1188           5 :     else if (!STARTS_WITH(pszXMLFilename, "/vsi"))
    1189             :     {
    1190           5 :         bValid =
    1191           5 :             xmlSchemaValidateFile(pSchemaValidCtxt, pszXMLFilename, 0) == 0;
    1192             :     }
    1193             :     else
    1194             :     {
    1195           0 :         char *pszXML = CPLLoadContentFromFile(pszXMLFilename);
    1196           0 :         if (pszXML != nullptr)
    1197             :         {
    1198             :             xmlDocPtr pDoc =
    1199           0 :                 xmlParseDoc(reinterpret_cast<const xmlChar *>(pszXML));
    1200           0 :             if (pDoc != nullptr)
    1201             :             {
    1202           0 :                 bValid = xmlSchemaValidateDoc(pSchemaValidCtxt, pDoc) == 0;
    1203             :             }
    1204           0 :             xmlFreeDoc(pDoc);
    1205             :         }
    1206           0 :         CPLFree(pszXML);
    1207             :     }
    1208         486 :     xmlSchemaFreeValidCtxt(pSchemaValidCtxt);
    1209         486 :     CPLFreeXMLSchema(pSchema);
    1210             : 
    1211         486 :     return bValid;
    1212             : }
    1213             : 
    1214             : #else  // HAVE_RECENT_LIBXML2
    1215             : 
    1216             : /************************************************************************/
    1217             : /*                          CPLValidateXML()                            */
    1218             : /************************************************************************/
    1219             : 
    1220             : int CPLValidateXML(const char * /* pszXMLFilename */,
    1221             :                    const char * /* pszXSDFilename */,
    1222             :                    CSLConstList /* papszOptions */)
    1223             : {
    1224             :     CPLError(CE_Failure, CPLE_NotSupported,
    1225             :              "%s not implemented due to missing libxml2 support",
    1226             :              "CPLValidateXML()");
    1227             :     return FALSE;
    1228             : }
    1229             : 
    1230             : #endif  // HAVE_RECENT_LIBXML2

Generated by: LCOV version 1.14