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 : /* CPLFindLocalXSD() */
610 : /************************************************************************/
611 :
612 0 : static CPLString CPLFindLocalXSD(const char *pszXSDFilename)
613 : {
614 0 : CPLString osTmp;
615 : const char *pszSchemasOpenGIS =
616 0 : CPLGetConfigOption("GDAL_OPENGIS_SCHEMAS", nullptr);
617 0 : if (pszSchemasOpenGIS != nullptr)
618 : {
619 0 : int nLen = static_cast<int>(strlen(pszSchemasOpenGIS));
620 0 : if (nLen > 0 && pszSchemasOpenGIS[nLen - 1] == '/')
621 : {
622 0 : osTmp = pszSchemasOpenGIS;
623 0 : osTmp += pszXSDFilename;
624 : }
625 : else
626 : {
627 0 : osTmp = pszSchemasOpenGIS;
628 0 : osTmp += "/";
629 0 : osTmp += pszXSDFilename;
630 : }
631 : }
632 0 : else if ((pszSchemasOpenGIS = CPLFindFile("gdal", "SCHEMAS_OPENGIS_NET")) !=
633 : nullptr)
634 : {
635 0 : osTmp = pszSchemasOpenGIS;
636 0 : osTmp += "/";
637 0 : osTmp += pszXSDFilename;
638 : }
639 :
640 : VSIStatBufL sStatBuf;
641 0 : if (VSIStatExL(osTmp, &sStatBuf, VSI_STAT_EXISTS_FLAG) == 0)
642 0 : return osTmp;
643 0 : return "";
644 : }
645 :
646 : /************************************************************************/
647 : /* CPLExternalEntityLoader() */
648 : /************************************************************************/
649 :
650 : constexpr char szXML_XSD[] =
651 : "<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" "
652 : "targetNamespace=\"http://www.w3.org/XML/1998/namespace\">"
653 : "<attribute name=\"lang\">"
654 : "<simpleType>"
655 : "<union memberTypes=\"language\">"
656 : "<simpleType>"
657 : "<restriction base=\"string\">"
658 : "<enumeration value=\"\"/>"
659 : "</restriction>"
660 : "</simpleType>"
661 : "</union>"
662 : "</simpleType>"
663 : "</attribute>"
664 : "<attribute name=\"space\">"
665 : "<simpleType>"
666 : "<restriction base=\"NCName\">"
667 : "<enumeration value=\"default\"/>"
668 : "<enumeration value=\"preserve\"/>"
669 : "</restriction>"
670 : "</simpleType>"
671 : "</attribute>"
672 : "<attribute name=\"base\" type=\"anyURI\"/>"
673 : "<attribute name=\"id\" type=\"ID\"/>"
674 : "<attributeGroup name=\"specialAttrs\">"
675 : "<attribute ref=\"xml:base\"/>"
676 : "<attribute ref=\"xml:lang\"/>"
677 : "<attribute ref=\"xml:space\"/>"
678 : "<attribute ref=\"xml:id\"/>"
679 : "</attributeGroup>"
680 : "</schema>";
681 :
682 : // Simplified (and truncated) version of http://www.w3.org/1999/xlink.xsd
683 : // (sufficient for GML schemas).
684 : constexpr char szXLINK_XSD[] =
685 : "<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" "
686 : "targetNamespace=\"http://www.w3.org/1999/xlink\" "
687 : "xmlns:xlink=\"http://www.w3.org/1999/xlink\">"
688 : "<attribute name=\"type\" type=\"string\"/>"
689 : "<attribute name=\"href\" type=\"anyURI\"/>"
690 : "<attribute name=\"role\" type=\"anyURI\"/>"
691 : "<attribute name=\"arcrole\" type=\"anyURI\"/>"
692 : "<attribute name=\"title\" type=\"string\"/>"
693 : "<attribute name=\"show\" type=\"string\"/>"
694 : "<attribute name=\"actuate\" type=\"string\"/>"
695 : "<attribute name=\"label\" type=\"NCName\"/>"
696 : "<attribute name=\"from\" type=\"NCName\"/>"
697 : "<attribute name=\"to\" type=\"NCName\"/>"
698 : "<attributeGroup name=\"simpleAttrs\">"
699 : "<attribute ref=\"xlink:type\" fixed=\"simple\"/>"
700 : "<attribute ref=\"xlink:href\"/>"
701 : "<attribute ref=\"xlink:role\"/>"
702 : "<attribute ref=\"xlink:arcrole\"/>"
703 : "<attribute ref=\"xlink:title\"/>"
704 : "<attribute ref=\"xlink:show\"/>"
705 : "<attribute ref=\"xlink:actuate\"/>"
706 : "</attributeGroup>"
707 : "</schema>";
708 :
709 0 : static xmlParserInputPtr CPLExternalEntityLoader(const char *URL,
710 : const char *ID,
711 : xmlParserCtxtPtr context)
712 : {
713 : #if DEBUG_VERBOSE
714 : CPLDebug("CPL", "CPLExternalEntityLoader(%s)", URL);
715 : #endif
716 : // Use libxml2 catalog mechanism to resolve the URL to something else.
717 : // xmlChar* pszResolved = xmlCatalogResolveSystem((const xmlChar*)URL);
718 : xmlChar *pszResolved =
719 0 : xmlCatalogResolveSystem(reinterpret_cast<const xmlChar *>(URL));
720 0 : if (pszResolved == nullptr)
721 : pszResolved =
722 0 : xmlCatalogResolveURI(reinterpret_cast<const xmlChar *>(URL));
723 0 : CPLString osURL;
724 0 : if (pszResolved)
725 : {
726 0 : CPLDebug("CPL", "Resolving %s in %s", URL,
727 : reinterpret_cast<const char *>(pszResolved));
728 0 : osURL = reinterpret_cast<const char *>(pszResolved);
729 0 : URL = osURL.c_str();
730 0 : xmlFree(pszResolved);
731 0 : pszResolved = nullptr;
732 : }
733 :
734 0 : if (STARTS_WITH(URL, "http://"))
735 : {
736 : // Make sure to use http://schemas.opengis.net/
737 : // when gml/2 or gml/3 is detected.
738 0 : const char *pszGML = strstr(URL, "gml/2");
739 0 : if (pszGML == nullptr)
740 0 : pszGML = strstr(URL, "gml/3");
741 0 : if (pszGML != nullptr)
742 : {
743 0 : osURL = "http://schemas.opengis.net/";
744 0 : osURL += pszGML;
745 0 : URL = osURL.c_str();
746 : }
747 0 : else if (strcmp(URL, "http://www.w3.org/2001/xml.xsd") == 0)
748 : {
749 0 : std::string osTmp = CPLFindLocalXSD("xml.xsd");
750 0 : if (!osTmp.empty())
751 : {
752 0 : osURL = std::move(osTmp);
753 0 : URL = osURL.c_str();
754 : }
755 : else
756 : {
757 0 : CPLDebug("CPL", "Resolving %s to local definition",
758 : "http://www.w3.org/2001/xml.xsd");
759 0 : return xmlNewStringInputStream(
760 0 : context, reinterpret_cast<const xmlChar *>(szXML_XSD));
761 : }
762 : }
763 0 : else if (strcmp(URL, "http://www.w3.org/1999/xlink.xsd") == 0)
764 : {
765 0 : std::string osTmp = CPLFindLocalXSD("xlink.xsd");
766 0 : if (!osTmp.empty())
767 : {
768 0 : osURL = std::move(osTmp);
769 0 : URL = osURL.c_str();
770 : }
771 : else
772 : {
773 0 : CPLDebug("CPL", "Resolving %s to local definition",
774 : "http://www.w3.org/1999/xlink.xsd");
775 0 : return xmlNewStringInputStream(
776 0 : context, reinterpret_cast<const xmlChar *>(szXLINK_XSD));
777 : }
778 : }
779 0 : else if (!STARTS_WITH(URL, "http://schemas.opengis.net/"))
780 : {
781 0 : CPLDebug("CPL", "Loading %s", URL);
782 0 : return pfnLibXMLOldExtranerEntityLoader(URL, ID, context);
783 : }
784 : }
785 0 : else if (STARTS_WITH(URL, "ftp://"))
786 : {
787 0 : return pfnLibXMLOldExtranerEntityLoader(URL, ID, context);
788 : }
789 0 : else if (STARTS_WITH(URL, "file://"))
790 : {
791 : // Parse file:// URI so as to be able to open them with VSI*L API.
792 0 : if (STARTS_WITH(URL, "file://localhost/"))
793 0 : URL += 16;
794 : else
795 0 : URL += 7;
796 :
797 0 : if (URL[0] == '/' && URL[1] != '\0' && URL[2] == ':' && URL[3] == '/')
798 : {
799 : // Windows.
800 0 : ++URL;
801 : }
802 0 : else if (URL[0] == '/')
803 : {
804 : // Unix.
805 : }
806 : else
807 : {
808 0 : return pfnLibXMLOldExtranerEntityLoader(URL, ID, context);
809 : }
810 : }
811 :
812 0 : CPLString osModURL;
813 0 : if (STARTS_WITH(URL, "/vsizip/vsicurl/http%3A//"))
814 : {
815 0 : osModURL = "/vsizip/vsicurl/http://";
816 0 : osModURL += URL + strlen("/vsizip/vsicurl/http%3A//");
817 : }
818 0 : else if (STARTS_WITH(URL, "/vsicurl/http%3A//"))
819 : {
820 0 : osModURL = "vsicurl/http://";
821 0 : osModURL += URL + strlen("/vsicurl/http%3A//");
822 : }
823 0 : else if (STARTS_WITH(URL, "http://schemas.opengis.net/"))
824 : {
825 0 : const char *pszAfterOpenGIS =
826 : URL + strlen("http://schemas.opengis.net/");
827 :
828 : const char *pszSchemasOpenGIS =
829 0 : CPLGetConfigOption("GDAL_OPENGIS_SCHEMAS", nullptr);
830 0 : if (pszSchemasOpenGIS != nullptr)
831 : {
832 0 : const int nLen = static_cast<int>(strlen(pszSchemasOpenGIS));
833 0 : if (nLen > 0 && pszSchemasOpenGIS[nLen - 1] == '/')
834 : {
835 0 : osModURL = pszSchemasOpenGIS;
836 0 : osModURL += pszAfterOpenGIS;
837 : }
838 : else
839 : {
840 0 : osModURL = pszSchemasOpenGIS;
841 0 : osModURL += "/";
842 0 : osModURL += pszAfterOpenGIS;
843 : }
844 : }
845 0 : else if ((pszSchemasOpenGIS =
846 0 : CPLFindFile("gdal", "SCHEMAS_OPENGIS_NET")) != nullptr)
847 : {
848 0 : osModURL = pszSchemasOpenGIS;
849 0 : osModURL += "/";
850 0 : osModURL += pszAfterOpenGIS;
851 : }
852 0 : else if ((pszSchemasOpenGIS = CPLFindFile(
853 0 : "gdal", "SCHEMAS_OPENGIS_NET.zip")) != nullptr)
854 : {
855 0 : osModURL = "/vsizip/";
856 0 : osModURL += pszSchemasOpenGIS;
857 0 : osModURL += "/";
858 0 : osModURL += pszAfterOpenGIS;
859 : }
860 : else
861 : {
862 : osModURL = "/vsizip/vsicurl/"
863 0 : "http://schemas.opengis.net/SCHEMAS_OPENGIS_NET.zip/";
864 0 : osModURL += pszAfterOpenGIS;
865 : }
866 : }
867 : else
868 : {
869 0 : osModURL = URL;
870 : }
871 :
872 0 : char *pszSchema = CPLLoadSchemaStr(osModURL);
873 0 : if (!pszSchema)
874 0 : return nullptr;
875 :
876 0 : xmlParserInputPtr parser = xmlNewStringInputStream(
877 : context, reinterpret_cast<const xmlChar *>(pszSchema));
878 0 : CPLFree(pszSchema);
879 :
880 0 : return parser;
881 : }
882 :
883 : /************************************************************************/
884 : /* CPLLibXMLWarningErrorCallback() */
885 : /************************************************************************/
886 :
887 81 : static void CPLLibXMLWarningErrorCallback(void *ctx, const char *msg, ...)
888 : {
889 : va_list varg;
890 81 : va_start(varg, msg);
891 :
892 81 : char *pszStr = reinterpret_cast<char *>(va_arg(varg, char *));
893 :
894 81 : if (strstr(pszStr, "since this namespace was already imported") == nullptr)
895 : {
896 81 : const xmlError *pErrorPtr = xmlGetLastError();
897 81 : const char *pszFilename = static_cast<char *>(ctx);
898 81 : char *pszStrDup = CPLStrdup(pszStr);
899 81 : int nLen = static_cast<int>(strlen(pszStrDup));
900 81 : if (nLen > 0 && pszStrDup[nLen - 1] == '\n')
901 81 : pszStrDup[nLen - 1] = '\0';
902 81 : if (pszFilename != nullptr && pszFilename[0] != '<')
903 : {
904 0 : CPLError(CE_Failure, CPLE_AppDefined, "libXML: %s:%d: %s",
905 : pszFilename, pErrorPtr ? pErrorPtr->line : 0, pszStrDup);
906 : }
907 : else
908 : {
909 81 : CPLError(CE_Failure, CPLE_AppDefined, "libXML: %d: %s",
910 : pErrorPtr ? pErrorPtr->line : 0, pszStrDup);
911 : }
912 81 : CPLFree(pszStrDup);
913 : }
914 :
915 81 : va_end(varg);
916 81 : }
917 :
918 : /************************************************************************/
919 : /* CPLLoadContentFromFile() */
920 : /************************************************************************/
921 :
922 0 : static char *CPLLoadContentFromFile(const char *pszFilename)
923 : {
924 0 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
925 0 : if (fp == nullptr)
926 0 : return nullptr;
927 0 : if (VSIFSeekL(fp, 0, SEEK_END) != 0)
928 : {
929 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
930 0 : return nullptr;
931 : }
932 0 : vsi_l_offset nSize = VSIFTellL(fp);
933 0 : if (VSIFSeekL(fp, 0, SEEK_SET) != 0)
934 : {
935 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
936 0 : return nullptr;
937 : }
938 0 : if (static_cast<vsi_l_offset>(static_cast<int>(nSize)) != nSize ||
939 : nSize > INT_MAX - 1)
940 : {
941 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
942 0 : return nullptr;
943 : }
944 : char *pszBuffer =
945 0 : static_cast<char *>(VSIMalloc(static_cast<size_t>(nSize) + 1));
946 0 : if (pszBuffer == nullptr)
947 : {
948 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
949 0 : return nullptr;
950 : }
951 0 : if (static_cast<size_t>(VSIFReadL(pszBuffer, 1, static_cast<size_t>(nSize),
952 0 : fp)) != static_cast<size_t>(nSize))
953 : {
954 0 : VSIFree(pszBuffer);
955 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
956 0 : return nullptr;
957 : }
958 0 : pszBuffer[nSize] = '\0';
959 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
960 0 : return pszBuffer;
961 : }
962 :
963 : /************************************************************************/
964 : /* CPLLoadXMLSchema() */
965 : /************************************************************************/
966 :
967 : typedef void *CPLXMLSchemaPtr;
968 :
969 : /**
970 : * \brief Load a XSD schema.
971 : *
972 : * The return value should be freed with CPLFreeXMLSchema().
973 : *
974 : * @param pszXSDFilename XSD schema to load.
975 : * @return a handle to the parsed XML schema, or NULL in case of failure.
976 : *
977 : * @since GDAL 1.10.0
978 : */
979 :
980 486 : static CPLXMLSchemaPtr CPLLoadXMLSchema(const char *pszXSDFilename)
981 : {
982 486 : char *pszStr = CPLLoadSchemaStr(pszXSDFilename);
983 486 : if (pszStr == nullptr)
984 0 : return nullptr;
985 :
986 486 : xmlExternalEntityLoader pfnLibXMLOldExtranerEntityLoaderLocal = nullptr;
987 486 : pfnLibXMLOldExtranerEntityLoaderLocal = xmlGetExternalEntityLoader();
988 486 : pfnLibXMLOldExtranerEntityLoader = pfnLibXMLOldExtranerEntityLoaderLocal;
989 486 : xmlSetExternalEntityLoader(CPLExternalEntityLoader);
990 :
991 : xmlSchemaParserCtxtPtr pSchemaParserCtxt =
992 486 : xmlSchemaNewMemParserCtxt(pszStr, static_cast<int>(strlen(pszStr)));
993 :
994 486 : xmlSchemaSetParserErrors(pSchemaParserCtxt, CPLLibXMLWarningErrorCallback,
995 : CPLLibXMLWarningErrorCallback, nullptr);
996 :
997 486 : xmlSchemaPtr pSchema = xmlSchemaParse(pSchemaParserCtxt);
998 486 : xmlSchemaFreeParserCtxt(pSchemaParserCtxt);
999 :
1000 486 : xmlSetExternalEntityLoader(pfnLibXMLOldExtranerEntityLoaderLocal);
1001 :
1002 486 : CPLFree(pszStr);
1003 :
1004 486 : return static_cast<CPLXMLSchemaPtr>(pSchema);
1005 : }
1006 :
1007 : /************************************************************************/
1008 : /* CPLFreeXMLSchema() */
1009 : /************************************************************************/
1010 :
1011 : /**
1012 : * \brief Free a XSD schema.
1013 : *
1014 : * @param pSchema a handle to the parsed XML schema.
1015 : *
1016 : * @since GDAL 1.10.0
1017 : */
1018 :
1019 486 : static void CPLFreeXMLSchema(CPLXMLSchemaPtr pSchema)
1020 : {
1021 486 : if (pSchema)
1022 486 : xmlSchemaFree(static_cast<xmlSchemaPtr>(pSchema));
1023 486 : }
1024 :
1025 : /************************************************************************/
1026 : /* CPLValidateXML() */
1027 : /************************************************************************/
1028 :
1029 : /**
1030 : * \brief Validate a XML file against a XML schema.
1031 : *
1032 : * @param pszXMLFilename the filename of the XML file to validate.
1033 : * @param pszXSDFilename the filename of the XSD schema.
1034 : * @param papszOptions unused for now. Set to NULL.
1035 : * @return TRUE if the XML file validates against the XML schema.
1036 : *
1037 : * @since GDAL 1.10.0
1038 : */
1039 :
1040 486 : int CPLValidateXML(const char *pszXMLFilename, const char *pszXSDFilename,
1041 : CPL_UNUSED CSLConstList papszOptions)
1042 : {
1043 486 : char szHeader[2048] = {}; // TODO(schwehr): Get this off of the stack.
1044 972 : CPLString osTmpXSDFilename;
1045 :
1046 486 : if (pszXMLFilename[0] == '<')
1047 : {
1048 481 : strncpy(szHeader, pszXMLFilename, sizeof(szHeader));
1049 481 : szHeader[sizeof(szHeader) - 1] = '\0';
1050 : }
1051 : else
1052 : {
1053 5 : VSILFILE *fpXML = VSIFOpenL(pszXMLFilename, "rb");
1054 5 : if (fpXML == nullptr)
1055 : {
1056 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
1057 : pszXMLFilename);
1058 0 : return FALSE;
1059 : }
1060 : const vsi_l_offset nRead =
1061 5 : VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fpXML);
1062 5 : szHeader[nRead] = '\0';
1063 5 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpXML));
1064 : }
1065 :
1066 : // Workaround following bug:
1067 : //
1068 : // "element FeatureCollection: Schemas validity error : Element
1069 : // '{http://www.opengis.net/wfs}FeatureCollection': No matching global
1070 : // declaration available for the validation root"
1071 : //
1072 : // We create a wrapping XSD that imports the WFS .xsd (and possibly the GML
1073 : // .xsd too) and the application schema. This is a known libxml2
1074 : // limitation.
1075 486 : if (strstr(szHeader, "<wfs:FeatureCollection") ||
1076 486 : (strstr(szHeader, "<FeatureCollection") &&
1077 0 : strstr(szHeader, "xmlns:wfs=\"http://www.opengis.net/wfs\"")))
1078 : {
1079 0 : const char *pszWFSSchemaNamespace = "http://www.opengis.net/wfs";
1080 0 : const char *pszWFSSchemaLocation = nullptr;
1081 0 : const char *pszGMLSchemaLocation = nullptr;
1082 0 : if (strstr(szHeader, "wfs/1.0.0/WFS-basic.xsd"))
1083 : {
1084 0 : pszWFSSchemaLocation =
1085 : "http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd";
1086 : }
1087 0 : else if (strstr(szHeader, "wfs/1.1.0/wfs.xsd"))
1088 : {
1089 0 : pszWFSSchemaLocation =
1090 : "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";
1091 : }
1092 0 : else if (strstr(szHeader, "wfs/2.0/wfs.xsd"))
1093 : {
1094 0 : pszWFSSchemaNamespace = "http://www.opengis.net/wfs/2.0";
1095 0 : pszWFSSchemaLocation = "http://schemas.opengis.net/wfs/2.0/wfs.xsd";
1096 : }
1097 :
1098 0 : VSILFILE *fpXSD = VSIFOpenL(pszXSDFilename, "rb");
1099 0 : if (fpXSD == nullptr)
1100 : {
1101 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
1102 : pszXSDFilename);
1103 0 : return FALSE;
1104 : }
1105 : const vsi_l_offset nRead =
1106 0 : VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fpXSD);
1107 0 : szHeader[nRead] = '\0';
1108 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpXSD));
1109 :
1110 0 : if (strstr(szHeader, "gml/3.1.1") != nullptr &&
1111 0 : strstr(szHeader, "gml/3.1.1/base/gml.xsd") == nullptr)
1112 : {
1113 0 : pszGMLSchemaLocation =
1114 : "http://schemas.opengis.net/gml/3.1.1/base/gml.xsd";
1115 : }
1116 :
1117 0 : if (pszWFSSchemaLocation != nullptr)
1118 : {
1119 : osTmpXSDFilename = CPLSPrintf("/vsimem/CPLValidateXML_%p_%p.xsd",
1120 0 : pszXMLFilename, pszXSDFilename);
1121 : char *const pszEscapedXSDFilename =
1122 0 : CPLEscapeString(pszXSDFilename, -1, CPLES_XML);
1123 0 : VSILFILE *const fpMEM = VSIFOpenL(osTmpXSDFilename, "wb");
1124 0 : CPL_IGNORE_RET_VAL(VSIFPrintfL(
1125 : fpMEM,
1126 : "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n"));
1127 0 : CPL_IGNORE_RET_VAL(VSIFPrintfL(
1128 : fpMEM,
1129 : " <xs:import namespace=\"%s\" schemaLocation=\"%s\"/>\n",
1130 : pszWFSSchemaNamespace, pszWFSSchemaLocation));
1131 0 : CPL_IGNORE_RET_VAL(VSIFPrintfL(
1132 : fpMEM,
1133 : " <xs:import namespace=\"ignored\" schemaLocation=\"%s\"/>\n",
1134 : pszEscapedXSDFilename));
1135 0 : if (pszGMLSchemaLocation)
1136 0 : CPL_IGNORE_RET_VAL(VSIFPrintfL(
1137 : fpMEM,
1138 : " <xs:import namespace=\"http://www.opengis.net/gml\" "
1139 : "schemaLocation=\"%s\"/>\n",
1140 : pszGMLSchemaLocation));
1141 0 : CPL_IGNORE_RET_VAL(VSIFPrintfL(fpMEM, "</xs:schema>\n"));
1142 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpMEM));
1143 0 : CPLFree(pszEscapedXSDFilename);
1144 : }
1145 : }
1146 :
1147 486 : CPLXMLSchemaPtr pSchema = CPLLoadXMLSchema(
1148 486 : !osTmpXSDFilename.empty() ? osTmpXSDFilename.c_str() : pszXSDFilename);
1149 486 : if (!osTmpXSDFilename.empty())
1150 0 : VSIUnlink(osTmpXSDFilename);
1151 486 : if (pSchema == nullptr)
1152 0 : return FALSE;
1153 :
1154 : xmlSchemaValidCtxtPtr pSchemaValidCtxt =
1155 486 : xmlSchemaNewValidCtxt(static_cast<xmlSchemaPtr>(pSchema));
1156 :
1157 486 : if (pSchemaValidCtxt == nullptr)
1158 : {
1159 0 : CPLFreeXMLSchema(pSchema);
1160 0 : return FALSE;
1161 : }
1162 :
1163 486 : xmlSchemaSetValidErrors(pSchemaValidCtxt, CPLLibXMLWarningErrorCallback,
1164 : CPLLibXMLWarningErrorCallback,
1165 : const_cast<char *>(pszXMLFilename));
1166 :
1167 486 : bool bValid = false;
1168 486 : if (pszXMLFilename[0] == '<')
1169 : {
1170 : xmlDocPtr pDoc =
1171 481 : xmlParseDoc(reinterpret_cast<const xmlChar *>(pszXMLFilename));
1172 481 : if (pDoc != nullptr)
1173 : {
1174 481 : bValid = xmlSchemaValidateDoc(pSchemaValidCtxt, pDoc) == 0;
1175 : }
1176 481 : xmlFreeDoc(pDoc);
1177 : }
1178 5 : else if (!STARTS_WITH(pszXMLFilename, "/vsi"))
1179 : {
1180 5 : bValid =
1181 5 : xmlSchemaValidateFile(pSchemaValidCtxt, pszXMLFilename, 0) == 0;
1182 : }
1183 : else
1184 : {
1185 0 : char *pszXML = CPLLoadContentFromFile(pszXMLFilename);
1186 0 : if (pszXML != nullptr)
1187 : {
1188 : xmlDocPtr pDoc =
1189 0 : xmlParseDoc(reinterpret_cast<const xmlChar *>(pszXML));
1190 0 : if (pDoc != nullptr)
1191 : {
1192 0 : bValid = xmlSchemaValidateDoc(pSchemaValidCtxt, pDoc) == 0;
1193 : }
1194 0 : xmlFreeDoc(pDoc);
1195 : }
1196 0 : CPLFree(pszXML);
1197 : }
1198 486 : xmlSchemaFreeValidCtxt(pSchemaValidCtxt);
1199 486 : CPLFreeXMLSchema(pSchema);
1200 :
1201 486 : return bValid;
1202 : }
1203 :
1204 : #else // HAVE_RECENT_LIBXML2
1205 :
1206 : /************************************************************************/
1207 : /* CPLValidateXML() */
1208 : /************************************************************************/
1209 :
1210 : int CPLValidateXML(const char * /* pszXMLFilename */,
1211 : const char * /* pszXSDFilename */,
1212 : CSLConstList /* papszOptions */)
1213 : {
1214 : CPLError(CE_Failure, CPLE_NotSupported,
1215 : "%s not implemented due to missing libxml2 support",
1216 : "CPLValidateXML()");
1217 : return FALSE;
1218 : }
1219 :
1220 : #endif // HAVE_RECENT_LIBXML2
|