diff --git a/NEWS b/NEWS index c9dab4b4fdbf4..b96c5c28025d2 100644 --- a/NEWS +++ b/NEWS @@ -2,9 +2,16 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.0 +- DOM: + . Fixed bug GH-12616 (DOM: Removing XMLNS namespace node results in invalid + default: prefix). (nielsdos) + - Intl: . Fixed bug GH-12635 (Test bug69398.phpt fails with ICU 74.1). (nielsdos) +- LibXML: + . Fixed bug GH-12702 (libxml2 2.12.0 issue building from src). (nono303) + - PCRE: . Fixed bug GH-12628 (The gh11374 test fails on Alpinelinux). (nielsdos) diff --git a/ext/dom/element.c b/ext/dom/element.c index 6e372c41ad66d..133ae0d76f6d1 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -862,6 +862,83 @@ PHP_METHOD(DOMElement, setAttributeNS) } /* }}} end dom_element_set_attribute_ns */ +static void dom_remove_eliminated_ns_single_element(xmlNodePtr node, xmlNsPtr eliminatedNs) +{ + ZEND_ASSERT(node->type == XML_ELEMENT_NODE); + if (node->ns == eliminatedNs) { + node->ns = NULL; + } + + for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) { + if (attr->ns == eliminatedNs) { + attr->ns = NULL; + } + } +} + +static void dom_remove_eliminated_ns(xmlNodePtr node, xmlNsPtr eliminatedNs) +{ + dom_remove_eliminated_ns_single_element(node, eliminatedNs); + + xmlNodePtr base = node; + node = node->children; + while (node != NULL) { + ZEND_ASSERT(node != base); + + if (node->type == XML_ELEMENT_NODE) { + dom_remove_eliminated_ns_single_element(node, eliminatedNs); + + if (node->children) { + node = node->children; + continue; + } + } + + if (node->next) { + node = node->next; + } else { + /* Go upwards, until we find a parent node with a next sibling, or until we hit the base. */ + do { + node = node->parent; + if (node == base) { + return; + } + } while (node->next == NULL); + node = node->next; + } + } +} + +static void dom_eliminate_ns(xmlNodePtr nodep, xmlNsPtr nsptr) +{ + if (nsptr->href != NULL) { + xmlFree((char *) nsptr->href); + nsptr->href = NULL; + } + if (nsptr->prefix != NULL) { + xmlFree((char *) nsptr->prefix); + nsptr->prefix = NULL; + } + + /* Remove it from the list and move it to the old ns list */ + xmlNsPtr current_ns = nodep->nsDef; + if (current_ns == nsptr) { + nodep->nsDef = nsptr->next; + } else { + do { + if (current_ns->next == nsptr) { + current_ns->next = nsptr->next; + break; + } + current_ns = current_ns->next; + } while (current_ns != NULL); + } + nsptr->next = NULL; + php_libxml_set_old_ns(nodep->doc, nsptr); + + dom_remove_eliminated_ns(nodep, nsptr); +} + /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElRemAtNS Since: DOM Level 2 */ @@ -887,14 +964,7 @@ PHP_METHOD(DOMElement, removeAttributeNS) nsptr = dom_get_nsdecl(nodep, (xmlChar *)name); if (nsptr != NULL) { if (xmlStrEqual((xmlChar *)uri, nsptr->href)) { - if (nsptr->href != NULL) { - xmlFree((char *) nsptr->href); - nsptr->href = NULL; - } - if (nsptr->prefix != NULL) { - xmlFree((char *) nsptr->prefix); - nsptr->prefix = NULL; - } + dom_eliminate_ns(nodep, nsptr); } else { RETURN_NULL(); } diff --git a/ext/dom/tests/gh12616_1.phpt b/ext/dom/tests/gh12616_1.phpt new file mode 100644 index 0000000000000..408d871aee6f6 --- /dev/null +++ b/ext/dom/tests/gh12616_1.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-12616 (DOM: Removing XMLNS namespace node results in invalid default: prefix) +--EXTENSIONS-- +dom +--FILE-- +loadXML( + << + CHILDREN + + XML +); + +$doc->documentElement->removeAttributeNS('http://symfony.com/schema/dic/services', ''); +echo $doc->saveXML(); + +$new = new DOMDocument(); +$new->append( + $new->importNode($doc->documentElement, true) +); + +echo $new->saveXML(); + +?> +--EXPECT-- + + + CHILDREN + + + + CHILDREN + diff --git a/ext/dom/tests/gh12616_2.phpt b/ext/dom/tests/gh12616_2.phpt new file mode 100644 index 0000000000000..57138e4c45b3f --- /dev/null +++ b/ext/dom/tests/gh12616_2.phpt @@ -0,0 +1,39 @@ +--TEST-- +GH-12616 (DOM: Removing XMLNS namespace node results in invalid default: prefix) +--EXTENSIONS-- +dom +--FILE-- +loadXML( + << + + + + + XML +); + +$doc->documentElement->removeAttributeNS('http://symfony.com/schema/dic/services', 'symfony'); +$xpath = new DOMXPath($doc); +$xpath->registerNamespace('test', 'urn:test'); + +echo $doc->saveXML(); + +$result = $xpath->query('//container/services/test:service[@id="hello"]'); +var_dump($result); + +?> +--EXPECT-- + + + + + + +object(DOMNodeList)#4 (1) { + ["length"]=> + int(1) +} diff --git a/ext/dom/tests/gh12616_3.phpt b/ext/dom/tests/gh12616_3.phpt new file mode 100644 index 0000000000000..1589920757204 --- /dev/null +++ b/ext/dom/tests/gh12616_3.phpt @@ -0,0 +1,172 @@ +--TEST-- +GH-12616 (DOM: Removing XMLNS namespace node results in invalid default: prefix) +--EXTENSIONS-- +dom +--FILE-- +loadXML( + << + + + + + + + + + + XML +); + +$doc->documentElement->firstElementChild->removeAttributeNS('http://symfony.com/schema/dic/services', 'x'); +echo $doc->saveXML(); + +$xpath = new DOMXPath($doc); + +echo "--- Namespaces of child1 ---\n"; + +foreach ($xpath->query("/container/child1/namespace::*") as $ns) { + var_dump($ns); +} + +echo "--- Namespaces of child1/foo (both nodes) ---\n"; + +foreach ($xpath->query("/container/child1/foo/namespace::*") as $ns) { + var_dump($ns); +} + +echo "--- Namespaces of child2 ---\n"; + +foreach ($xpath->query("/container/child2/namespace::*") as $ns) { + var_dump($ns); +} + +?> +--EXPECT-- + + + + + + + + + + + +--- Namespaces of child1 --- +object(DOMNameSpaceNode)#4 (10) { + ["nodeName"]=> + string(9) "xmlns:xml" + ["nodeValue"]=> + string(36) "http://www.w3.org/XML/1998/namespace" + ["nodeType"]=> + int(18) + ["prefix"]=> + string(3) "xml" + ["localName"]=> + string(3) "xml" + ["namespaceURI"]=> + string(36) "http://www.w3.org/XML/1998/namespace" + ["isConnected"]=> + bool(true) + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["parentNode"]=> + string(22) "(object value omitted)" + ["parentElement"]=> + string(22) "(object value omitted)" +} +--- Namespaces of child1/foo (both nodes) --- +object(DOMNameSpaceNode)#5 (10) { + ["nodeName"]=> + string(9) "xmlns:xml" + ["nodeValue"]=> + string(36) "http://www.w3.org/XML/1998/namespace" + ["nodeType"]=> + int(18) + ["prefix"]=> + string(3) "xml" + ["localName"]=> + string(3) "xml" + ["namespaceURI"]=> + string(36) "http://www.w3.org/XML/1998/namespace" + ["isConnected"]=> + bool(true) + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["parentNode"]=> + string(22) "(object value omitted)" + ["parentElement"]=> + string(22) "(object value omitted)" +} +object(DOMNameSpaceNode)#8 (10) { + ["nodeName"]=> + string(9) "xmlns:xml" + ["nodeValue"]=> + string(36) "http://www.w3.org/XML/1998/namespace" + ["nodeType"]=> + int(18) + ["prefix"]=> + string(3) "xml" + ["localName"]=> + string(3) "xml" + ["namespaceURI"]=> + string(36) "http://www.w3.org/XML/1998/namespace" + ["isConnected"]=> + bool(true) + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["parentNode"]=> + string(22) "(object value omitted)" + ["parentElement"]=> + string(22) "(object value omitted)" +} +--- Namespaces of child2 --- +object(DOMNameSpaceNode)#9 (10) { + ["nodeName"]=> + string(9) "xmlns:xml" + ["nodeValue"]=> + string(36) "http://www.w3.org/XML/1998/namespace" + ["nodeType"]=> + int(18) + ["prefix"]=> + string(3) "xml" + ["localName"]=> + string(3) "xml" + ["namespaceURI"]=> + string(36) "http://www.w3.org/XML/1998/namespace" + ["isConnected"]=> + bool(true) + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["parentNode"]=> + string(22) "(object value omitted)" + ["parentElement"]=> + string(22) "(object value omitted)" +} +object(DOMNameSpaceNode)#5 (10) { + ["nodeName"]=> + string(7) "xmlns:x" + ["nodeValue"]=> + string(38) "http://symfony.com/schema/dic/services" + ["nodeType"]=> + int(18) + ["prefix"]=> + string(1) "x" + ["localName"]=> + string(1) "x" + ["namespaceURI"]=> + string(38) "http://symfony.com/schema/dic/services" + ["isConnected"]=> + bool(true) + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["parentNode"]=> + string(22) "(object value omitted)" + ["parentElement"]=> + string(22) "(object value omitted)" +} diff --git a/ext/libxml/php_libxml.h b/ext/libxml/php_libxml.h index 7ce7def92ae5f..16507b0f9f0b2 100644 --- a/ext/libxml/php_libxml.h +++ b/ext/libxml/php_libxml.h @@ -35,6 +35,7 @@ extern zend_module_entry libxml_module_entry; #include "zend_smart_str.h" #include +#include #define LIBXML_SAVE_NOEMPTYTAG 1<<2