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

Generated by: LCOV version 1.14