diff --git a/ext/dom/node.c b/ext/dom/node.c index b291ccc99a308..bcf4ee487d38d 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -943,12 +943,20 @@ PHP_METHOD(DOMNode, insertBefore) return; } } + new_child = xmlAddPrevSibling(refp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } } else if (child->type == XML_DOCUMENT_FRAG_NODE) { + xmlNodePtr last = child->last; new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern, childobj); - } - - if (new_child == NULL) { + dom_reconcile_ns_list(parentp->doc, new_child, last); + } else { new_child = xmlAddPrevSibling(refp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } + dom_reconcile_ns(parentp->doc, new_child); } } else { if (child->parent != NULL){ @@ -985,23 +993,28 @@ PHP_METHOD(DOMNode, insertBefore) return; } } + new_child = xmlAddChild(parentp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } } else if (child->type == XML_DOCUMENT_FRAG_NODE) { + xmlNodePtr last = child->last; new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern, childobj); - } - if (new_child == NULL) { + dom_reconcile_ns_list(parentp->doc, new_child, last); + } else { new_child = xmlAddChild(parentp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } + dom_reconcile_ns(parentp->doc, new_child); } } - if (NULL == new_child) { - zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode"); - RETURN_THROWS(); - } - - dom_reconcile_ns(parentp->doc, new_child); - DOM_RET_OBJ(new_child, &ret, intern); - + return; +cannot_add: + zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode"); + RETURN_THROWS(); } /* }}} end dom_node_insert_before */ @@ -1066,9 +1079,10 @@ PHP_METHOD(DOMNode, replaceChild) xmlUnlinkNode(oldchild); + xmlNodePtr last = newchild->last; newchild = _php_dom_insert_fragment(nodep, prevsib, nextsib, newchild, intern, newchildobj); if (newchild) { - dom_reconcile_ns(nodep->doc, newchild); + dom_reconcile_ns_list(nodep->doc, newchild, last); } } else if (oldchild != newchild) { xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc); @@ -1215,22 +1229,28 @@ PHP_METHOD(DOMNode, appendChild) php_libxml_node_free_resource((xmlNodePtr) lastattr); } } + new_child = xmlAddChild(nodep, child); + if (UNEXPECTED(new_child == NULL)) { + goto cannot_add; + } } else if (child->type == XML_DOCUMENT_FRAG_NODE) { + xmlNodePtr last = child->last; new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern, childobj); - } - - if (new_child == NULL) { + dom_reconcile_ns_list(nodep->doc, new_child, last); + } else { new_child = xmlAddChild(nodep, child); - if (new_child == NULL) { - // TODO Convert to Error? - php_error_docref(NULL, E_WARNING, "Couldn't append node"); - RETURN_FALSE; + if (UNEXPECTED(new_child == NULL)) { + goto cannot_add; } + dom_reconcile_ns(nodep->doc, new_child); } - dom_reconcile_ns(nodep->doc, new_child); - DOM_RET_OBJ(new_child, &ret, intern); + return; +cannot_add: + // TODO Convert to Error? + php_error_docref(NULL, E_WARNING, "Couldn't append node"); + RETURN_FALSE; } /* }}} end dom_node_append_child */ diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c index c99a2a5a6622a..b7e8e3ba774e3 100644 --- a/ext/dom/parentnode.c +++ b/ext/dom/parentnode.c @@ -298,13 +298,14 @@ void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc) parentNode->children = newchild; } - parentNode->last = fragment->last; + xmlNodePtr last = fragment->last; + parentNode->last = last; newchild->prev = prevsib; dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(parentNode->doc, newchild); + dom_reconcile_ns_list(parentNode->doc, newchild, last); } xmlFree(fragment); @@ -335,13 +336,14 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc) nextsib = parentNode->children; if (newchild) { + xmlNodePtr last = fragment->last; parentNode->children = newchild; fragment->last->next = nextsib; - nextsib->prev = fragment->last; + nextsib->prev = last; dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(parentNode->doc, newchild); + dom_reconcile_ns_list(parentNode->doc, newchild, last); } xmlFree(fragment); @@ -414,11 +416,13 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc) newchild = fragment->children; if (newchild) { + xmlNodePtr last = fragment->last; + /* Step 5: place fragment into the parent before viable_next_sibling */ dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment); dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(doc, newchild); + dom_reconcile_ns_list(doc, newchild, last); } xmlFree(fragment); @@ -463,6 +467,8 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc) newchild = fragment->children; if (newchild) { + xmlNodePtr last = fragment->last; + /* Step 5: if viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling */ if (!viable_previous_sibling) { viable_previous_sibling = parentNode->children; @@ -473,7 +479,7 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc) dom_pre_insert(viable_previous_sibling, parentNode, newchild, fragment); dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(doc, newchild); + dom_reconcile_ns_list(doc, newchild, last); } xmlFree(fragment); diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 1883767d2e48b..df20093221f16 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -1385,38 +1385,73 @@ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) { } /* }}} end dom_set_old_ns */ -void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */ +static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep) { xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL; - if (nodep->type == XML_ELEMENT_NODE) { - /* Following if block primarily used for inserting nodes created via createElementNS */ - if (nodep->nsDef != NULL) { - curns = nodep->nsDef; - while (curns) { - nsdftptr = curns->next; - if (curns->href != NULL) { - if((nsptr = xmlSearchNsByHref(doc, nodep->parent, curns->href)) && - (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) { - curns->next = NULL; - if (prevns == NULL) { - nodep->nsDef = nsdftptr; - } else { - prevns->next = nsdftptr; - } - dom_set_old_ns(doc, curns); - curns = prevns; + /* Following if block primarily used for inserting nodes created via createElementNS */ + if (nodep->nsDef != NULL) { + curns = nodep->nsDef; + while (curns) { + nsdftptr = curns->next; + if (curns->href != NULL) { + if((nsptr = xmlSearchNsByHref(doc, nodep->parent, curns->href)) && + (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) { + curns->next = NULL; + if (prevns == NULL) { + nodep->nsDef = nsdftptr; + } else { + prevns->next = nsdftptr; } + dom_set_old_ns(doc, curns); + curns = prevns; } - prevns = curns; - curns = nsdftptr; } + prevns = curns; + curns = nsdftptr; } + } +} + +void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */ +{ + if (nodep->type == XML_ELEMENT_NODE) { + dom_reconcile_ns_internal(doc, nodep); xmlReconciliateNs(doc, nodep); } } /* }}} */ +static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last) +{ + ZEND_ASSERT(nodep != NULL); + while (true) { + if (nodep->type == XML_ELEMENT_NODE) { + dom_reconcile_ns_internal(doc, nodep); + if (nodep->children) { + dom_reconcile_ns_list_internal(doc, nodep->children, nodep->last /* process the whole children list */); + } + } + if (nodep == last) { + break; + } + nodep = nodep->next; + } +} + +void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last) +{ + dom_reconcile_ns_list_internal(doc, nodep, last); + /* Outside of the recursion above because xmlReconciliateNs() performs its own recursion. */ + while (true) { + xmlReconciliateNs(doc, nodep); + if (nodep == last) { + break; + } + nodep = nodep->next; + } +} + /* http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index fdfdd4e7a31ca..924d1397ca73a 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -110,6 +110,7 @@ int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, i xmlNsPtr dom_get_ns(xmlNodePtr node, char *uri, int *errorcode, char *prefix); void dom_set_old_ns(xmlDoc *doc, xmlNs *ns); void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep); +void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last); xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName); void dom_normalize (xmlNodePtr nodep); xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *local, int *cur, int index); diff --git a/ext/dom/tests/bug67440.phpt b/ext/dom/tests/bug67440.phpt new file mode 100644 index 0000000000000..3e30f69b9ae4d --- /dev/null +++ b/ext/dom/tests/bug67440.phpt @@ -0,0 +1,151 @@ +--TEST-- +Bug #67440 (append_node of a DOMDocumentFragment does not reconcile namespaces) +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + $fragment = $document->createDocumentFragment(); + $fragment->appendChild($document->createTextNode("\n")); + $fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '1')); + $fragment->appendChild($document->createTextNode("\n")); + $fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '2')); + $fragment->appendChild($document->createTextNode("\n")); + return array($document, $fragment); +} + +function case1($method) { + list($document, $fragment) = createDocument(); + $document->documentElement->{$method}($fragment); + echo $document->saveXML(); +} + +function case2($method) { + list($document, $fragment) = createDocument(); + $childNodes = iterator_to_array($fragment->childNodes); + foreach ($childNodes as $childNode) { + $document->documentElement->{$method}($childNode); + } + echo $document->saveXML(); +} + +function case3($method) { + list($document, $fragment) = createDocument(); + $fragment->removeChild($fragment->firstChild); + $document->documentElement->{$method}($fragment); + echo $document->saveXML(); +} + +function case4($method) { + list($document, $fragment) = createDocument(); + $fragment->childNodes[1]->appendChild($document->createElementNS('http://example/ns2', 'myns2:childNode', '3')); + $document->documentElement->{$method}($fragment); + echo $document->saveXML(); +} + +echo "== appendChild ==\n"; +echo "-- fragment to document element --\n"; case1('appendChild'); echo "\n"; +echo "-- children manually document element --\n"; case2('appendChild'); echo "\n"; +echo "-- fragment to document where first element is not a text node --\n"; case3('appendChild'); echo "\n"; +echo "-- fragment with namespace declarations in children --\n"; case4('appendChild'); echo "\n"; + +echo "== insertBefore ==\n"; +echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n"; +echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n"; +echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n"; +echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n"; + +echo "== insertAfter ==\n"; +echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n"; +echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n"; +echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n"; +echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n"; + +?> +--EXPECT-- +== appendChild == +-- fragment to document element -- + + +1 +2 + + +-- children manually document element -- + + +1 +2 + + +-- fragment to document where first element is not a text node -- + +1 +2 + + +-- fragment with namespace declarations in children -- + + +13 +2 + + +== insertBefore == +-- fragment to document element -- + + +1 +2 + + +-- children manually document element -- + + +1 +2 + + +-- fragment to document where first element is not a text node -- + +1 +2 + + +-- fragment with namespace declarations in children -- + + +13 +2 + + +== insertAfter == +-- fragment to document element -- + + +1 +2 + + +-- children manually document element -- + + +1 +2 + + +-- fragment to document where first element is not a text node -- + +1 +2 + + +-- fragment with namespace declarations in children -- + + +13 +2 +