|
5 | 5 | namespace SimpleSAML\XML; |
6 | 6 |
|
7 | 7 | use Dom; |
| 8 | +use Dom\XMLDocument; |
8 | 9 | use SimpleSAML\XML\Assert\Assert; |
9 | 10 | use SimpleSAML\XML\Exception\IOException; |
10 | 11 | use SimpleSAML\XML\Exception\RuntimeException; |
@@ -41,29 +42,55 @@ public static function schemaValidate(Dom\Document $document, ?string $schemaFil |
41 | 42 | $internalErrors = libxml_use_internal_errors(true); |
42 | 43 | libxml_clear_errors(); |
43 | 44 |
|
44 | | - if ($schemaFile === null) { |
45 | | - $schemaFile = self::getSchemaFile(); |
46 | | - } |
| 45 | + try { |
| 46 | + if ($schemaFile === null) { |
| 47 | + $schemaFile = self::getSchemaFile(); |
| 48 | + } |
47 | 49 |
|
48 | | - // Must suppress the warnings here in order to throw them as an error below. |
49 | | - $result = @$document->schemaValidate($schemaFile); |
| 50 | + if (!$document instanceof XMLDocument) { |
| 51 | + throw new \LogicException('Schema validation requires an instance of Dom\\XMLDocument.'); |
| 52 | + } |
50 | 53 |
|
51 | | - if ($result === false) { |
52 | | - $msgs = []; |
53 | | - foreach (libxml_get_errors() as $err) { |
54 | | - $msgs[] = trim($err->message) . ' on line ' . $err->line; |
| 54 | + /** |
| 55 | + * Validates using a legacy \DOMDocument round-trip (serialize + re-parse) before running schema validation. |
| 56 | + * This avoids false negatives seen with Dom\Document::schemaValidate(), especially around xs:QName |
| 57 | + * prefix scope. Validation is performed against the exact serialized XML that would be |
| 58 | + * exchanged externally. |
| 59 | + */ |
| 60 | + $root = $document->documentElement; |
| 61 | + if ($root === null) { |
| 62 | + throw new SchemaViolationException('The document has no document element.'); |
55 | 63 | } |
56 | 64 |
|
57 | | - throw new SchemaViolationException(sprintf( |
58 | | - "XML schema validation errors:\n - %s", |
59 | | - implode("\n - ", array_unique($msgs)), |
60 | | - )); |
61 | | - } |
| 65 | + $xml = $document->saveXml($root); |
| 66 | + if ($xml === false || trim($xml) === '') { |
| 67 | + throw new SchemaViolationException('Could not serialize XML for schema validation.'); |
| 68 | + } |
62 | 69 |
|
63 | | - libxml_use_internal_errors($internalErrors); |
64 | | - libxml_clear_errors(); |
| 70 | + $legacy = new \DOMDocument('1.0', 'UTF-8'); |
| 71 | + $legacy->preserveWhiteSpace = true; |
| 72 | + $legacy->formatOutput = false; |
| 73 | + $legacy->loadXML($xml); |
65 | 74 |
|
66 | | - return $document; |
| 75 | + $result = $legacy->schemaValidate($schemaFile); |
| 76 | + |
| 77 | + if ($result === false) { |
| 78 | + $msgs = []; |
| 79 | + foreach (libxml_get_errors() as $err) { |
| 80 | + $msgs[] = trim($err->message) . ' on line ' . $err->line; |
| 81 | + } |
| 82 | + |
| 83 | + throw new SchemaViolationException(sprintf( |
| 84 | + "XML schema validation errors:\n - %s", |
| 85 | + implode("\n - ", array_unique($msgs)), |
| 86 | + )); |
| 87 | + } |
| 88 | + |
| 89 | + return $document; |
| 90 | + } finally { |
| 91 | + libxml_clear_errors(); |
| 92 | + libxml_use_internal_errors($internalErrors); |
| 93 | + } |
67 | 94 | } |
68 | 95 |
|
69 | 96 |
|
|
0 commit comments