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