Skip to content

Commit 8c63ec4

Browse files
committed
Merge branch 'PHP-8.5'
* PHP-8.5: ext/dom: resolve in-scope prefixed QName values during document validation.
2 parents b50b30c + bb20b5f commit 8c63ec4

5 files changed

Lines changed: 238 additions & 142 deletions

File tree

ext/dom/document.c

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

1752+
/* For modern DOM, namespace declarations are stored as attributes (node->nsDef
1753+
* is NULL), so libxml's native validators can't resolve prefixed QNames found in
1754+
* content (e.g. an xs:QName attribute value). Temporarily relink them, mirroring
1755+
* what C14N does in dom_canonicalization(). */
1756+
typedef struct {
1757+
HashTable links;
1758+
bool active;
1759+
} dom_validate_ns_guard;
1760+
1761+
static void dom_validate_ns_guard_begin(dom_validate_ns_guard *guard, xmlDocPtr docp)
1762+
{
1763+
guard->active = php_dom_follow_spec_node((const xmlNode *) docp);
1764+
if (guard->active) {
1765+
zend_hash_init(&guard->links, 0, NULL, NULL, false);
1766+
xmlNodePtr root_element = xmlDocGetRootElement(docp);
1767+
if (root_element) {
1768+
dom_relink_ns_decls(&guard->links, root_element);
1769+
}
1770+
}
1771+
}
1772+
1773+
static void dom_validate_ns_guard_end(dom_validate_ns_guard *guard)
1774+
{
1775+
if (guard->active) {
1776+
dom_unlink_ns_decls(&guard->links);
1777+
zend_hash_destroy(&guard->links);
1778+
}
1779+
}
1780+
17521781
/* {{{ Substitutues xincludes in a DomDocument */
17531782
PHP_METHOD(DOMDocument, xinclude)
17541783
{
@@ -1822,8 +1851,11 @@ PHP_METHOD(DOMDocument, validate)
18221851
cvp->userData = NULL;
18231852
cvp->error = (xmlValidityErrorFunc) php_libxml_error_handler;
18241853
cvp->warning = (xmlValidityErrorFunc) php_libxml_error_handler;
1825-
1826-
if (xmlValidateDocument(cvp, docp)) {
1854+
dom_validate_ns_guard guard;
1855+
dom_validate_ns_guard_begin(&guard, docp);
1856+
int dtd_valid = xmlValidateDocument(cvp, docp);
1857+
dom_validate_ns_guard_end(&guard);
1858+
if (dtd_valid) {
18271859
RETVAL_TRUE;
18281860
} else {
18291861
RETVAL_FALSE;
@@ -1920,7 +1952,10 @@ static void dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type)
19201952
PHP_LIBXML_SANITIZE_GLOBALS(validate);
19211953
xmlSchemaSetValidOptions(vptr, valid_opts);
19221954
xmlSchemaSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
1955+
dom_validate_ns_guard guard;
1956+
dom_validate_ns_guard_begin(&guard, docp);
19231957
is_valid = xmlSchemaValidateDoc(vptr, docp);
1958+
dom_validate_ns_guard_end(&guard);
19241959
xmlSchemaFree(sptr);
19251960
xmlSchemaFreeValidCtxt(vptr);
19261961
PHP_LIBXML_RESTORE_GLOBALS(validate);
@@ -2018,7 +2053,10 @@ static void dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS, int type
20182053
}
20192054

20202055
xmlRelaxNGSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
2056+
dom_validate_ns_guard guard;
2057+
dom_validate_ns_guard_begin(&guard, docp);
20212058
is_valid = xmlRelaxNGValidateDoc(vptr, docp);
2059+
dom_validate_ns_guard_end(&guard);
20222060
xmlRelaxNGFree(sptr);
20232061
xmlRelaxNGFreeValidCtxt(vptr);
20242062

ext/dom/namespace_compat.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,4 +492,142 @@ PHP_DOM_EXPORT void php_dom_in_scope_ns_destroy(php_dom_in_scope_ns *in_scope_ns
492492
}
493493
}
494494

495+
static xmlNsPtr dom_alloc_ns_decl(HashTable *links, xmlNodePtr node)
496+
{
497+
xmlNsPtr ns = xmlMalloc(sizeof(*ns));
498+
if (!ns) {
499+
return NULL;
500+
}
501+
502+
zval *zv = zend_hash_index_lookup(links, (zend_ulong) node);
503+
if (Z_ISNULL_P(zv)) {
504+
ZVAL_LONG(zv, 1);
505+
} else {
506+
Z_LVAL_P(zv)++;
507+
}
508+
509+
memset(ns, 0, sizeof(*ns));
510+
ns->type = XML_LOCAL_NAMESPACE;
511+
ns->next = node->nsDef;
512+
node->nsDef = ns;
513+
514+
return ns;
515+
}
516+
517+
/* Mint a temporary nsDef entry so C14N finds namespaces that live on node->ns
518+
* but have no matching xmlns attribute (typical for createElementNS). */
519+
static void dom_add_synthetic_ns_decl(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
520+
{
521+
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
522+
if (!ns) {
523+
return;
524+
}
525+
526+
ns->href = xmlStrdup(src_ns->href);
527+
ns->prefix = src_ns->prefix ? xmlStrdup(src_ns->prefix) : NULL;
528+
}
529+
530+
/* Same, but for attribute namespaces, which may collide by prefix with the
531+
* element's own ns or with a sibling attribute's ns. */
532+
static void dom_add_synthetic_ns_decl_for_attr(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
533+
{
534+
for (xmlNsPtr existing = node->nsDef; existing; existing = existing->next) {
535+
if (xmlStrEqual(existing->prefix, src_ns->prefix)) {
536+
return;
537+
}
538+
}
539+
540+
dom_add_synthetic_ns_decl(links, node, src_ns);
541+
}
542+
543+
static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
544+
{
545+
if (node->type == XML_ELEMENT_NODE) {
546+
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
547+
if (php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
548+
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
549+
if (!ns) {
550+
return;
551+
}
552+
553+
bool should_free;
554+
xmlChar *attr_value = php_libxml_attr_value(attr, &should_free);
555+
556+
ns->href = should_free ? attr_value : xmlStrdup(attr_value);
557+
ns->prefix = attr->ns->prefix ? xmlStrdup(attr->name) : NULL;
558+
ns->_private = attr;
559+
if (attr->prev) {
560+
attr->prev->next = attr->next;
561+
} else {
562+
node->properties = attr->next;
563+
}
564+
if (attr->next) {
565+
attr->next->prev = attr->prev;
566+
}
567+
}
568+
}
569+
570+
/* The default namespace is handled separately from the other namespaces in C14N.
571+
* The default namespace is explicitly looked up while the other namespaces are
572+
* deduplicated and compared to a list of visible namespaces. */
573+
if (node->ns && !node->ns->prefix) {
574+
/* Workaround for the behaviour where the xmlSearchNs() call inside c14n.c
575+
* can return the current namespace. */
576+
zend_hash_index_add_new_ptr(links, (zend_ulong) node | 1, node->ns);
577+
node->ns = xmlSearchNs(node->doc, node, NULL);
578+
} else if (node->ns) {
579+
dom_add_synthetic_ns_decl(links, node, node->ns);
580+
}
581+
582+
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
583+
if (attr->ns && !php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
584+
dom_add_synthetic_ns_decl_for_attr(links, node, attr->ns);
585+
}
586+
}
587+
}
588+
}
589+
590+
void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
591+
{
592+
dom_relink_ns_decls_element(links, root);
593+
594+
xmlNodePtr base = root;
595+
xmlNodePtr node = base->children;
596+
while (node != NULL) {
597+
dom_relink_ns_decls_element(links, node);
598+
node = php_dom_next_in_tree_order(node, base);
599+
}
600+
}
601+
602+
void dom_unlink_ns_decls(HashTable *links)
603+
{
604+
ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL(links, zend_ulong h, zval *data) {
605+
if (h & 1) {
606+
xmlNodePtr node = (xmlNodePtr) (h ^ 1);
607+
node->ns = Z_PTR_P(data);
608+
} else {
609+
xmlNodePtr node = (xmlNodePtr) h;
610+
while (Z_LVAL_P(data)-- > 0) {
611+
xmlNsPtr ns = node->nsDef;
612+
node->nsDef = ns->next;
613+
614+
xmlAttrPtr attr = ns->_private;
615+
if (attr) {
616+
if (attr->prev) {
617+
attr->prev->next = attr;
618+
} else {
619+
node->properties = attr;
620+
}
621+
if (attr->next) {
622+
attr->next->prev = attr;
623+
}
624+
}
625+
626+
xmlFreeNs(ns);
627+
}
628+
}
629+
} ZEND_HASH_FOREACH_END();
630+
}
631+
632+
495633
#endif /* HAVE_LIBXML && HAVE_DOM */

ext/dom/node.c

Lines changed: 0 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,146 +2101,6 @@ PHP_METHOD(DOMNode, lookupNamespaceURI)
21012101
}
21022102
/* }}} end dom_node_lookup_namespace_uri */
21032103

2104-
/* Allocate, track and prepend a temporary nsDef entry for C14N.
2105-
* Returns the new xmlNsPtr for the caller to fill in href/prefix/_private,
2106-
* or NULL on allocation failure. */
2107-
static xmlNsPtr dom_alloc_ns_decl(HashTable *links, xmlNodePtr node)
2108-
{
2109-
xmlNsPtr ns = xmlMalloc(sizeof(*ns));
2110-
if (!ns) {
2111-
return NULL;
2112-
}
2113-
2114-
zval *zv = zend_hash_index_lookup(links, (zend_ulong) node);
2115-
if (Z_ISNULL_P(zv)) {
2116-
ZVAL_LONG(zv, 1);
2117-
} else {
2118-
Z_LVAL_P(zv)++;
2119-
}
2120-
2121-
memset(ns, 0, sizeof(*ns));
2122-
ns->type = XML_LOCAL_NAMESPACE;
2123-
ns->next = node->nsDef;
2124-
node->nsDef = ns;
2125-
2126-
return ns;
2127-
}
2128-
2129-
/* Mint a temporary nsDef entry so C14N finds namespaces that live on node->ns
2130-
* but have no matching xmlns attribute (typical for createElementNS). */
2131-
static void dom_add_synthetic_ns_decl(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
2132-
{
2133-
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
2134-
if (!ns) {
2135-
return;
2136-
}
2137-
2138-
ns->href = xmlStrdup(src_ns->href);
2139-
ns->prefix = src_ns->prefix ? xmlStrdup(src_ns->prefix) : NULL;
2140-
}
2141-
2142-
/* Same, but for attribute namespaces, which may collide by prefix with the
2143-
* element's own ns or with a sibling attribute's ns. */
2144-
static void dom_add_synthetic_ns_decl_for_attr(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
2145-
{
2146-
for (xmlNsPtr existing = node->nsDef; existing; existing = existing->next) {
2147-
if (xmlStrEqual(existing->prefix, src_ns->prefix)) {
2148-
return;
2149-
}
2150-
}
2151-
2152-
dom_add_synthetic_ns_decl(links, node, src_ns);
2153-
}
2154-
2155-
static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
2156-
{
2157-
if (node->type == XML_ELEMENT_NODE) {
2158-
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
2159-
if (php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
2160-
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
2161-
if (!ns) {
2162-
return;
2163-
}
2164-
2165-
bool should_free;
2166-
xmlChar *attr_value = php_libxml_attr_value(attr, &should_free);
2167-
2168-
ns->href = should_free ? attr_value : xmlStrdup(attr_value);
2169-
ns->prefix = attr->ns->prefix ? xmlStrdup(attr->name) : NULL;
2170-
ns->_private = attr;
2171-
if (attr->prev) {
2172-
attr->prev->next = attr->next;
2173-
} else {
2174-
node->properties = attr->next;
2175-
}
2176-
if (attr->next) {
2177-
attr->next->prev = attr->prev;
2178-
}
2179-
}
2180-
}
2181-
2182-
/* The default namespace is handled separately from the other namespaces in C14N.
2183-
* The default namespace is explicitly looked up while the other namespaces are
2184-
* deduplicated and compared to a list of visible namespaces. */
2185-
if (node->ns && !node->ns->prefix) {
2186-
/* Workaround for the behaviour where the xmlSearchNs() call inside c14n.c
2187-
* can return the current namespace. */
2188-
zend_hash_index_add_new_ptr(links, (zend_ulong) node | 1, node->ns);
2189-
node->ns = xmlSearchNs(node->doc, node, NULL);
2190-
} else if (node->ns) {
2191-
dom_add_synthetic_ns_decl(links, node, node->ns);
2192-
}
2193-
2194-
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
2195-
if (attr->ns && !php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
2196-
dom_add_synthetic_ns_decl_for_attr(links, node, attr->ns);
2197-
}
2198-
}
2199-
}
2200-
}
2201-
2202-
static void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
2203-
{
2204-
dom_relink_ns_decls_element(links, root);
2205-
2206-
xmlNodePtr base = root;
2207-
xmlNodePtr node = base->children;
2208-
while (node != NULL) {
2209-
dom_relink_ns_decls_element(links, node);
2210-
node = php_dom_next_in_tree_order(node, base);
2211-
}
2212-
}
2213-
2214-
static void dom_unlink_ns_decls(HashTable *links)
2215-
{
2216-
ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL(links, zend_ulong h, zval *data) {
2217-
if (h & 1) {
2218-
xmlNodePtr node = (xmlNodePtr) (h ^ 1);
2219-
node->ns = Z_PTR_P(data);
2220-
} else {
2221-
xmlNodePtr node = (xmlNodePtr) h;
2222-
while (Z_LVAL_P(data)-- > 0) {
2223-
xmlNsPtr ns = node->nsDef;
2224-
node->nsDef = ns->next;
2225-
2226-
xmlAttrPtr attr = ns->_private;
2227-
if (attr) {
2228-
if (attr->prev) {
2229-
attr->prev->next = attr;
2230-
} else {
2231-
node->properties = attr;
2232-
}
2233-
if (attr->next) {
2234-
attr->next->prev = attr;
2235-
}
2236-
}
2237-
2238-
xmlFreeNs(ns);
2239-
}
2240-
}
2241-
} ZEND_HASH_FOREACH_END();
2242-
}
2243-
22442104
static int dom_canonicalize_node_parent_lookup_cb(void *user_data, xmlNodePtr node, xmlNodePtr parent)
22452105
{
22462106
xmlNodePtr root = user_data;

ext/dom/php_dom.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ void dom_set_document_ref_pointers_attr(xmlAttrPtr attr, php_libxml_ref_obj *doc
161161

162162
/* Prop getters by offset */
163163
zval *dom_get_prop_checked_offset(dom_object *obj, uint32_t offset, const char *name);
164+
/* Temporarily materialize namespace declarations as nsDef entries on the tree so
165+
* that libxml's native validators/canonicalizers can resolve prefixed QNames that
166+
* appear in element/attribute *content*. Modern DOM keeps declarations off the
167+
* tree (node->nsDef == NULL), which xmlSearchNs() cannot follow. Internal only. */
168+
void dom_relink_ns_decls(HashTable *links, xmlNodePtr root);
169+
void dom_unlink_ns_decls(HashTable *links);
164170
zval *dom_element_class_list_zval(dom_object *obj);
165171
zval *dom_parent_node_children(dom_object *obj);
166172

0 commit comments

Comments
 (0)