diff --git a/ext/dom/element.c b/ext/dom/element.c
index 4835c7fad91ad..b36c5b78dc8db 100644
--- a/ext/dom/element.c
+++ b/ext/dom/element.c
@@ -20,6 +20,7 @@
#endif
#include "php.h"
+
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "php_dom.h"
@@ -74,14 +75,14 @@ PHP_METHOD(DOMElement, __construct)
RETURN_THROWS();
}
} else {
- /* If you don't pass a namespace uri, then you can't set a prefix */
- localname = (char *) xmlSplitQName2((xmlChar *) name, (xmlChar **) &prefix);
- if (prefix != NULL) {
+ /* If you don't pass a namespace uri, then you can't set a prefix */
+ localname = (char *) xmlSplitQName2((xmlChar *) name, (xmlChar **) &prefix);
+ if (prefix != NULL) {
xmlFree(localname);
xmlFree(prefix);
- php_dom_throw_error(NAMESPACE_ERR, 1);
- RETURN_THROWS();
- }
+ php_dom_throw_error(NAMESPACE_ERR, 1);
+ RETURN_THROWS();
+ }
nodep = xmlNewNode(NULL, (xmlChar *) name);
}
@@ -152,8 +153,8 @@ int dom_element_schema_type_info_read(dom_object *obj, zval *retval)
static xmlNodePtr dom_get_dom1_attribute(xmlNodePtr elem, xmlChar *name) /* {{{ */
{
- int len;
- const xmlChar *nqname;
+ int len;
+ const xmlChar *nqname;
nqname = xmlSplitQName3(name, &len);
if (nqname != NULL) {
@@ -570,9 +571,9 @@ PHP_METHOD(DOMElement, getAttributeNS)
static xmlNsPtr _dom_new_reconNs(xmlDocPtr doc, xmlNodePtr tree, xmlNsPtr ns) /* {{{ */
{
- xmlNsPtr def;
- xmlChar prefix[50];
- int counter = 1;
+ xmlNsPtr def;
+ xmlChar prefix[50];
+ int counter = 1;
if ((tree == NULL) || (ns == NULL) || (ns->type != XML_NAMESPACE_DECL)) {
return NULL;
@@ -883,12 +884,12 @@ PHP_METHOD(DOMElement, setAttributeNodeNS)
RETURN_FALSE;
}
- nsp = attrp->ns;
- if (nsp != NULL) {
- existattrp = xmlHasNsProp(nodep, nsp->href, attrp->name);
- } else {
- existattrp = xmlHasProp(nodep, attrp->name);
- }
+ nsp = attrp->ns;
+ if (nsp != NULL) {
+ existattrp = xmlHasNsProp(nodep, nsp->href, attrp->name);
+ } else {
+ existattrp = xmlHasProp(nodep, attrp->name);
+ }
if (existattrp != NULL && existattrp->type != XML_ATTRIBUTE_DECL) {
if ((oldobj = php_dom_object_get_data((xmlNodePtr) existattrp)) != NULL &&
@@ -1252,7 +1253,9 @@ PHP_METHOD(DOMElement, replaceWith)
DOM_GET_OBJ(context, id, xmlNodePtr, intern);
dom_parent_node_after(intern, args, argc);
- dom_child_node_remove(intern);
+ if (!dom_node_is_argument(intern, args, argc)) {
+ dom_child_node_remove(intern);
+ }
}
/* }}} end DOMElement::prepend */
diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c
index 375c692dcad85..5c22d6636be48 100644
--- a/ext/dom/parentnode.c
+++ b/ext/dom/parentnode.c
@@ -21,6 +21,7 @@
#endif
#include "php.h"
+
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "php_dom.h"
@@ -181,6 +182,10 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod
return NULL;
}
+ if (nodesc > 1) {
+ newNode = xmlCopyNode(newNode, 1);
+ }
+
if (!xmlAddChild(fragment, newNode)) {
xmlFree(fragment);
@@ -302,7 +307,9 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
{
xmlNode *prevsib = dom_object_get_node(context);
xmlNodePtr newchild, parentNode;
- xmlNode *fragment;
+ xmlNode *fragment, *nextsib;
+ xmlDoc *doc;
+ bool afterlastchild;
int stricterror = dom_get_strict_error(context->document);
@@ -311,7 +318,10 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
return;
}
+ doc = prevsib->doc;
parentNode = prevsib->parent;
+ nextsib = prevsib->next;
+ afterlastchild = (nextsib == NULL);
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
if (fragment == NULL) {
@@ -321,13 +331,31 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
newchild = fragment->children;
if (newchild) {
- fragment->last->next = prevsib->next;
- prevsib->next = newchild;
+ if (!parentNode->children) {
+ prevsib = nextsib = NULL;
+ } else if (afterlastchild) {
+ prevsib = parentNode->children == prevsib ? prevsib : parentNode->last;
+ } else {
+ prevsib = parentNode->children == prevsib ? prevsib : NULL;
+ }
- newchild->prev = prevsib;
+ if (prevsib) {
+ fragment->last->next = prevsib->next;
+ if (prevsib->next) {
+ prevsib->next->prev = fragment->last;
+ }
+ prevsib->next = newchild;
+ } else {
+ parentNode->children = newchild;
+ if (nextsib) {
+ fragment->last->next = nextsib;
+ nextsib->prev = fragment->last;
+ }
+ }
+ newchild->prev = prevsib;
dom_fragment_assign_parent_node(parentNode, fragment);
- dom_reconcile_ns(prevsib->doc, newchild);
+ dom_reconcile_ns(doc, newchild);
}
xmlFree(fragment);
@@ -337,10 +365,15 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
{
xmlNode *nextsib = dom_object_get_node(context);
xmlNodePtr newchild, prevsib, parentNode;
- xmlNode *fragment;
+ xmlNode *fragment, *afternextsib;
+ xmlDoc *doc;
+ bool beforefirstchild;
+ doc = nextsib->doc;
prevsib = nextsib->prev;
+ afternextsib = nextsib->next;
parentNode = nextsib->parent;
+ beforefirstchild = !prevsib;
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
if (fragment == NULL) {
@@ -350,24 +383,56 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
newchild = fragment->children;
if (newchild) {
+ if (!parentNode->children) {
+ nextsib = NULL;
+ } else if (beforefirstchild) {
+ nextsib = parentNode->children == nextsib ? nextsib : afternextsib;
+ } else {
+ nextsib = parentNode->children == prevsib ? prevsib->next : nextsib;
+ }
+
if (parentNode->children == nextsib) {
parentNode->children = newchild;
} else {
prevsib->next = newchild;
}
+
fragment->last->next = nextsib;
- nextsib->prev = fragment->last;
+ if (nextsib) {
+ nextsib->prev = fragment->last;
+ }
newchild->prev = prevsib;
-
dom_fragment_assign_parent_node(parentNode, fragment);
-
- dom_reconcile_ns(nextsib->doc, newchild);
+ dom_reconcile_ns(doc, newchild);
}
xmlFree(fragment);
}
+bool dom_node_is_argument(dom_object *context, zval *nodes, int nodesc)
+{
+ int i;
+ xmlNode *newNode;
+ zend_class_entry *ce;
+ dom_object *newNodeObj;
+ xmlNode *child = dom_object_get_node(context);
+
+ for (i = 0; i < nodesc; i++) {
+ if (Z_TYPE(nodes[i]) == IS_OBJECT) {
+ ce = Z_OBJCE(nodes[i]);
+ if (instanceof_function(ce, dom_node_class_entry)) {
+ newNodeObj = Z_DOMOBJ_P(&nodes[i]);
+ newNode = dom_object_get_node(newNodeObj);
+ if (child == newNode) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
void dom_child_node_remove(dom_object *context)
{
xmlNode *child = dom_object_get_node(context);
diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h
index 24e1ea646a05d..a39ce6914e3b4 100644
--- a/ext/dom/php_dom.h
+++ b/ext/dom/php_dom.h
@@ -130,6 +130,7 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc);
void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc);
void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc);
void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc);
+bool dom_node_is_argument(dom_object *context, zval *nodes, int nodesc);
void dom_child_node_remove(dom_object *context);
#define REGISTER_DOM_CLASS(ce, name, parent_ce, funcs, entry) \
diff --git a/ext/dom/tests/bug80602.phpt b/ext/dom/tests/bug80602.phpt
new file mode 100644
index 0000000000000..064e856b60e64
--- /dev/null
+++ b/ext/dom/tests/bug80602.phpt
@@ -0,0 +1,181 @@
+--TEST--
+Bug #80602 (Segfault when using DOMChildNode::before())
+--SKIPIF--
+
+--FILE--
+loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($doc->documentElement->firstChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target, $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($doc->documentElement->lastChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($target, $doc->documentElement->firstChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($doc->documentElement->firstChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before('bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before('bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target, 'bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before('bar', $target, 'baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before('bar', 'baz', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($target, 'bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before('bar', $target, 'baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before('bar', 'baz', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before('bar', $target, $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target, 'bar', $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target, $doc->documentElement->lastChild, 'bar');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before('bar', $doc->documentElement->firstChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($doc->documentElement->firstChild, 'bar', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($doc->documentElement->firstChild, $target, 'bar');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+?>
+--EXPECTF--
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+barbazfoo
+foobarbaz
+foobarbaz
+barfoobaz
+barbazfoo
+foobarbaz
+foobarbaz
+foobarbaz
+barfoo
+foobar
+foobar
+barfoo
+foobar
+foobar
+
diff --git a/ext/dom/tests/bug80602_2.phpt b/ext/dom/tests/bug80602_2.phpt
new file mode 100644
index 0000000000000..9d697c64172a0
--- /dev/null
+++ b/ext/dom/tests/bug80602_2.phpt
@@ -0,0 +1,180 @@
+--TEST--
+Bug #80602 (Segfault when using DOMChildNode::after())
+--SKIPIF--
+
+--FILE--
+loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($doc->documentElement->firstChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target, $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($doc->documentElement->lastChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($target, $doc->documentElement->firstChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($doc->documentElement->firstChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after('bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after('bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target, 'bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after('bar', $target, 'baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after('bar', 'baz', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($target, 'bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after('bar', $target, 'baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after('bar', 'baz', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after('bar', $target, $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target, 'bar', $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target, $doc->documentElement->lastChild, 'bar');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after('bar', $doc->documentElement->firstChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($doc->documentElement->firstChild, 'bar', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($doc->documentElement->firstChild, $target, 'bar');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+?>
+--EXPECTF--
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+foobarbaz
+foobarbaz
+foobarbaz
+barfoobaz
+barbazfoo
+foobarbaz
+foobarbaz
+foobarbaz
+barfoo
+foobar
+foobar
+barfoo
+foobar
+foobar
diff --git a/ext/dom/tests/bug81642.phpt b/ext/dom/tests/bug81642.phpt
new file mode 100644
index 0000000000000..b1581cfdb14ba
--- /dev/null
+++ b/ext/dom/tests/bug81642.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Bug #81642 DOMChildNode::replaceWith() bug when replacing a node with itself.
+--SKIPIF--
+
+--FILE--
+createElement("head");
+$doc->appendChild($headNode);
+$titleNode = $doc->createElement("title");
+$headNode->appendChild($titleNode);
+$titleNode->replaceWith($titleNode);
+echo $doc->saveXML().PHP_EOL;
+
+$doc = new DOMDocument;
+$headNode = $doc->createElement("head");
+$doc->appendChild($headNode);
+$titleNode = $doc->createElement("title");
+$headNode->appendChild($titleNode);
+$titleNode->replaceWith($titleNode, 'foo');
+echo $doc->saveXML().PHP_EOL;
+
+?>
+--EXPECT--
+
+
+
+
+foo
+
diff --git a/ext/dom/tests/gh9142.phpt b/ext/dom/tests/gh9142.phpt
new file mode 100644
index 0000000000000..bae8cc2f3ff68
--- /dev/null
+++ b/ext/dom/tests/gh9142.phpt
@@ -0,0 +1,40 @@
+--TEST--
+GitHub #9142 (DOMChildNode replaceWith() double-free error when replacing elements not separated by any whitespace)
+--SKIPIF--
+
+--FILE--
+One Two'; // Works fine
+($domA = new DOMDocument('1.0', 'UTF-8'))->loadHTML($a);
+foreach ((new DOMXPath($domA))->query('//var') as $var) {
+ $var->replaceWith($domA->createElement('p', $var->nodeValue));
+}
+var_dump($domA->saveHTML());
+
+
+$b = 'OneTwo'; // Causes a 'double free' error
+($domB = new DOMDocument('1.0', 'UTF-8'))->loadHTML($b);
+foreach ((new DOMXPath($domB))->query('//var') as $var) {
+ $var->replaceWith($domB->createElement('p', $var->nodeValue));
+}
+var_dump($domB->saveHTML());
+
+
+$document = 'OneTwo';
+($dom = new DOMDocument('1.0', 'UTF-8'))->loadHTML($document);
+foreach ((new DOMXPath($dom))->query('//var') as $var) {
+ $var->after($dom->createElement('p', $var->nodeValue));
+ $var->remove();
+}
+var_dump($dom->saveHTML());
+?>
+--EXPECT--
+string(155) "
+One
Two
+"
+string(154) "
+One
Two
+"
+string(154) "
+One
Two
+"