From f397ad0d698c430dc8fe1729f9b96783d00a7b31 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] 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. --- ext/dom/parentnode.c | 22 ++++++++---- ext/dom/tests/gh11625.phpt | 72 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 ext/dom/tests/gh11625.phpt diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c index a9dfda59622b..dba580ead7cd 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 000000000000..40e34d32b808 --- /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-- + +
+ +Hi 0!
+ +Hi 0!
Hi 1!
+ +Hi 0!
Hi 0!
Hi 1!