Skip to content

Commit b21a8cf

Browse files
committed
fix SchemaValidatableElementTrait::schemaValidate inconsistent behavior across modules. Fix bugs.
1 parent cf8d29d commit b21a8cf

2 files changed

Lines changed: 46 additions & 19 deletions

File tree

src/XML/DOMDocumentFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ static function (int $severity, string $message): never {
9797
restore_error_handler();
9898
}
9999

100-
foreach ($domDocument->childNodes as $child) {
100+
foreach ($loaded->childNodes as $child) {
101101
Assert::false(
102102
$child->nodeType === \XML_DOCUMENT_TYPE_NODE,
103103
'Dangerous XML detected, DOCTYPE nodes are not allowed in the XML body',
@@ -245,7 +245,7 @@ public static function lookupNamespaceURI(Dom\Element $elt, ?string $prefix): ?s
245245
}
246246

247247
/** @var \Dom\NamespaceInfo[] $namespaces */
248-
$namespaces = $elt->ownerDocument->documentElement->getInScopeNamespaces();
248+
$namespaces = $elt->getInScopeNamespaces();
249249

250250
foreach ($namespaces as $ns) {
251251
if ($ns->prefix === $prefix) {

src/XML/SchemaValidatableElementTrait.php

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace SimpleSAML\XML;
66

77
use Dom;
8+
use Dom\XMLDocument;
89
use SimpleSAML\XML\Assert\Assert;
910
use SimpleSAML\XML\Exception\IOException;
1011
use SimpleSAML\XML\Exception\RuntimeException;
@@ -41,29 +42,55 @@ public static function schemaValidate(Dom\Document $document, ?string $schemaFil
4142
$internalErrors = libxml_use_internal_errors(true);
4243
libxml_clear_errors();
4344

44-
if ($schemaFile === null) {
45-
$schemaFile = self::getSchemaFile();
46-
}
45+
try {
46+
if ($schemaFile === null) {
47+
$schemaFile = self::getSchemaFile();
48+
}
4749

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+
}
5053

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.');
5563
}
5664

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+
}
6269

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);
6574

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+
}
6794
}
6895

6996

0 commit comments

Comments
 (0)