1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Fix GH-11500: Namespace reuse in createElementNS() generates wrong output

When you construct a DOM tree containing subtrees which are constructed
top-down, this won't remove the redundant namespaces. That's because the
following conditions hold:
1) The namespace are reused from the doc->oldNs list.
2) Therefore during reconciliation no nsDef field is set, so no redundant
   namespaces are removed by our reconciliation code.

Furthermore, it would only be fixed up automatically if the tree wasn't
added in bottom-up way, or if it had been constructed bottom-up from the
start.

Fix it by setting a flag to remove redundant namespaces in the libxml2
reconciliation call.
Since removing redundant namespaces may have a performance cost, we only do
this after performing a simple check.

Closes GH-11528.
This commit is contained in:
Niels Dossche
2023-06-25 13:48:04 +02:00
committed by nielsdos
parent c0147a0588
commit 961e57eb60
3 changed files with 166 additions and 1 deletions

4
NEWS
View File

@@ -6,6 +6,10 @@ PHP NEWS
. Fixed bug GH-11507 (String concatenation performance regression in 8.3).
(nielsdos)
- DOM:
. Fixed bug GH-11500 (Namespace reuse in createElementNS() generates wrong
output). (nielsdos)
- Fileinfo:
. Fix GH-11408 (Unable to build PHP 8.3.0 alpha 1 / fileinfo extension).
(nielsdos)

View File

@@ -32,6 +32,10 @@
#define PHP_XPATH 1
#define PHP_XPTR 2
/* libxml2 doesn't expose this constant as part of their public API.
* See xmlDOMReconcileNSOptions in tree.c */
#define PHP_LIBXML2_DOM_RECONNS_REMOVEREDUND (1 << 0)
/* {{{ class entries */
PHP_DOM_EXPORT zend_class_entry *dom_node_class_entry;
PHP_DOM_EXPORT zend_class_entry *dom_domexception_class_entry;
@@ -1494,7 +1498,8 @@ static void dom_libxml_reconcile_ensure_namespaces_are_declared(xmlNodePtr nodep
* Although libxml2 currently does not use this for the reconciliation, it still
* makes sense to do this just in case libxml2's internal change in the future. */
xmlDOMWrapCtxt dummy_ctxt = {0};
xmlDOMWrapReconcileNamespaces(&dummy_ctxt, nodep, /* options */ 0);
bool remove_redundant = nodep->nsDef == NULL && nodep->ns != NULL;
xmlDOMWrapReconcileNamespaces(&dummy_ctxt, nodep, /* options */ remove_redundant ? PHP_LIBXML2_DOM_RECONNS_REMOVEREDUND : 0);
}
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */

156
ext/dom/tests/gh11500.phpt Normal file
View File

@@ -0,0 +1,156 @@
--TEST--
GH-11500 (Namespace reuse in createElementNS() generates wrong output)
--EXTENSIONS--
dom
--FILE--
<?php
function api_test_depth2($root_ns) {
$dom = new DOMDocument();
$root = $dom->createElementNS($root_ns, 'root');
$dom->appendChild($root);
$a1 = $dom->createElementNS('http://example.com', 'a1');
$b1 = $a1->appendChild($dom->createElementNS('http://example.com', 'b1'));
$root->appendChild($a1);
$a2 = $dom->createElementNS('http://example.com', 'a2');
$b2 = $a2->appendChild($dom->createElementNS('http://example.com', 'b2'));
$root->appendChild($a2);
echo $dom->saveXML();
var_dump($root->namespaceURI);
var_dump($a1->namespaceURI);
var_dump($b1->namespaceURI);
var_dump($a2->namespaceURI);
var_dump($b2->namespaceURI);
}
function api_test_depth3($root_ns, $swapped) {
$dom = new DOMDocument();
$root = $dom->createElementNS($root_ns, 'root');
$dom->appendChild($root);
$a1 = $dom->createElementNS('http://example.com', 'a1');
$b1 = $a1->appendChild($dom->createElementNS('http://example.com', 'b1'));
$c1 = $b1->appendChild($dom->createElementNS('http://example.com', 'c1'));
$root->appendChild($a1);
$a2 = $dom->createElementNS('http://example.com', 'a2');
if ($swapped) {
$b2 = $dom->createElementNS('http://example.com', 'b2');
$c2 = $b2->appendChild($dom->createElementNS('http://example.com', 'c2'));
$a2->appendChild($b2);
} else {
$b2 = $a2->appendChild($dom->createElementNS('http://example.com', 'b2'));
$c2 = $b2->appendChild($dom->createElementNS('http://example.com', 'c2'));
}
$root->appendChild($a2);
echo $dom->saveXML();
var_dump($root->namespaceURI);
var_dump($a1->namespaceURI);
var_dump($b1->namespaceURI);
var_dump($c1->namespaceURI);
var_dump($a2->namespaceURI);
var_dump($b2->namespaceURI);
var_dump($c2->namespaceURI);
}
echo "-- Constructed from API (depth 2, mismatched root variation) --\n";
api_test_depth2('http://example2.com');
echo "-- Constructed from API (depth 2, matching root variation) --\n";
api_test_depth2('http://example.com');
echo "-- Constructed from API (depth 3, mismatched root variation, non-swapped) --\n";
api_test_depth3('http://example2.com', false);
echo "-- Constructed from API (depth 3, matching root variation, non-swapped) --\n";
api_test_depth3('http://example.com', false);
echo "-- Constructed from API (depth 3, mismatched root variation, swapped) --\n";
api_test_depth3('http://example2.com', true);
echo "-- Constructed from API (depth 3, matching root variation, swapped) --\n";
api_test_depth3('http://example.com', true);
echo "-- Constructed depth 2 from string --\n";
$xml = '<?xml version="1.0"?><root xmlns="http://example2.com"><a1 xmlns="http://example.com"><b1/></a1><a2 xmlns="http://example.com"><b2/></a2></root>';
$dom = new DOMDocument;
$dom->loadXML($xml);
echo $dom->saveXML(), "\n";
var_dump($dom->documentElement->namespaceURI); // root
var_dump($dom->documentElement->firstChild->namespaceURI); // a1
var_dump($dom->documentElement->firstChild->firstChild->namespaceURI); // b1
var_dump($dom->documentElement->firstChild->nextSibling->namespaceURI); // a2
var_dump($dom->documentElement->firstChild->nextSibling->firstChild->namespaceURI); // b2
?>
--EXPECT--
-- Constructed from API (depth 2, mismatched root variation) --
<?xml version="1.0"?>
<root xmlns="http://example2.com"><a1 xmlns="http://example.com"><b1/></a1><a2 xmlns="http://example.com"><b2/></a2></root>
string(19) "http://example2.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
-- Constructed from API (depth 2, matching root variation) --
<?xml version="1.0"?>
<root xmlns="http://example.com"><a1><b1/></a1><a2><b2/></a2></root>
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
-- Constructed from API (depth 3, mismatched root variation, non-swapped) --
<?xml version="1.0"?>
<root xmlns="http://example2.com"><a1 xmlns="http://example.com"><b1><c1/></b1></a1><a2 xmlns="http://example.com"><b2><c2/></b2></a2></root>
string(19) "http://example2.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
-- Constructed from API (depth 3, matching root variation, non-swapped) --
<?xml version="1.0"?>
<root xmlns="http://example.com"><a1><b1><c1/></b1></a1><a2><b2><c2/></b2></a2></root>
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
-- Constructed from API (depth 3, mismatched root variation, swapped) --
<?xml version="1.0"?>
<root xmlns="http://example2.com"><a1 xmlns="http://example.com"><b1><c1/></b1></a1><a2 xmlns="http://example.com"><b2><c2/></b2></a2></root>
string(19) "http://example2.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
-- Constructed from API (depth 3, matching root variation, swapped) --
<?xml version="1.0"?>
<root xmlns="http://example.com"><a1><b1><c1/></b1></a1><a2><b2><c2/></b2></a2></root>
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
-- Constructed depth 2 from string --
<?xml version="1.0"?>
<root xmlns="http://example2.com"><a1 xmlns="http://example.com"><b1/></a1><a2 xmlns="http://example.com"><b2/></a2></root>
string(19) "http://example2.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"
string(18) "http://example.com"