Skip to content

Commit 095f88f

Browse files
committed
Fix GH-21544: Dom\XMLDocument::C14N() drops namespace declarations on DOM-built documents.
For programmatically built documents (e.g. createElementNS), namespaces live on node->ns without corresponding xmlns attributes. Create temporary synthetic nsDef entries so C14N can find them, complementing the existing xmlns attribute to nsDef conversion. close GH-21561
1 parent aeb5671 commit 095f88f

File tree

3 files changed

+99
-20
lines changed

3 files changed

+99
-20
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ PHP NEWS
3030
. Fixed bug GH-21688 (segmentation fault on empty HTMLDocument).
3131
(David Carlier)
3232
. Upgrade to lexbor v2.7.0. (ndossche, ilutov)
33+
. Fixed bug GH-21544 (Dom\XMLDocument::C14N*( drops namespace declarations
34+
on DOM-built documents). (David Carlier, ndossche)
3335

3436
- Iconv:
3537
. Fixed bug GH-17399 (iconv memory leak on bailout). (iliaal)

ext/dom/node.c

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,33 +2103,72 @@ PHP_METHOD(DOMNode, lookupNamespaceURI)
21032103
}
21042104
/* }}} end dom_node_lookup_namespace_uri */
21052105

2106+
/* Allocate, track and prepend a temporary nsDef entry for C14N.
2107+
* Returns the new xmlNsPtr for the caller to fill in href/prefix/_private,
2108+
* or NULL on allocation failure. */
2109+
static xmlNsPtr dom_alloc_ns_decl(HashTable *links, xmlNodePtr node)
2110+
{
2111+
xmlNsPtr ns = xmlMalloc(sizeof(*ns));
2112+
if (!ns) {
2113+
return NULL;
2114+
}
2115+
2116+
zval *zv = zend_hash_index_lookup(links, (zend_ulong) node);
2117+
if (Z_ISNULL_P(zv)) {
2118+
ZVAL_LONG(zv, 1);
2119+
} else {
2120+
Z_LVAL_P(zv)++;
2121+
}
2122+
2123+
memset(ns, 0, sizeof(*ns));
2124+
ns->type = XML_LOCAL_NAMESPACE;
2125+
ns->next = node->nsDef;
2126+
node->nsDef = ns;
2127+
2128+
return ns;
2129+
}
2130+
2131+
/* Mint a temporary nsDef entry so C14N finds namespaces that live on node->ns
2132+
* but have no matching xmlns attribute (typical for createElementNS). */
2133+
static void dom_add_synthetic_ns_decl(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
2134+
{
2135+
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
2136+
if (!ns) {
2137+
return;
2138+
}
2139+
2140+
ns->href = xmlStrdup(src_ns->href);
2141+
ns->prefix = src_ns->prefix ? xmlStrdup(src_ns->prefix) : NULL;
2142+
}
2143+
2144+
/* Same, but for attribute namespaces, which may collide by prefix with the
2145+
* element's own ns or with a sibling attribute's ns. */
2146+
static void dom_add_synthetic_ns_decl_for_attr(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
2147+
{
2148+
for (xmlNsPtr existing = node->nsDef; existing; existing = existing->next) {
2149+
if (xmlStrEqual(existing->prefix, src_ns->prefix)) {
2150+
return;
2151+
}
2152+
}
2153+
2154+
dom_add_synthetic_ns_decl(links, node, src_ns);
2155+
}
2156+
21062157
static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
21072158
{
21082159
if (node->type == XML_ELEMENT_NODE) {
21092160
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
21102161
if (php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
2111-
xmlNsPtr ns = xmlMalloc(sizeof(*ns));
2162+
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
21122163
if (!ns) {
21132164
return;
21142165
}
21152166

2116-
zval *zv = zend_hash_index_lookup(links, (zend_ulong) node);
2117-
if (Z_ISNULL_P(zv)) {
2118-
ZVAL_LONG(zv, 1);
2119-
} else {
2120-
Z_LVAL_P(zv)++;
2121-
}
2122-
21232167
bool should_free;
21242168
xmlChar *attr_value = php_libxml_attr_value(attr, &should_free);
21252169

2126-
memset(ns, 0, sizeof(*ns));
2127-
ns->type = XML_LOCAL_NAMESPACE;
21282170
ns->href = should_free ? attr_value : xmlStrdup(attr_value);
21292171
ns->prefix = attr->ns->prefix ? xmlStrdup(attr->name) : NULL;
2130-
ns->next = node->nsDef;
2131-
node->nsDef = ns;
2132-
21332172
ns->_private = attr;
21342173
if (attr->prev) {
21352174
attr->prev->next = attr->next;
@@ -2150,6 +2189,14 @@ static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
21502189
* can return the current namespace. */
21512190
zend_hash_index_add_new_ptr(links, (zend_ulong) node | 1, node->ns);
21522191
node->ns = xmlSearchNs(node->doc, node, NULL);
2192+
} else if (node->ns) {
2193+
dom_add_synthetic_ns_decl(links, node, node->ns);
2194+
}
2195+
2196+
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
2197+
if (attr->ns && !php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
2198+
dom_add_synthetic_ns_decl_for_attr(links, node, attr->ns);
2199+
}
21532200
}
21542201
}
21552202
}
@@ -2179,13 +2226,15 @@ static void dom_unlink_ns_decls(HashTable *links)
21792226
node->nsDef = ns->next;
21802227

21812228
xmlAttrPtr attr = ns->_private;
2182-
if (attr->prev) {
2183-
attr->prev->next = attr;
2184-
} else {
2185-
node->properties = attr;
2186-
}
2187-
if (attr->next) {
2188-
attr->next->prev = attr;
2229+
if (attr) {
2230+
if (attr->prev) {
2231+
attr->prev->next = attr;
2232+
} else {
2233+
node->properties = attr;
2234+
}
2235+
if (attr->next) {
2236+
attr->next->prev = attr;
2237+
}
21892238
}
21902239

21912240
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)