Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions ext/dom/document.c
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,35 @@ static int dom_perform_xinclude(xmlDocPtr docp, dom_object *intern, zend_long fl
return err;
}

/* For modern DOM, namespace declarations are stored as attributes (node->nsDef
* is NULL), so libxml's native validators can't resolve prefixed QNames found in
* content (e.g. an xs:QName attribute value). Temporarily relink them, mirroring
* what C14N does in dom_canonicalization(). */
typedef struct {
HashTable links;
bool active;
} dom_validate_ns_guard;

static void dom_validate_ns_guard_begin(dom_validate_ns_guard *guard, xmlDocPtr docp)
{
guard->active = php_dom_follow_spec_node((const xmlNode *) docp);
if (guard->active) {
zend_hash_init(&guard->links, 0, NULL, NULL, false);
xmlNodePtr root_element = xmlDocGetRootElement(docp);
if (root_element) {
dom_relink_ns_decls(&guard->links, root_element);
}
}
}

static void dom_validate_ns_guard_end(dom_validate_ns_guard *guard)
{
if (guard->active) {
dom_unlink_ns_decls(&guard->links);
zend_hash_destroy(&guard->links);
}
}

/* {{{ Substitutues xincludes in a DomDocument */
PHP_METHOD(DOMDocument, xinclude)
{
Expand Down Expand Up @@ -1832,8 +1861,11 @@ PHP_METHOD(DOMDocument, validate)
cvp->userData = NULL;
cvp->error = (xmlValidityErrorFunc) php_libxml_error_handler;
cvp->warning = (xmlValidityErrorFunc) php_libxml_error_handler;

if (xmlValidateDocument(cvp, docp)) {
dom_validate_ns_guard guard;
dom_validate_ns_guard_begin(&guard, docp);
int dtd_valid = xmlValidateDocument(cvp, docp);
dom_validate_ns_guard_end(&guard);
if (dtd_valid) {
RETVAL_TRUE;
} else {
RETVAL_FALSE;
Expand Down Expand Up @@ -1930,7 +1962,10 @@ static void dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type)
PHP_LIBXML_SANITIZE_GLOBALS(validate);
xmlSchemaSetValidOptions(vptr, valid_opts);
xmlSchemaSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
dom_validate_ns_guard guard;
dom_validate_ns_guard_begin(&guard, docp);
is_valid = xmlSchemaValidateDoc(vptr, docp);
dom_validate_ns_guard_end(&guard);
xmlSchemaFree(sptr);
xmlSchemaFreeValidCtxt(vptr);
PHP_LIBXML_RESTORE_GLOBALS(validate);
Expand Down Expand Up @@ -2028,7 +2063,10 @@ static void dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS, int type
}

xmlRelaxNGSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
dom_validate_ns_guard guard;
dom_validate_ns_guard_begin(&guard, docp);
is_valid = xmlRelaxNGValidateDoc(vptr, docp);
dom_validate_ns_guard_end(&guard);
xmlRelaxNGFree(sptr);
xmlRelaxNGFreeValidCtxt(vptr);

Expand Down
7 changes: 7 additions & 0 deletions ext/dom/namespace_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,11 @@ PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns(php_dom_libxml_ns_map
PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns_legacy(const xmlNode *node);
PHP_DOM_EXPORT void php_dom_in_scope_ns_destroy(php_dom_in_scope_ns *in_scope_ns);

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

#endif
4 changes: 2 additions & 2 deletions ext/dom/node.c
Original file line number Diff line number Diff line change
Expand Up @@ -2201,7 +2201,7 @@ static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
}
}

static void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
{
dom_relink_ns_decls_element(links, root);

Expand All @@ -2213,7 +2213,7 @@ static void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
}
}

static void dom_unlink_ns_decls(HashTable *links)
void dom_unlink_ns_decls(HashTable *links)
{
ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL(links, zend_ulong h, zval *data) {
if (h & 1) {
Expand Down
54 changes: 54 additions & 0 deletions ext/dom/tests/gh22219.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
--TEST--
GH-22219 (Dom\XMLDocument::schemaValidate fails to resolve xs:QName value from an in-scope prefix)
--EXTENSIONS--
dom
--SKIPIF--
<?php
if (!method_exists('Dom\XMLDocument', 'schemaValidateSource')) die('skip schema validation not available');
?>
--FILE--
<?php

$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="urn:test" xmlns:ref="urn:other">
<item target="ref:something"/>
</root>
XML;

// The 'ref' prefix is declared on <root> but only used inside the xs:QName
// attribute value, never as an element or attribute namespace.
$xsd = <<<XSD
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:test" elementFormDefault="qualified">
<element name="root">
<complexType>
<sequence>
<element name="item">
<complexType>
<attribute name="target" type="QName"/>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
XSD;

libxml_use_internal_errors(true);

$modern = Dom\XMLDocument::createFromString($xml, LIBXML_NSCLEAN);
var_dump($modern->schemaValidateSource($xsd));

$legacy = new DOMDocument();
$legacy->loadXML($xml, LIBXML_NSCLEAN);
var_dump($legacy->schemaValidateSource($xsd));

foreach (libxml_get_errors() as $error) {
echo trim($error->message), PHP_EOL;
}
?>
--EXPECT--
bool(true)
bool(true)
Loading