Fix cloning attribute with namespace disappearing namespace

Closes GH-12547.
This commit is contained in:
Niels Dossche 2023-10-28 19:21:02 +02:00
parent a470c4aeef
commit 3e33eda39a
10 changed files with 184 additions and 0 deletions

1
NEWS
View File

@ -9,6 +9,7 @@ DOM:
. Added DOMNode::compareDocumentPosition(). (nielsdos)
. Implement #53655 (Improve speed of DOMNode::C14N() on large XML documents).
(nielsdos)
. Fix cloning attribute with namespace disappearing namespace. (nielsdos)
Intl:
. Added IntlDateFormatter::PATTERN constant. (David Carlier)

View File

@ -640,6 +640,7 @@ PHP_METHOD(DOMElement, setAttributeNode)
}
xmlAddChild(nodep, (xmlNodePtr) attrp);
php_dom_reconcile_attribute_namespace_after_insertion(attrp);
/* Returns old property if removed otherwise NULL */
if (existattrp != NULL) {
@ -1012,6 +1013,7 @@ PHP_METHOD(DOMElement, setAttributeNodeNS)
}
xmlAddChild(nodep, (xmlNodePtr) attrp);
php_dom_reconcile_attribute_namespace_after_insertion(attrp);
/* Returns old property if removed otherwise NULL */
if (existattrp != NULL) {

View File

@ -1259,6 +1259,7 @@ PHP_METHOD(DOMNode, appendChild)
if (UNEXPECTED(new_child == NULL)) {
goto cannot_add;
}
php_dom_reconcile_attribute_namespace_after_insertion((xmlAttrPtr) new_child);
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
xmlNodePtr last = child->last;
new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern, childobj);
@ -1362,6 +1363,13 @@ PHP_METHOD(DOMNode, cloneNode)
}
}
if (node->type == XML_ATTRIBUTE_NODE && n->ns != NULL && node->ns == NULL) {
/* Let reconciliation deal with this. The lifetime of the namespace poses no problem
* because we're increasing the refcount of the document proxy at the return.
* libxml2 doesn't set the ns because it can't know that this is safe. */
node->ns = n->ns;
}
/* If document cloned we want a new document proxy */
if (node->doc != n->doc) {
intern = NULL;

View File

@ -1458,6 +1458,22 @@ static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePt
}
}
void php_dom_reconcile_attribute_namespace_after_insertion(xmlAttrPtr attrp)
{
ZEND_ASSERT(attrp != NULL);
if (attrp->ns != NULL) {
/* Try to link to an existing namespace. If that won't work, reconcile. */
xmlNodePtr nodep = attrp->parent;
xmlNsPtr matching_ns = xmlSearchNs(nodep->doc, nodep, attrp->ns->prefix);
if (matching_ns && xmlStrEqual(matching_ns->href, attrp->ns->href)) {
attrp->ns = matching_ns;
} else {
xmlReconciliateNs(nodep->doc, nodep);
}
}
}
static void dom_libxml_reconcile_ensure_namespaces_are_declared(xmlNodePtr nodep)
{
/* Ideally we'd use the DOM-wrapped version, but we cannot: https://github.com/php/php-src/pull/12308. */
@ -1474,6 +1490,8 @@ static void dom_libxml_reconcile_ensure_namespaces_are_declared(xmlNodePtr nodep
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
{
ZEND_ASSERT(nodep->type != XML_ATTRIBUTE_NODE);
/* Although the node type will be checked by the libxml2 API,
* we still want to do the internal reconciliation conditionally. */
if (nodep->type == XML_ELEMENT_NODE) {

View File

@ -152,6 +152,7 @@ zend_string *dom_node_get_node_name_attribute_or_element(const xmlNode *nodep);
bool php_dom_is_node_connected(const xmlNode *node);
bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, xmlDocPtr new_document);
xmlNsPtr dom_get_ns_resolve_prefix_conflict(xmlNodePtr tree, const char *uri);
void php_dom_reconcile_attribute_namespace_after_insertion(xmlAttrPtr attrp);
/* parentnode */
void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc);

View File

@ -26,7 +26,9 @@ $dom1->loadXML('<?xml version="1.0"?><container xmlns:foo="http://php.net" foo:b
$dom2->loadXML('<?xml version="1.0"?><container xmlns="http://php.net" xmlns:foo="http://php.net/2"/>');
$attribute = $dom1->documentElement->getAttributeNode('foo:bar');
$imported = $dom2->importNode($attribute);
var_dump($imported->prefix, $imported->namespaceURI);
$dom2->documentElement->setAttributeNodeNS($imported);
var_dump($imported->prefix, $imported->namespaceURI);
echo $dom1->saveXML();
echo $dom2->saveXML();
@ -54,6 +56,10 @@ echo $dom2->saveXML();
<?xml version="1.0"?>
<container xmlns:foo="http://php.net/2" xmlns:default="http://php.net" default:bar="yes"/>
--- Non-default namespace test case with a default namespace in the destination ---
string(7) "default"
string(14) "http://php.net"
string(7) "default"
string(14) "http://php.net"
<?xml version="1.0"?>
<container xmlns:foo="http://php.net" foo:bar="yes"/>
<?xml version="1.0"?>

View File

@ -0,0 +1,84 @@
--TEST--
Cloning an attribute should retain its namespace 01
--EXTENSIONS--
dom
--FILE--
<?php
function createTestDocument() {
$dom = new DOMDocument;
$dom->loadXML('<?xml version="1.0"?><container/>');
$dom->documentElement->setAttributeNs("some:ns", "foo:bar", "value");
$attr = $dom->documentElement->getAttributeNodeNs("some:ns", "bar");
$clone = $attr->cloneNode(true);
return [$dom, $clone];
}
[$dom, $clone] = createTestDocument();
var_dump($clone->prefix, $clone->namespaceURI);
echo "--- Re-adding a namespaced attribute ---\n";
[$dom, $clone] = createTestDocument();
$dom->documentElement->removeAttributeNs("some:ns", "bar");
echo $dom->saveXML();
$dom->documentElement->setAttributeNodeNs($clone);
echo $dom->saveXML();
echo "--- Re-adding a namespaced attribute, with the namespace deleted (setAttributeNodeNs variation) ---\n";
function readd_test(string $method) {
[$dom, $clone] = createTestDocument();
$dom->documentElement->removeAttributeNs("some:ns", "bar");
$dom->documentElement->removeAttribute("xmlns:foo");
echo $dom->saveXML();
$child = $dom->documentElement->appendChild($dom->createElement("child"));
$child->{$method}($clone);
echo $dom->saveXML();
}
readd_test("setAttributeNodeNs");
echo "--- Re-adding a namespaced attribute, with the namespace deleted (setAttributeNode variation) ---\n";
readd_test("setAttributeNode");
echo "--- Re-adding a namespaced attribute, with the namespace deleted (appendChild variation) ---\n";
readd_test("appendChild");
echo "--- Removing the document reference should not crash ---\n";
[$dom, $clone] = createTestDocument();
unset($dom);
var_dump($clone->prefix, $clone->namespaceURI);
?>
--EXPECT--
string(3) "foo"
string(7) "some:ns"
--- Re-adding a namespaced attribute ---
<?xml version="1.0"?>
<container xmlns:foo="some:ns"/>
<?xml version="1.0"?>
<container xmlns:foo="some:ns" foo:bar="value"/>
--- Re-adding a namespaced attribute, with the namespace deleted (setAttributeNodeNs variation) ---
<?xml version="1.0"?>
<container/>
<?xml version="1.0"?>
<container><child xmlns:foo="some:ns" foo:bar="value"/></container>
--- Re-adding a namespaced attribute, with the namespace deleted (setAttributeNode variation) ---
<?xml version="1.0"?>
<container/>
<?xml version="1.0"?>
<container><child xmlns:foo="some:ns" foo:bar="value"/></container>
--- Re-adding a namespaced attribute, with the namespace deleted (appendChild variation) ---
<?xml version="1.0"?>
<container/>
<?xml version="1.0"?>
<container><child xmlns:foo="some:ns" foo:bar="value"/></container>
--- Removing the document reference should not crash ---
string(3) "foo"
string(7) "some:ns"

View File

@ -0,0 +1,26 @@
--TEST--
Cloning an attribute should retain its namespace 02
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument;
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container xmlns:foo="some:ns" foo:bar="1">
<child xmlns:foo="some:other"/>
</container>
XML);
$clone = $dom->documentElement->getAttributeNodeNs("some:ns", "bar")->cloneNode(true);
$dom->documentElement->firstElementChild->setAttributeNodeNs($clone);
echo $dom->saveXML();
?>
--EXPECT--
<?xml version="1.0"?>
<container xmlns:foo="some:ns" foo:bar="1">
<child xmlns:foo="some:other" xmlns:foo1="some:ns" foo1:bar="1"/>
</container>

View File

@ -0,0 +1,37 @@
--TEST--
Cloning an attribute should retain its namespace 02
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument;
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container xmlns:foo="some:ns" foo:bar="1"/>
XML);
$dom2 = new DOMDocument;
$dom2->loadXML(<<<XML
<?xml version="1.0"?>
<container xmlns:foo="some:other"/>
XML);
$imported = $dom2->importNode($dom->documentElement->getAttributeNodeNs("some:ns", "bar"));
var_dump($imported->prefix, $imported->namespaceURI);
$dom2->documentElement->setAttributeNodeNs($imported);
var_dump($imported->prefix, $imported->namespaceURI);
echo $dom->saveXML();
echo $dom2->saveXML();
?>
--EXPECT--
string(7) "default"
string(7) "some:ns"
string(7) "default"
string(7) "some:ns"
<?xml version="1.0"?>
<container xmlns:foo="some:ns" foo:bar="1"/>
<?xml version="1.0"?>
<container xmlns:foo="some:other" xmlns:default="some:ns" default:bar="1"/>

View File

@ -2,6 +2,7 @@
GH-12532 (PharData created from zip has incorrect timestamp)
--EXTENSIONS--
phar
zlib
--FILE--
<?php