Fix spec compliance error for DOMDocument::getElementsByTagNameNS

Spec link: https://dom.spec.whatwg.org/#concept-getelementsbytagnamens
Spec says we should match any namespace when '*' is provided. This was
however not the case: elements that didn't have a namespace were not
returned. This patch fixes the error by modifying the namespace check.

Closes GH-11343.
This commit is contained in:
Niels Dossche 2023-05-29 18:36:02 +02:00 committed by nielsdos
parent 9c59d22a7b
commit 154c251013
3 changed files with 90 additions and 1 deletions

2
NEWS
View File

@ -11,6 +11,8 @@ PHP NEWS
. Fixed bug GH-10234 (Setting DOMAttr::textContent results in an empty
attribute value). (nielsdos)
. Fix return value in stub file for DOMNodeList::item. (divinity76)
. Fix spec compliance error with '*' namespace for
DOMDocument::getElementsByTagNameNS. (nielsdos)
- Opcache:
. Fix allocation loop in zend_shared_alloc_startup(). (nielsdos)

View File

@ -1270,10 +1270,15 @@ xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *l
{
xmlNodePtr ret = NULL;
/* Note: The spec says that ns == '' must be transformed to ns == NULL. In other words, they are equivalent.
* PHP however does not do this and internally uses the empty string everywhere when the user provides ns == NULL.
* This is because for PHP ns == NULL has another meaning: "match every namespace" instead of "match the empty namespace". */
bool ns_match_any = ns == NULL || (ns[0] == '*' && ns[1] == '\0');
while (nodep != NULL && (*cur <= index || index == -1)) {
if (nodep->type == XML_ELEMENT_NODE) {
if (xmlStrEqual(nodep->name, (xmlChar *)local) || xmlStrEqual((xmlChar *)"*", (xmlChar *)local)) {
if (ns == NULL || (!strcmp(ns, "") && nodep->ns == NULL) || (nodep->ns != NULL && (xmlStrEqual(nodep->ns->href, (xmlChar *)ns) || xmlStrEqual((xmlChar *)"*", (xmlChar *)ns)))) {
if (ns_match_any || (!strcmp(ns, "") && nodep->ns == NULL) || (nodep->ns != NULL && xmlStrEqual(nodep->ns->href, (xmlChar *)ns))) {
if (*cur == index) {
ret = nodep;
break;

View File

@ -0,0 +1,82 @@
--TEST--
DOMDocument::getElementsByTagNameNS() match any namespace
--EXTENSIONS--
dom
--FILE--
<?php
/* Sample document taken from https://www.php.net/manual/en/domdocument.getelementsbytagname.php */
$xml = <<<EOD
<?xml version="1.0" ?>
<chapter xmlns:xi="http://www.w3.org/2001/XInclude">
<title>Books of the other guy..</title>
<para>
<xi:include href="book.xml">
<xi:fallback>
<error>xinclude: book.xml not found</error>
</xi:fallback>
</xi:include>
<include>
This is another namespace
</include>
</para>
</chapter>
EOD;
$dom = new DOMDocument;
// load the XML string defined above
$dom->loadXML($xml);
function test($namespace, $local) {
global $dom;
$namespace_str = $namespace !== NULL ? "'$namespace'" : "null";
echo "-- getElementsByTagNameNS($namespace_str, '$local') --\n";
foreach ($dom->getElementsByTagNameNS($namespace, $local) as $element) {
echo 'local name: \'', $element->localName, '\', prefix: \'', $element->prefix, "'\n";
}
}
// Should *also* include objects even without a namespace
test(null, '*');
// Should *also* include objects even without a namespace
test('*', '*');
// Should *only* include objects without a namespace
test('', '*');
// Should *only* include objects with the specified namespace
test('http://www.w3.org/2001/XInclude', '*');
// Should not give any output
test('', 'fallback');
// Should not give any output, because the null namespace is the same as the empty namespace
test(null, 'fallback');
// Should only output the include from the empty namespace
test(null, 'include');
?>
--EXPECT--
-- getElementsByTagNameNS(null, '*') --
local name: 'chapter', prefix: ''
local name: 'title', prefix: ''
local name: 'para', prefix: ''
local name: 'error', prefix: ''
local name: 'include', prefix: ''
-- getElementsByTagNameNS('*', '*') --
local name: 'chapter', prefix: ''
local name: 'title', prefix: ''
local name: 'para', prefix: ''
local name: 'include', prefix: 'xi'
local name: 'fallback', prefix: 'xi'
local name: 'error', prefix: ''
local name: 'include', prefix: ''
-- getElementsByTagNameNS('', '*') --
local name: 'chapter', prefix: ''
local name: 'title', prefix: ''
local name: 'para', prefix: ''
local name: 'error', prefix: ''
local name: 'include', prefix: ''
-- getElementsByTagNameNS('http://www.w3.org/2001/XInclude', '*') --
local name: 'include', prefix: 'xi'
local name: 'fallback', prefix: 'xi'
-- getElementsByTagNameNS('', 'fallback') --
-- getElementsByTagNameNS(null, 'fallback') --
-- getElementsByTagNameNS(null, 'include') --
local name: 'include', prefix: ''