Skip to content

Commit ee3addf

Browse files
committed
Merge branch 'PHP-8.5'
* PHP-8.5: Fix phpGH-21544: Dom\XMLDocument::C14N() drops namespace declarations on DOM-built documents.
2 parents 60b2d0d + 86a1b36 commit ee3addf

File tree

2 files changed

+97
-20
lines changed

2 files changed

+97
-20
lines changed

ext/dom/node.c

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,33 +2101,72 @@ 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+
21042155
static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
21052156
{
21062157
if (node->type == XML_ELEMENT_NODE) {
21072158
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
21082159
if (php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
2109-
xmlNsPtr ns = xmlMalloc(sizeof(*ns));
2160+
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
21102161
if (!ns) {
21112162
return;
21122163
}
21132164

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-
21212165
bool should_free;
21222166
xmlChar *attr_value = php_libxml_attr_value(attr, &should_free);
21232167

2124-
memset(ns, 0, sizeof(*ns));
2125-
ns->type = XML_LOCAL_NAMESPACE;
21262168
ns->href = should_free ? attr_value : xmlStrdup(attr_value);
21272169
ns->prefix = attr->ns->prefix ? xmlStrdup(attr->name) : NULL;
2128-
ns->next = node->nsDef;
2129-
node->nsDef = ns;
2130-
21312170
ns->_private = attr;
21322171
if (attr->prev) {
21332172
attr->prev->next = attr->next;
@@ -2148,6 +2187,14 @@ static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
21482187
* can return the current namespace. */
21492188
zend_hash_index_add_new_ptr(links, (zend_ulong) node | 1, node->ns);
21502189
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+
}
21512198
}
21522199
}
21532200
}
@@ -2177,13 +2224,15 @@ static void dom_unlink_ns_decls(HashTable *links)
21772224
node->nsDef = ns->next;
21782225

21792226
xmlAttrPtr attr = ns->_private;
2180-
if (attr->prev) {
2181-
attr->prev->next = attr;
2182-
} else {
2183-
node->properties = attr;
2184-
}
2185-
if (attr->next) {
2186-
attr->next->prev = attr;
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+
}
21872236
}
21882237

21892238
xmlFreeNs(ns);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
GH-21544 (Dom\XMLDocument::C14N() drops namespace declarations on DOM-built documents)
3+
--CREDITS--
4+
Toon Verwerft (veewee)
5+
--EXTENSIONS--
6+
dom
7+
--FILE--
8+
<?php
9+
10+
$doc = Dom\XMLDocument::createEmpty();
11+
$root = $doc->createElementNS("urn:envelope", "env:Root");
12+
$doc->appendChild($root);
13+
$child = $doc->createElementNS("urn:child", "x:Child");
14+
$root->appendChild($child);
15+
16+
$parsed = Dom\XMLDocument::createFromString(
17+
'<env:Root xmlns:env="urn:envelope"><x:Child xmlns:x="urn:child"/></env:Root>'
18+
);
19+
20+
echo "DOM-built C14N: " . $doc->C14N() . PHP_EOL;
21+
echo "Parsed C14N: " . $parsed->C14N() . PHP_EOL;
22+
var_dump($doc->C14N() === $parsed->C14N());
23+
24+
?>
25+
--EXPECT--
26+
DOM-built C14N: <env:Root xmlns:env="urn:envelope"><x:Child xmlns:x="urn:child"></x:Child></env:Root>
27+
Parsed C14N: <env:Root xmlns:env="urn:envelope"><x:Child xmlns:x="urn:child"></x:Child></env:Root>
28+
bool(true)

0 commit comments

Comments
 (0)