LCOV - code coverage report
Current view: top level - port - cpl_xml_validate.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 117 422 27.7 %
Date: 2025-07-01 22:47:05 Functions: 7 12 58.3 %

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

Generated by: LCOV version 1.14