Skip to content

Commit 6c4c5e3

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.
1 parent 78a0b57 commit 6c4c5e3

2 files changed

Lines changed: 78 additions & 7 deletions

File tree

ext/dom/node.c

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,6 +2103,38 @@ PHP_METHOD(DOMNode, lookupNamespaceURI)
21032103
}
21042104
/* }}} end dom_node_lookup_namespace_uri */
21052105

2106+
/* For DOM-built documents (e.g. createElementNS), namespaces live on node->ns
2107+
* without corresponding xmlns attributes. C14N expects nsDef entries, so create
2108+
* temporary ones. The _private field stays NULL to distinguish from attr-based
2109+
* entries during cleanup. */
2110+
static void dom_add_synthetic_ns_decl(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
2111+
{
2112+
for (xmlNsPtr existing = node->nsDef; existing; existing = existing->next) {
2113+
if (xmlStrEqual(existing->prefix, src_ns->prefix)) {
2114+
return;
2115+
}
2116+
}
2117+
2118+
xmlNsPtr ns = xmlMalloc(sizeof(*ns));
2119+
if (!ns) {
2120+
return;
2121+
}
2122+
2123+
zval *zv = zend_hash_index_lookup(links, (zend_ulong) node);
2124+
if (Z_ISNULL_P(zv)) {
2125+
ZVAL_LONG(zv, 1);
2126+
} else {
2127+
Z_LVAL_P(zv)++;
2128+
}
2129+
2130+
memset(ns, 0, sizeof(*ns));
2131+
ns->type = XML_LOCAL_NAMESPACE;
2132+
ns->href = xmlStrdup(src_ns->href);
2133+
ns->prefix = src_ns->prefix ? xmlStrdup(src_ns->prefix) : NULL;
2134+
ns->next = node->nsDef;
2135+
node->nsDef = ns;
2136+
}
2137+
21062138
static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
21072139
{
21082140
if (node->type == XML_ELEMENT_NODE) {
@@ -2142,6 +2174,15 @@ static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
21422174
}
21432175
}
21442176

2177+
if (node->ns) {
2178+
dom_add_synthetic_ns_decl(links, node, node->ns);
2179+
}
2180+
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
2181+
if (attr->ns && !php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
2182+
dom_add_synthetic_ns_decl(links, node, attr->ns);
2183+
}
2184+
}
2185+
21452186
/* The default namespace is handled separately from the other namespaces in C14N.
21462187
* The default namespace is explicitly looked up while the other namespaces are
21472188
* deduplicated and compared to a list of visible namespaces. */
@@ -2179,13 +2220,15 @@ static void dom_unlink_ns_decls(HashTable *links)
21792220
node->nsDef = ns->next;
21802221

21812222
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;
2223+
if (attr) {
2224+
if (attr->prev) {
2225+
attr->prev->next = attr;
2226+
} else {
2227+
node->properties = attr;
2228+
}
2229+
if (attr->next) {
2230+
attr->next->prev = attr;
2231+
}
21892232
}
21902233

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