Skip to content

Commit 23f7002

Browse files
committed
Fix bug #81642: DOMChildNode::replaceWith() bug when replacing a node with itself
Closes GH-11363.
1 parent b1d8e24 commit 23f7002

File tree

5 files changed

+112
-14
lines changed

5 files changed

+112
-14
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ PHP NEWS
2323
xpath query). (nielsdos)
2424
. Fixed bug #67440 (append_node of a DOMDocumentFragment does not reconcile
2525
namespaces). (nielsdos)
26+
. Fixed bug #81642 (DOMChildNode::replaceWith() bug when replacing a node
27+
with itself). (nielsdos)
2628

2729
- Opcache:
2830
. Fix allocation loop in zend_shared_alloc_startup(). (nielsdos)

ext/dom/element.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,7 +1234,7 @@ PHP_METHOD(DOMElement, prepend)
12341234
}
12351235
/* }}} end DOMElement::prepend */
12361236

1237-
/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend
1237+
/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
12381238
Since: DOM Living Standard (DOM4)
12391239
*/
12401240
PHP_METHOD(DOMElement, replaceWith)
@@ -1251,8 +1251,7 @@ PHP_METHOD(DOMElement, replaceWith)
12511251
id = ZEND_THIS;
12521252
DOM_GET_OBJ(context, id, xmlNodePtr, intern);
12531253

1254-
dom_parent_node_after(intern, args, argc);
1255-
dom_child_node_remove(intern);
1254+
dom_child_replace_with(intern, args, argc);
12561255
}
12571256
/* }}} end DOMElement::prepend */
12581257

ext/dom/parentnode.c

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -485,35 +485,45 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
485485
xmlFree(fragment);
486486
}
487487

488-
void dom_child_node_remove(dom_object *context)
488+
static zend_result dom_child_removal_preconditions(const xmlNodePtr child, int stricterror)
489489
{
490-
xmlNode *child = dom_object_get_node(context);
491-
xmlNodePtr children;
492-
int stricterror;
493-
494-
stricterror = dom_get_strict_error(context->document);
495-
496490
if (dom_node_is_read_only(child) == SUCCESS ||
497491
(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
498492
php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
499-
return;
493+
return FAILURE;
500494
}
501495

502496
if (!child->parent) {
503497
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
504-
return;
498+
return FAILURE;
505499
}
506500

507501
if (dom_node_children_valid(child->parent) == FAILURE) {
508-
return;
502+
return FAILURE;
509503
}
510504

511-
children = child->parent->children;
505+
xmlNodePtr children = child->parent->children;
512506
if (!children) {
513507
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
508+
return FAILURE;
509+
}
510+
511+
return SUCCESS;
512+
}
513+
514+
void dom_child_node_remove(dom_object *context)
515+
{
516+
xmlNode *child = dom_object_get_node(context);
517+
xmlNodePtr children;
518+
int stricterror;
519+
520+
stricterror = dom_get_strict_error(context->document);
521+
522+
if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
514523
return;
515524
}
516525

526+
children = child->parent->children;
517527
while (children) {
518528
if (children == child) {
519529
xmlUnlinkNode(child);
@@ -525,4 +535,41 @@ void dom_child_node_remove(dom_object *context)
525535
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
526536
}
527537

538+
void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc)
539+
{
540+
xmlNodePtr child = dom_object_get_node(context);
541+
xmlNodePtr parentNode = child->parent;
542+
543+
int stricterror = dom_get_strict_error(context->document);
544+
if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
545+
return;
546+
}
547+
548+
xmlNodePtr insertion_point = child->next;
549+
550+
xmlNodePtr fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
551+
if (UNEXPECTED(fragment == NULL)) {
552+
return;
553+
}
554+
555+
xmlNodePtr newchild = fragment->children;
556+
xmlDocPtr doc = parentNode->doc;
557+
558+
if (newchild) {
559+
xmlNodePtr last = fragment->last;
560+
561+
/* Unlink and free it unless it became a part of the fragment. */
562+
if (child->parent != fragment) {
563+
xmlUnlinkNode(child);
564+
}
565+
566+
dom_pre_insert(insertion_point, parentNode, newchild, fragment);
567+
568+
dom_fragment_assign_parent_node(parentNode, fragment);
569+
dom_reconcile_ns_list(doc, newchild, last);
570+
}
571+
572+
xmlFree(fragment);
573+
}
574+
528575
#endif

ext/dom/php_dom.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc);
132132
void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc);
133133
void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc);
134134
void dom_child_node_remove(dom_object *context);
135+
void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc);
135136

136137
#define DOM_GET_OBJ(__ptr, __id, __prtype, __intern) { \
137138
__intern = Z_DOMOBJ_P(__id); \

ext/dom/tests/bug81642.phpt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
--TEST--
2+
Bug #81642 (DOMChildNode::replaceWith() bug when replacing a node with itself)
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
// Replace with itself
9+
$doc = new DOMDocument();
10+
$doc->appendChild($target = $doc->createElement('test'));
11+
$target->replaceWith($target);
12+
var_dump($doc->saveXML());
13+
14+
// Replace with itself + another element
15+
$doc = new DOMDocument();
16+
$doc->appendChild($target = $doc->createElement('test'));
17+
$target->replaceWith($target, $doc->createElement('foo'));
18+
var_dump($doc->saveXML());
19+
20+
// Replace with text node
21+
$doc = new DOMDocument();
22+
$doc->appendChild($target = $doc->createElement('test'));
23+
$target->replaceWith($target, 'foo');
24+
var_dump($doc->saveXML());
25+
26+
// Replace with text node variant 2
27+
$doc = new DOMDocument();
28+
$doc->appendChild($target = $doc->createElement('test'));
29+
$target->replaceWith('bar', $target, 'foo');
30+
var_dump($doc->saveXML());
31+
32+
?>
33+
--EXPECT--
34+
string(30) "<?xml version="1.0"?>
35+
<test/>
36+
"
37+
string(37) "<?xml version="1.0"?>
38+
<test/>
39+
<foo/>
40+
"
41+
string(34) "<?xml version="1.0"?>
42+
<test/>
43+
foo
44+
"
45+
string(38) "<?xml version="1.0"?>
46+
bar
47+
<test/>
48+
foo
49+
"

0 commit comments

Comments
 (0)