From 15ff830373be3ae46d659c72fea27a2696039241 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 7 Jul 2023 23:13:56 +0200 Subject: [PATCH 1/3] Fix GH-11625: DOMElement::replaceWith() doesn't replace node with DOMDocumentFragment but just deletes node or causes wrapping <> depending on libxml2 version Depending on the libxml2 version, the behaviour is either to not render the fragment correctly, or to wrap it inside <>. Fix it by unpacking fragments manually. This has the side effect that we need to move the unlinking check in the replacement function to earlier because the empty child list is now possible in non-error cases. Also fixes a mistake in the linked list management. Closes GH-11627. --- NEWS | 5 +++ ext/dom/parentnode.c | 22 ++++++++---- ext/dom/tests/gh11625.phpt | 72 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 ext/dom/tests/gh11625.phpt diff --git a/NEWS b/NEWS index b72270513f3b6..ffa067224c378 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,11 @@ PHP NEWS - Date: . Fixed bug GH-11368 (Date modify returns invalid datetime). (Derick) +- DOM: + . Fixed bug GH-11625 (DOMElement::replaceWith() doesn't replace node with + DOMDocumentFragment but just deletes node or causes wrapping <> + depending on libxml2 version). (nielsdos) + - Fileinfo: . Fixed bug GH-11298 (finfo returns wrong mime type for xz files). (Anatol) diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c index a9dfda59622b7..dba580ead7cd7 100644 --- a/ext/dom/parentnode.c +++ b/ext/dom/parentnode.c @@ -183,7 +183,15 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod goto err; } - if (newNode->parent != NULL) { + if (newNode->type == XML_DOCUMENT_FRAG_NODE) { + /* Unpack document fragment nodes, the behaviour differs for different libxml2 versions. */ + newNode = newNode->children; + if (UNEXPECTED(newNode == NULL)) { + /* No nodes to add, nothing to do here */ + continue; + } + xmlUnlinkNode(newNode); + } else if (newNode->parent != NULL) { xmlUnlinkNode(newNode); } @@ -370,7 +378,7 @@ static void dom_pre_insert(xmlNodePtr insertion_point, xmlNodePtr parentNode, xm insertion_point->prev->next = newchild; newchild->prev = insertion_point->prev; } - insertion_point->prev = newchild; + insertion_point->prev = fragment->last; if (parentNode->children == insertion_point) { parentNode->children = newchild; } @@ -555,14 +563,14 @@ void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc) xmlNodePtr newchild = fragment->children; xmlDocPtr doc = parentNode->doc; + /* Unlink and free it unless it became a part of the fragment. */ + if (child->parent != fragment) { + xmlUnlinkNode(child); + } + if (newchild) { xmlNodePtr last = fragment->last; - /* Unlink and free it unless it became a part of the fragment. */ - if (child->parent != fragment) { - xmlUnlinkNode(child); - } - dom_pre_insert(insertion_point, parentNode, newchild, fragment); dom_fragment_assign_parent_node(parentNode, fragment); diff --git a/ext/dom/tests/gh11625.phpt b/ext/dom/tests/gh11625.phpt new file mode 100644 index 0000000000000..40e34d32b808d --- /dev/null +++ b/ext/dom/tests/gh11625.phpt @@ -0,0 +1,72 @@ +--TEST-- +GH-11625 (DOMElement::replaceWith() doesn't replace node with DOMDocumentFragment but just deletes node or causes wrapping <> depending on libxml2 version) +--EXTENSIONS-- +dom +--FILE-- + +
+ + XML; + + $dom = new DOMDocument(); + $dom->loadXML($html); + + $divs = iterator_to_array($dom->getElementsByTagName('div')->getIterator()); + $i = 0; + foreach ($divs as $div) { + $mutator($dom, $div, $i); + echo $dom->saveHTML(); + $i++; + } +} + +echo "--- Single replacement ---\n"; + +test(function($dom, $div, $i) { + $fragment = $dom->createDocumentFragment(); + $fragment->appendXML("

Hi $i!

"); + $div->replaceWith($fragment); +}); + +echo "--- Multiple replacement ---\n"; + +test(function($dom, $div, $i) { + $fragment = $dom->createDocumentFragment(); + $fragment->appendXML("

Hi $i!

"); + $div->replaceWith($fragment, $dom->createElement('x'), "hello"); +}); + +echo "--- Empty fragment replacement ---\n"; + +test(function($dom, $div, $i) { + $fragment = $dom->createDocumentFragment(); + $div->replaceWith($fragment); +}); + +?> +--EXPECT-- +--- Single replacement --- + +

Hi 0!

+ + +

Hi 0!

Hi 1!

+ +--- Multiple replacement --- + +

Hi 0!

hello
+ + +

Hi 0!

hello

Hi 1!

hello + +--- Empty fragment replacement --- + +
+ + + + From 06d87e4c14b1ad582dbef50c926162e0e22a90aa Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:49:54 +0200 Subject: [PATCH 2/3] Fix GH-11629: bug77020.phpt tries to send mail Closes GH-11636. --- ext/imap/tests/bug77020.phpt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/imap/tests/bug77020.phpt b/ext/imap/tests/bug77020.phpt index f605267e45e4b..1da0cd8cf5ce3 100644 --- a/ext/imap/tests/bug77020.phpt +++ b/ext/imap/tests/bug77020.phpt @@ -2,8 +2,13 @@ Bug #77020 (null pointer dereference in imap_mail) --EXTENSIONS-- imap +--INI-- +sendmail_path="echo >/dev/null" --FILE-- From c408a8b604a1aaf9e2d5e661a72526037b1a71fa Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:34:52 +0200 Subject: [PATCH 3/3] Fix GH-11630: proc_nice_basic.phpt only works at certain nice levels Closes GH-11635. --- ext/standard/tests/general_functions/proc_nice_basic.phpt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/standard/tests/general_functions/proc_nice_basic.phpt b/ext/standard/tests/general_functions/proc_nice_basic.phpt index 17bca0f7b75fe..55622eaf46b48 100644 --- a/ext/standard/tests/general_functions/proc_nice_basic.phpt +++ b/ext/standard/tests/general_functions/proc_nice_basic.phpt @@ -30,7 +30,9 @@ if ($exit_code !== 0) { $niceBefore = getNice($pid); proc_nice($delta); $niceAfter = getNice($pid); - var_dump($niceBefore == ($niceAfter - $delta)); + // The maximum niceness level is 19, if the process is already running at a high niceness, it cannot be increased. + // Decreasing is only possible for superusers. + var_dump(min($niceBefore + $delta, 19) == $niceAfter); ?> --EXPECT-- bool(true)