mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Fix GH-13012: DOMNode::isEqualNode() is incorrect when attribute order is different
Attributes (and namespace declarations) have to be compared in an unordered way. Closes GH-13017.
This commit is contained in:
2
NEWS
2
NEWS
@@ -22,6 +22,8 @@ PHP NEWS
|
||||
(nielsdos)
|
||||
. Fix crash when toggleAttribute() is used without a document. (nielsdos)
|
||||
. Fix crash in adoptNode with attribute references. (nielsdos)
|
||||
. Fixed bug GH-13012 (DOMNode::isEqualNode() is incorrect when attribute
|
||||
order is different). (nielsdos)
|
||||
|
||||
- FFI:
|
||||
. Fixed bug GH-9698 (stream_wrapper_register crashes with FFI\CData).
|
||||
|
||||
@@ -1490,33 +1490,58 @@ static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other
|
||||
|
||||
#define PHP_DOM_FUNC_CAT(prefix, suffix) prefix##_##suffix
|
||||
/* xmlNode and xmlNs have incompatible struct layouts, i.e. the next field is in a different offset */
|
||||
#define PHP_DOM_DEFINE_LIST_EQUALITY_HELPER(type) \
|
||||
static size_t PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(const type *node) \
|
||||
{ \
|
||||
size_t counter = 0; \
|
||||
while (node) { \
|
||||
counter++; \
|
||||
node = node->next; \
|
||||
} \
|
||||
return counter; \
|
||||
} \
|
||||
static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check, type)(const type *list1, const type *list2) \
|
||||
{ \
|
||||
size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1); \
|
||||
if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) { \
|
||||
return false; \
|
||||
} \
|
||||
for (size_t i = 0; i < count; i++) { \
|
||||
if (!php_dom_node_is_equal_node((const xmlNode *) list1, (const xmlNode *) list2)) { \
|
||||
return false; \
|
||||
} \
|
||||
list1 = list1->next; \
|
||||
list2 = list2->next; \
|
||||
} \
|
||||
return true; \
|
||||
#define PHP_DOM_DEFINE_LIST_COUNTER_HELPER(type) \
|
||||
static size_t PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(const type *node) \
|
||||
{ \
|
||||
size_t counter = 0; \
|
||||
while (node) { \
|
||||
counter++; \
|
||||
node = node->next; \
|
||||
} \
|
||||
return counter; \
|
||||
}
|
||||
PHP_DOM_DEFINE_LIST_EQUALITY_HELPER(xmlNode)
|
||||
PHP_DOM_DEFINE_LIST_EQUALITY_HELPER(xmlNs)
|
||||
#define PHP_DOM_DEFINE_LIST_EQUALITY_ORDERED_HELPER(type) \
|
||||
static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check_ordered, type)(const type *list1, const type *list2) \
|
||||
{ \
|
||||
size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1); \
|
||||
if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) { \
|
||||
return false; \
|
||||
} \
|
||||
for (size_t i = 0; i < count; i++) { \
|
||||
if (!php_dom_node_is_equal_node((const xmlNode *) list1, (const xmlNode *) list2)) { \
|
||||
return false; \
|
||||
} \
|
||||
list1 = list1->next; \
|
||||
list2 = list2->next; \
|
||||
} \
|
||||
return true; \
|
||||
}
|
||||
#define PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(type) \
|
||||
static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check_unordered, type)(const type *list1, const type *list2)\
|
||||
{ \
|
||||
size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1); \
|
||||
if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) { \
|
||||
return false; \
|
||||
} \
|
||||
for (const type *n1 = list1; n1 != NULL; n1 = n1->next) { \
|
||||
bool found = false; \
|
||||
for (const type *n2 = list2; n2 != NULL && !found; n2 = n2->next) { \
|
||||
if (php_dom_node_is_equal_node((const xmlNode *) n1, (const xmlNode *) n2)) { \
|
||||
found = true; \
|
||||
} \
|
||||
} \
|
||||
if (!found) { \
|
||||
return false; \
|
||||
} \
|
||||
} \
|
||||
return true; \
|
||||
}
|
||||
|
||||
PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNode)
|
||||
PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNs)
|
||||
PHP_DOM_DEFINE_LIST_EQUALITY_ORDERED_HELPER(xmlNode)
|
||||
PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(xmlNode)
|
||||
PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(xmlNs)
|
||||
|
||||
static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other)
|
||||
{
|
||||
@@ -1535,9 +1560,9 @@ static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other
|
||||
&& php_dom_node_is_ns_prefix_equal(this, other)
|
||||
&& php_dom_node_is_ns_uri_equal(this, other)
|
||||
/* Check attributes first, then namespace declarations, then children */
|
||||
&& php_dom_node_list_equality_check_xmlNode((const xmlNode *) this->properties, (const xmlNode *) other->properties)
|
||||
&& php_dom_node_list_equality_check_xmlNs(this->nsDef, other->nsDef)
|
||||
&& php_dom_node_list_equality_check_xmlNode(this->children, other->children);
|
||||
&& php_dom_node_list_equality_check_unordered_xmlNode((const xmlNode *) this->properties, (const xmlNode *) other->properties)
|
||||
&& php_dom_node_list_equality_check_unordered_xmlNs(this->nsDef, other->nsDef)
|
||||
&& php_dom_node_list_equality_check_ordered_xmlNode(this->children, other->children);
|
||||
} else if (this->type == XML_DTD_NODE) {
|
||||
/* Note: in the living spec entity declarations and notations are no longer compared because they're considered obsolete. */
|
||||
const xmlDtd *this_dtd = (const xmlDtd *) this;
|
||||
@@ -1570,7 +1595,7 @@ static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other
|
||||
const xmlNs *other_ns = (const xmlNs *) other;
|
||||
return xmlStrEqual(this_ns->prefix, other_ns->prefix) && xmlStrEqual(this_ns->href, other_ns->href);
|
||||
} else if (this->type == XML_DOCUMENT_FRAG_NODE || this->type == XML_HTML_DOCUMENT_NODE || this->type == XML_DOCUMENT_NODE) {
|
||||
return php_dom_node_list_equality_check_xmlNode(this->children, other->children);
|
||||
return php_dom_node_list_equality_check_ordered_xmlNode(this->children, other->children);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
37
ext/dom/tests/gh13012.phpt
Normal file
37
ext/dom/tests/gh13012.phpt
Normal file
@@ -0,0 +1,37 @@
|
||||
--TEST--
|
||||
GH-13012 (DOMNode::isEqualNode() is incorrect when attribute order is different)
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$dom = new DOMDocument;
|
||||
$dom->loadXML("<root><x a='a' b='b'/><x b='b' a='a'/><x b='b' a='a' c='c'/></root>");
|
||||
|
||||
foreach ($dom->getElementsByTagName('x') as $x1) {
|
||||
foreach ($dom->getElementsByTagName('x') as $x2) {
|
||||
echo "Comparing ", $dom->saveXML($x1), " with ", $dom->saveXML($x2), "\n";
|
||||
var_dump($x1->isEqualNode($x2));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Comparing <x a="a" b="b"/> with <x a="a" b="b"/>
|
||||
bool(true)
|
||||
Comparing <x a="a" b="b"/> with <x b="b" a="a"/>
|
||||
bool(true)
|
||||
Comparing <x a="a" b="b"/> with <x b="b" a="a" c="c"/>
|
||||
bool(false)
|
||||
Comparing <x b="b" a="a"/> with <x a="a" b="b"/>
|
||||
bool(true)
|
||||
Comparing <x b="b" a="a"/> with <x b="b" a="a"/>
|
||||
bool(true)
|
||||
Comparing <x b="b" a="a"/> with <x b="b" a="a" c="c"/>
|
||||
bool(false)
|
||||
Comparing <x b="b" a="a" c="c"/> with <x a="a" b="b"/>
|
||||
bool(false)
|
||||
Comparing <x b="b" a="a" c="c"/> with <x b="b" a="a"/>
|
||||
bool(false)
|
||||
Comparing <x b="b" a="a" c="c"/> with <x b="b" a="a" c="c"/>
|
||||
bool(true)
|
||||
37
ext/dom/tests/gh13012_ns.phpt
Normal file
37
ext/dom/tests/gh13012_ns.phpt
Normal file
@@ -0,0 +1,37 @@
|
||||
--TEST--
|
||||
GH-13012 (DOMNode::isEqualNode() is incorrect when attribute order is different - ns variation)
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$dom = new DOMDocument;
|
||||
$dom->loadXML("<root><x/><x xmlns:a=\"urn:a\" xmlns:b=\"urn:b\"/><x xmlns:b=\"urn:b\" xmlns:a=\"urn:a\"/></root>");
|
||||
|
||||
foreach ($dom->getElementsByTagName('x') as $x1) {
|
||||
foreach ($dom->getElementsByTagName('x') as $x2) {
|
||||
echo "Comparing ", $dom->saveXML($x1), " with ", $dom->saveXML($x2), "\n";
|
||||
var_dump($x1->isEqualNode($x2));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Comparing <x/> with <x/>
|
||||
bool(true)
|
||||
Comparing <x/> with <x xmlns:a="urn:a" xmlns:b="urn:b"/>
|
||||
bool(false)
|
||||
Comparing <x/> with <x xmlns:b="urn:b" xmlns:a="urn:a"/>
|
||||
bool(false)
|
||||
Comparing <x xmlns:a="urn:a" xmlns:b="urn:b"/> with <x/>
|
||||
bool(false)
|
||||
Comparing <x xmlns:a="urn:a" xmlns:b="urn:b"/> with <x xmlns:a="urn:a" xmlns:b="urn:b"/>
|
||||
bool(true)
|
||||
Comparing <x xmlns:a="urn:a" xmlns:b="urn:b"/> with <x xmlns:b="urn:b" xmlns:a="urn:a"/>
|
||||
bool(true)
|
||||
Comparing <x xmlns:b="urn:b" xmlns:a="urn:a"/> with <x/>
|
||||
bool(false)
|
||||
Comparing <x xmlns:b="urn:b" xmlns:a="urn:a"/> with <x xmlns:a="urn:a" xmlns:b="urn:b"/>
|
||||
bool(true)
|
||||
Comparing <x xmlns:b="urn:b" xmlns:a="urn:a"/> with <x xmlns:b="urn:b" xmlns:a="urn:a"/>
|
||||
bool(true)
|
||||
Reference in New Issue
Block a user