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