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