Skip to content

Commit 8e57401

Browse files
committed
ext/dom: resolve in-scope prefixed QName values during document validation.
Fix #22219 Modern DOM keeps namespace declarations off the tree (node->nsDef is NULL), so libxml's native validators cannot resolve a prefixed QName appearing in element or attribute content. Temporarily materialize them as nsDef entries around schema, RelaxNG and DTD validation, reusing the C14N relink machinery, then restore the tree.
1 parent 3064540 commit 8e57401

4 files changed

Lines changed: 103 additions & 4 deletions

File tree

ext/dom/document.c

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,35 @@ static int dom_perform_xinclude(xmlDocPtr docp, dom_object *intern, zend_long fl
17571757
return err;
17581758
}
17591759

1760+
/* For modern DOM, namespace declarations are stored as attributes (node->nsDef
1761+
* is NULL), so libxml's native validators can't resolve prefixed QNames found in
1762+
* content (e.g. an xs:QName attribute value). Temporarily relink them, mirroring
1763+
* what C14N does in dom_canonicalization(). */
1764+
typedef struct {
1765+
HashTable links;
1766+
bool active;
1767+
} dom_validate_ns_guard;
1768+
1769+
static void dom_validate_ns_guard_begin(dom_validate_ns_guard *guard, xmlDocPtr docp)
1770+
{
1771+
guard->active = php_dom_follow_spec_node((const xmlNode *) docp);
1772+
if (guard->active) {
1773+
zend_hash_init(&guard->links, 0, NULL, NULL, false);
1774+
xmlNodePtr root_element = xmlDocGetRootElement(docp);
1775+
if (root_element) {
1776+
dom_relink_ns_decls(&guard->links, root_element);
1777+
}
1778+
}
1779+
}
1780+
1781+
static void dom_validate_ns_guard_end(dom_validate_ns_guard *guard)
1782+
{
1783+
if (guard->active) {
1784+
dom_unlink_ns_decls(&guard->links);
1785+
zend_hash_destroy(&guard->links);
1786+
}
1787+
}
1788+
17601789
/* {{{ Substitutues xincludes in a DomDocument */
17611790
PHP_METHOD(DOMDocument, xinclude)
17621791
{
@@ -1832,8 +1861,11 @@ PHP_METHOD(DOMDocument, validate)
18321861
cvp->userData = NULL;
18331862
cvp->error = (xmlValidityErrorFunc) php_libxml_error_handler;
18341863
cvp->warning = (xmlValidityErrorFunc) php_libxml_error_handler;
1835-
1836-
if (xmlValidateDocument(cvp, docp)) {
1864+
dom_validate_ns_guard guard;
1865+
dom_validate_ns_guard_begin(&guard, docp);
1866+
int dtd_valid = xmlValidateDocument(cvp, docp);
1867+
dom_validate_ns_guard_end(&guard);
1868+
if (dtd_valid) {
18371869
RETVAL_TRUE;
18381870
} else {
18391871
RETVAL_FALSE;
@@ -1930,7 +1962,10 @@ static void dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type)
19301962
PHP_LIBXML_SANITIZE_GLOBALS(validate);
19311963
xmlSchemaSetValidOptions(vptr, valid_opts);
19321964
xmlSchemaSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
1965+
dom_validate_ns_guard guard;
1966+
dom_validate_ns_guard_begin(&guard, docp);
19331967
is_valid = xmlSchemaValidateDoc(vptr, docp);
1968+
dom_validate_ns_guard_end(&guard);
19341969
xmlSchemaFree(sptr);
19351970
xmlSchemaFreeValidCtxt(vptr);
19361971
PHP_LIBXML_RESTORE_GLOBALS(validate);
@@ -2028,7 +2063,10 @@ static void dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS, int type
20282063
}
20292064

20302065
xmlRelaxNGSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
2066+
dom_validate_ns_guard guard;
2067+
dom_validate_ns_guard_begin(&guard, docp);
20312068
is_valid = xmlRelaxNGValidateDoc(vptr, docp);
2069+
dom_validate_ns_guard_end(&guard);
20322070
xmlRelaxNGFree(sptr);
20332071
xmlRelaxNGFreeValidCtxt(vptr);
20342072

ext/dom/namespace_compat.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,11 @@ PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns(php_dom_libxml_ns_map
6969
PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns_legacy(const xmlNode *node);
7070
PHP_DOM_EXPORT void php_dom_in_scope_ns_destroy(php_dom_in_scope_ns *in_scope_ns);
7171

72+
/* Temporarily materialize namespace declarations as nsDef entries on the tree so
73+
* that libxml's native validators/canonicalizers can resolve prefixed QNames that
74+
* appear in element/attribute *content*. Modern DOM keeps declarations off the
75+
* tree (node->nsDef == NULL), which xmlSearchNs() cannot follow. */
76+
PHP_DOM_EXPORT void dom_relink_ns_decls(HashTable *links, xmlNodePtr root);
77+
PHP_DOM_EXPORT void dom_unlink_ns_decls(HashTable *links);
78+
7279
#endif

ext/dom/node.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,7 +2201,7 @@ static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
22012201
}
22022202
}
22032203

2204-
static void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
2204+
void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
22052205
{
22062206
dom_relink_ns_decls_element(links, root);
22072207

@@ -2213,7 +2213,7 @@ static void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
22132213
}
22142214
}
22152215

2216-
static void dom_unlink_ns_decls(HashTable *links)
2216+
void dom_unlink_ns_decls(HashTable *links)
22172217
{
22182218
ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL(links, zend_ulong h, zval *data) {
22192219
if (h & 1) {

ext/dom/tests/gh22219.phpt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
GH-22219 (Dom\XMLDocument::schemaValidate fails to resolve xs:QName value from an in-scope prefix)
3+
--EXTENSIONS--
4+
dom
5+
--SKIPIF--
6+
<?php
7+
if (!method_exists('Dom\XMLDocument', 'schemaValidateSource')) die('skip schema validation not available');
8+
?>
9+
--FILE--
10+
<?php
11+
12+
$xml = <<<XML
13+
<?xml version="1.0" encoding="UTF-8"?>
14+
<root xmlns="urn:test" xmlns:ref="urn:other">
15+
<item target="ref:something"/>
16+
</root>
17+
XML;
18+
19+
// The 'ref' prefix is declared on <root> but only used inside the xs:QName
20+
// attribute value, never as an element or attribute namespace.
21+
$xsd = <<<XSD
22+
<?xml version="1.0" encoding="UTF-8"?>
23+
<schema xmlns="http://www.w3.org/2001/XMLSchema"
24+
targetNamespace="urn:test" elementFormDefault="qualified">
25+
<element name="root">
26+
<complexType>
27+
<sequence>
28+
<element name="item">
29+
<complexType>
30+
<attribute name="target" type="QName"/>
31+
</complexType>
32+
</element>
33+
</sequence>
34+
</complexType>
35+
</element>
36+
</schema>
37+
XSD;
38+
39+
libxml_use_internal_errors(true);
40+
41+
$modern = Dom\XMLDocument::createFromString($xml, LIBXML_NSCLEAN);
42+
var_dump($modern->schemaValidateSource($xsd));
43+
44+
$legacy = new DOMDocument();
45+
$legacy->loadXML($xml, LIBXML_NSCLEAN);
46+
var_dump($legacy->schemaValidateSource($xsd));
47+
48+
foreach (libxml_get_errors() as $error) {
49+
echo trim($error->message), PHP_EOL;
50+
}
51+
?>
52+
--EXPECT--
53+
bool(true)
54+
bool(true)

0 commit comments

Comments
 (0)