mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
Revert "Fix GH-11404: DOMDocument::savexml and friends ommit xmlns="" declaration for null namespace, creating incorrect xml representation of the DOM"
This reverts commit 7eb3e9cd17
.
Although the fix follows the spec, it causes issues because a lot of old
code assumes the incorrect behaviour PHP had since a long time.
We cannot do this yet, especially not in a stable release.
We revert this for the time being.
See GH-11428.
This commit is contained in:
parent
9f7d88802e
commit
c174ebfce0
7 changed files with 10 additions and 216 deletions
2
NEWS
2
NEWS
|
@ -38,8 +38,6 @@ PHP NEWS
|
||||||
. Fix "invalid state error" with cloned namespace declarations. (nielsdos)
|
. Fix "invalid state error" with cloned namespace declarations. (nielsdos)
|
||||||
. Fixed bug #55294 and #47530 and #47847 (various namespace reconciliation
|
. Fixed bug #55294 and #47530 and #47847 (various namespace reconciliation
|
||||||
issues). (nielsdos)
|
issues). (nielsdos)
|
||||||
. Fixed bug GH-11404 (DOMDocument::saveXML and friends omit xmlns=""
|
|
||||||
declaration for null namespace). (nielsdos)
|
|
||||||
. Fixed bug #80332 (Completely broken array access functionality with
|
. Fixed bug #80332 (Completely broken array access functionality with
|
||||||
DOMNamedNodeMap). (nielsdos)
|
DOMNamedNodeMap). (nielsdos)
|
||||||
|
|
||||||
|
|
|
@ -878,10 +878,6 @@ PHP_METHOD(DOMDocument, createElementNS)
|
||||||
|
|
||||||
if (errorcode == 0) {
|
if (errorcode == 0) {
|
||||||
if (xmlValidateName((xmlChar *) localname, 0) == 0) {
|
if (xmlValidateName((xmlChar *) localname, 0) == 0) {
|
||||||
/* https://dom.spec.whatwg.org/#validate-and-extract: demands us to set an empty string uri to NULL */
|
|
||||||
if (uri_len == 0) {
|
|
||||||
uri = NULL;
|
|
||||||
}
|
|
||||||
nodep = xmlNewDocNode(docp, NULL, (xmlChar *) localname, (xmlChar *) value);
|
nodep = xmlNewDocNode(docp, NULL, (xmlChar *) localname, (xmlChar *) value);
|
||||||
if (nodep != NULL && uri != NULL) {
|
if (nodep != NULL && uri != NULL) {
|
||||||
nsptr = xmlSearchNsByHref(nodep->doc, nodep, (xmlChar *) uri);
|
nsptr = xmlSearchNsByHref(nodep->doc, nodep, (xmlChar *) uri);
|
||||||
|
|
|
@ -56,10 +56,6 @@ PHP_METHOD(DOMElement, __construct)
|
||||||
if (uri_len > 0) {
|
if (uri_len > 0) {
|
||||||
errorcode = dom_check_qname(name, &localname, &prefix, uri_len, name_len);
|
errorcode = dom_check_qname(name, &localname, &prefix, uri_len, name_len);
|
||||||
if (errorcode == 0) {
|
if (errorcode == 0) {
|
||||||
/* https://dom.spec.whatwg.org/#validate-and-extract: demands us to set an empty string uri to NULL */
|
|
||||||
if (uri_len == 0) {
|
|
||||||
uri = NULL;
|
|
||||||
}
|
|
||||||
nodep = xmlNewNode (NULL, (xmlChar *)localname);
|
nodep = xmlNewNode (NULL, (xmlChar *)localname);
|
||||||
if (nodep != NULL && uri != NULL) {
|
if (nodep != NULL && uri != NULL) {
|
||||||
nsptr = dom_get_ns(nodep, uri, &errorcode, prefix);
|
nsptr = dom_get_ns(nodep, uri, &errorcode, prefix);
|
||||||
|
|
|
@ -531,6 +531,7 @@ Since: DOM Level 2
|
||||||
int dom_node_namespace_uri_read(dom_object *obj, zval *retval)
|
int dom_node_namespace_uri_read(dom_object *obj, zval *retval)
|
||||||
{
|
{
|
||||||
xmlNode *nodep = dom_object_get_node(obj);
|
xmlNode *nodep = dom_object_get_node(obj);
|
||||||
|
char *str = NULL;
|
||||||
|
|
||||||
if (nodep == NULL) {
|
if (nodep == NULL) {
|
||||||
php_dom_throw_error(INVALID_STATE_ERR, 1);
|
php_dom_throw_error(INVALID_STATE_ERR, 1);
|
||||||
|
@ -542,19 +543,20 @@ int dom_node_namespace_uri_read(dom_object *obj, zval *retval)
|
||||||
case XML_ATTRIBUTE_NODE:
|
case XML_ATTRIBUTE_NODE:
|
||||||
case XML_NAMESPACE_DECL:
|
case XML_NAMESPACE_DECL:
|
||||||
if (nodep->ns != NULL) {
|
if (nodep->ns != NULL) {
|
||||||
char *str = (char *) nodep->ns->href;
|
str = (char *) nodep->ns->href;
|
||||||
/* https://dom.spec.whatwg.org/#concept-attribute: namespaceUri is "null or a non-empty string" */
|
|
||||||
if (str != NULL && str[0] != '\0') {
|
|
||||||
ZVAL_STRING(retval, str);
|
|
||||||
return SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
str = NULL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (str != NULL) {
|
||||||
|
ZVAL_STRING(retval, str);
|
||||||
|
} else {
|
||||||
ZVAL_NULL(retval);
|
ZVAL_NULL(retval);
|
||||||
|
}
|
||||||
|
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1496,22 +1496,6 @@ static void dom_libxml_reconcile_ensure_namespaces_are_declared(xmlNodePtr nodep
|
||||||
xmlDOMWrapReconcileNamespaces(&dummy_ctxt, nodep, /* options */ 0);
|
xmlDOMWrapReconcileNamespaces(&dummy_ctxt, nodep, /* options */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dom_must_replace_namespace_by_empty_default(xmlDocPtr doc, xmlNodePtr nodep)
|
|
||||||
{
|
|
||||||
xmlNsPtr default_ns = xmlSearchNs(doc, nodep->parent, NULL);
|
|
||||||
return default_ns != NULL && default_ns->href != NULL && default_ns->href[0] != '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dom_replace_namespace_by_empty_default(xmlDocPtr doc, xmlNodePtr nodep)
|
|
||||||
{
|
|
||||||
ZEND_ASSERT(nodep->ns == NULL);
|
|
||||||
/* The node uses the default empty namespace, but the current default namespace is non-empty.
|
|
||||||
* We can't unconditionally do this because otherwise libxml2 creates an xmlns="" declaration.
|
|
||||||
* Note: there's no point searching the oldNs list, because we haven't found it in the tree anyway.
|
|
||||||
* Ideally this would be pre-allocated but unfortunately libxml2 doesn't offer such a functionality. */
|
|
||||||
xmlSetNs(nodep, xmlNewNs(nodep, (const xmlChar *) "", NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
|
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
|
||||||
{
|
{
|
||||||
/* Although the node type will be checked by the libxml2 API,
|
/* Although the node type will be checked by the libxml2 API,
|
||||||
|
@ -1519,10 +1503,6 @@ void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
|
||||||
if (nodep->type == XML_ELEMENT_NODE) {
|
if (nodep->type == XML_ELEMENT_NODE) {
|
||||||
dom_reconcile_ns_internal(doc, nodep, nodep->parent);
|
dom_reconcile_ns_internal(doc, nodep, nodep->parent);
|
||||||
dom_libxml_reconcile_ensure_namespaces_are_declared(nodep);
|
dom_libxml_reconcile_ensure_namespaces_are_declared(nodep);
|
||||||
/* Check nodep->ns first to avoid an expensive lookup. */
|
|
||||||
if (nodep->ns == NULL && dom_must_replace_namespace_by_empty_default(doc, nodep)) {
|
|
||||||
dom_replace_namespace_by_empty_default(doc, nodep);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
@ -1546,30 +1526,12 @@ static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlN
|
||||||
|
|
||||||
void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last)
|
void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last)
|
||||||
{
|
{
|
||||||
bool did_compute_must_replace_namespace_by_empty_default = false;
|
|
||||||
bool must_replace_namespace_by_empty_default = false;
|
|
||||||
|
|
||||||
dom_reconcile_ns_list_internal(doc, nodep, last, nodep->parent);
|
dom_reconcile_ns_list_internal(doc, nodep, last, nodep->parent);
|
||||||
|
|
||||||
/* The loop is outside of the recursion in the above call because
|
/* The loop is outside of the recursion in the above call because
|
||||||
* dom_libxml_reconcile_ensure_namespaces_are_declared() performs its own recursion. */
|
* dom_libxml_reconcile_ensure_namespaces_are_declared() performs its own recursion. */
|
||||||
while (true) {
|
while (true) {
|
||||||
/* The internal libxml2 call will already check the node type, no need for us to do it here. */
|
/* The internal libxml2 call will already check the node type, no need for us to do it here. */
|
||||||
dom_libxml_reconcile_ensure_namespaces_are_declared(nodep);
|
dom_libxml_reconcile_ensure_namespaces_are_declared(nodep);
|
||||||
|
|
||||||
/* We don't have to handle the children, because if their ns's are NULL they'll just take on the default
|
|
||||||
* which should've been reconciled before. */
|
|
||||||
if (nodep->ns == NULL) {
|
|
||||||
/* This is an optimistic approach: we assume that most of the time we don't need the result of the computation. */
|
|
||||||
if (!did_compute_must_replace_namespace_by_empty_default) {
|
|
||||||
did_compute_must_replace_namespace_by_empty_default = true;
|
|
||||||
must_replace_namespace_by_empty_default = dom_must_replace_namespace_by_empty_default(doc, nodep);
|
|
||||||
}
|
|
||||||
if (must_replace_namespace_by_empty_default) {
|
|
||||||
dom_replace_namespace_by_empty_default(doc, nodep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodep == last) {
|
if (nodep == last) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,7 @@ test_appendChild_with_shadowing();
|
||||||
<html xmlns="https://php.net/something" xmlns:ns="https://php.net/whatever"><element ns:foo="https://php.net/bar"/></html>
|
<html xmlns="https://php.net/something" xmlns:ns="https://php.net/whatever"><element ns:foo="https://php.net/bar"/></html>
|
||||||
-- Test document fragment without import --
|
-- Test document fragment without import --
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<html xmlns=""><element xmlns:foo="https://php.net/bar"><foo:bar/><bar/></element></html>
|
<html xmlns=""><element xmlns:foo="https://php.net/bar"><foo:bar/><bar xmlns=""/></element></html>
|
||||||
string(7) "foo:bar"
|
string(7) "foo:bar"
|
||||||
string(19) "https://php.net/bar"
|
string(19) "https://php.net/bar"
|
||||||
-- Test document import --
|
-- Test document import --
|
||||||
|
|
|
@ -1,160 +0,0 @@
|
||||||
--TEST--
|
|
||||||
GH-11404: DOMDocument::savexml and friends ommit xmlns="" declaration for null namespace, creating incorrect xml representation of the DOM
|
|
||||||
--EXTENSIONS--
|
|
||||||
dom
|
|
||||||
--FILE--
|
|
||||||
<?php
|
|
||||||
|
|
||||||
echo "-- Test append and attributes: with default namespace variation --\n";
|
|
||||||
|
|
||||||
function testAppendAndAttributes($dom) {
|
|
||||||
$nodeA = $dom->createElement('a');
|
|
||||||
$nodeB = $dom->createElementNS(null, 'b');
|
|
||||||
$nodeC = $dom->createElementNS('', 'c');
|
|
||||||
$nodeD = $dom->createElement('d');
|
|
||||||
$nodeD->setAttributeNS('some:ns', 'x:attrib', 'val');
|
|
||||||
$nodeE = $dom->createElementNS('some:ns', 'e');
|
|
||||||
// And these two respect the default ns.
|
|
||||||
$nodeE->setAttributeNS(null, 'attrib1', 'val');
|
|
||||||
$nodeE->setAttributeNS('', 'attrib2', 'val');
|
|
||||||
|
|
||||||
$dom->documentElement->appendChild($nodeA);
|
|
||||||
$dom->documentElement->appendChild($nodeB);
|
|
||||||
$dom->documentElement->appendChild($nodeC);
|
|
||||||
$dom->documentElement->appendChild($nodeD);
|
|
||||||
$dom->documentElement->appendChild($nodeE);
|
|
||||||
|
|
||||||
var_dump($nodeA->namespaceURI);
|
|
||||||
var_dump($nodeB->namespaceURI);
|
|
||||||
var_dump($nodeC->namespaceURI);
|
|
||||||
var_dump($nodeD->namespaceURI);
|
|
||||||
var_dump($nodeE->namespaceURI);
|
|
||||||
|
|
||||||
echo $dom->saveXML();
|
|
||||||
|
|
||||||
// Create a subtree without using a fragment
|
|
||||||
$subtree = $dom->createElement('subtree');
|
|
||||||
$subtree->appendChild($dom->createElementNS('some:ns', 'subtreechild1'));
|
|
||||||
$subtree->firstElementChild->appendChild($dom->createElement('subtreechild2'));
|
|
||||||
$dom->documentElement->appendChild($subtree);
|
|
||||||
|
|
||||||
echo $dom->saveXML();
|
|
||||||
|
|
||||||
// Create a subtree with the use of a fragment
|
|
||||||
$subtree = $dom->createDocumentFragment();
|
|
||||||
$subtree->appendChild($child3 = $dom->createElement('child3'));
|
|
||||||
$child3->appendChild($dom->createElement('child4'));
|
|
||||||
$subtree->appendChild($dom->createElement('child5'));
|
|
||||||
$dom->documentElement->appendChild($subtree);
|
|
||||||
|
|
||||||
echo $dom->saveXML();
|
|
||||||
}
|
|
||||||
|
|
||||||
$dom1 = new DOMDocument;
|
|
||||||
$dom1->loadXML('<?xml version="1.0" ?><with xmlns="some:ns" />');
|
|
||||||
testAppendAndAttributes($dom1);
|
|
||||||
|
|
||||||
echo "-- Test append and attributes: without default namespace variation --\n";
|
|
||||||
|
|
||||||
$dom1 = new DOMDocument;
|
|
||||||
$dom1->loadXML('<?xml version="1.0" ?><with/>');
|
|
||||||
testAppendAndAttributes($dom1);
|
|
||||||
|
|
||||||
echo "-- Test import --\n";
|
|
||||||
|
|
||||||
function testImport(?string $href, string $toBeImported) {
|
|
||||||
$dom1 = new DOMDocument;
|
|
||||||
$decl = $href === NULL ? '' : "xmlns=\"$href\"";
|
|
||||||
$dom1->loadXML('<?xml version="1.0" ?><with ' . $decl . '/>');
|
|
||||||
|
|
||||||
$dom2 = new DOMDocument;
|
|
||||||
$dom2->loadXML('<?xml version="1.0" ?>' . $toBeImported);
|
|
||||||
|
|
||||||
$dom1->documentElement->append(
|
|
||||||
$imported = $dom1->importNode($dom2->documentElement, true)
|
|
||||||
);
|
|
||||||
|
|
||||||
var_dump($imported->namespaceURI);
|
|
||||||
|
|
||||||
echo $dom1->saveXML();
|
|
||||||
}
|
|
||||||
|
|
||||||
testImport(null, '<none/>');
|
|
||||||
testImport('', '<none/>');
|
|
||||||
testImport('some:ns', '<none/>');
|
|
||||||
testImport('', '<none><div xmlns="some:ns"/></none>');
|
|
||||||
testImport('some:ns', '<none xmlns="some:ns"><div xmlns=""/></none>');
|
|
||||||
|
|
||||||
echo "-- Namespace URI comparison --\n";
|
|
||||||
|
|
||||||
$dom1 = new DOMDocument;
|
|
||||||
$dom1->loadXML('<?xml version="1.0"?><test xmlns="a:b"><div/></test>');
|
|
||||||
var_dump($dom1->firstElementChild->namespaceURI);
|
|
||||||
var_dump($dom1->firstElementChild->firstElementChild->namespaceURI);
|
|
||||||
|
|
||||||
$dom1 = new DOMDocument;
|
|
||||||
$dom1->appendChild($dom1->createElementNS('a:b', 'parent'));
|
|
||||||
$dom1->firstElementChild->appendChild($dom1->createElementNS('a:b', 'child1'));
|
|
||||||
$dom1->firstElementChild->appendChild($second = $dom1->createElement('child2'));
|
|
||||||
var_dump($dom1->firstElementChild->namespaceURI);
|
|
||||||
var_dump($dom1->firstElementChild->firstElementChild->namespaceURI);
|
|
||||||
var_dump($second->namespaceURI);
|
|
||||||
echo $dom1->saveXML();
|
|
||||||
|
|
||||||
$dom1 = new DOMDocument;
|
|
||||||
$dom1->loadXML('<?xml version="1.0"?><test xmlns="a:b"/>');
|
|
||||||
var_dump($dom1->firstElementChild->namespaceURI);
|
|
||||||
$dom1->firstElementChild->appendChild($dom1->createElementNS('a:b', 'tag'));
|
|
||||||
var_dump($dom1->firstElementChild->firstElementChild->namespaceURI);
|
|
||||||
?>
|
|
||||||
--EXPECT--
|
|
||||||
-- Test append and attributes: with default namespace variation --
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
string(7) "some:ns"
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with xmlns="some:ns"><a xmlns=""/><b xmlns=""/><c xmlns=""/><d xmlns:x="some:ns" xmlns="" x:attrib="val"/><e attrib1="val" attrib2="val"/></with>
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with xmlns="some:ns"><a xmlns=""/><b xmlns=""/><c xmlns=""/><d xmlns:x="some:ns" xmlns="" x:attrib="val"/><e attrib1="val" attrib2="val"/><subtree xmlns=""><subtreechild1 xmlns="some:ns"><subtreechild2 xmlns=""/></subtreechild1></subtree></with>
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with xmlns="some:ns"><a xmlns=""/><b xmlns=""/><c xmlns=""/><d xmlns:x="some:ns" xmlns="" x:attrib="val"/><e attrib1="val" attrib2="val"/><subtree xmlns=""><subtreechild1 xmlns="some:ns"><subtreechild2 xmlns=""/></subtreechild1></subtree><child3 xmlns=""><child4/></child3><child5 xmlns=""/></with>
|
|
||||||
-- Test append and attributes: without default namespace variation --
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
NULL
|
|
||||||
string(7) "some:ns"
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with><a/><b/><c/><d xmlns:x="some:ns" x:attrib="val"/><e xmlns="some:ns" attrib1="val" attrib2="val"/></with>
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with><a/><b/><c/><d xmlns:x="some:ns" x:attrib="val"/><e xmlns="some:ns" attrib1="val" attrib2="val"/><subtree><subtreechild1 xmlns="some:ns"><subtreechild2 xmlns=""/></subtreechild1></subtree></with>
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with><a/><b/><c/><d xmlns:x="some:ns" x:attrib="val"/><e xmlns="some:ns" attrib1="val" attrib2="val"/><subtree><subtreechild1 xmlns="some:ns"><subtreechild2 xmlns=""/></subtreechild1></subtree><child3><child4/></child3><child5/></with>
|
|
||||||
-- Test import --
|
|
||||||
NULL
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with><none/></with>
|
|
||||||
NULL
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with xmlns=""><none/></with>
|
|
||||||
NULL
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with xmlns="some:ns"><none xmlns=""/></with>
|
|
||||||
NULL
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with xmlns=""><none><div xmlns="some:ns"/></none></with>
|
|
||||||
string(7) "some:ns"
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<with xmlns="some:ns"><none><div xmlns=""/></none></with>
|
|
||||||
-- Namespace URI comparison --
|
|
||||||
string(3) "a:b"
|
|
||||||
string(3) "a:b"
|
|
||||||
string(3) "a:b"
|
|
||||||
string(3) "a:b"
|
|
||||||
NULL
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<parent xmlns="a:b"><child1/><child2 xmlns=""/></parent>
|
|
||||||
string(3) "a:b"
|
|
||||||
string(3) "a:b"
|
|
Loading…
Add table
Add a link
Reference in a new issue