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