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-11-21 22:18:42 Functions: 7 13 53.8 %

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

Generated by: LCOV version 1.14