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-- + +</head> + +<?xml version="1.0"?> +<head><title/>foo</head> + 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-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +$a = '<var>One</var> <var>Two</var>'; // 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 = '<var>One</var><var>Two</var>'; // 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 = '<var>One</var><var>Two</var>'; +($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) "<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> +<html><body><p>One</p> <p>Two</p></body></html> +" +string(154) "<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> +<html><body><p>One</p><p>Two</p></body></html> +" +string(154) "<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> +<html><body><p>One</p><p>Two</p></body></html> +"