Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions ext/dom/document.c
Original file line number Diff line number Diff line change
Expand Up @@ -1076,10 +1076,62 @@ static void php_dom_transfer_document_ref(xmlNodePtr node, php_libxml_ref_obj *n
}
}

/* Workaround for bug that was fixed in https://github.com/GNOME/libxml2/commit/4bc3ebf3eaba352fbbce2ef70ad00a3c7752478a
* and https://github.com/GNOME/libxml2/commit/bc7ab5a2e61e4b36accf6803c5b0e245c11154b1 */
#if LIBXML_VERSION < 21300
static xmlChar *libxml_copy_dicted_string(xmlDictPtr src_dict, xmlDictPtr dst_dict, xmlChar *str)
{
if (str == NULL) {
return NULL;
}
if (xmlDictOwns(src_dict, str) == 1) {
if (dst_dict == NULL) {
return xmlStrdup(str);
}
return BAD_CAST xmlDictLookup(dst_dict, str, -1);
}
return str;
}

static void libxml_fixup_name_and_content(xmlDocPtr src_doc, xmlDocPtr dst_doc, xmlNodePtr node)
{
if (node->type == XML_ENTITY_REF_NODE) {
node->children = NULL; /* Break link with original document. */
}
if (src_doc != NULL && src_doc->dict != NULL) {
ZEND_ASSERT(dst_doc != src_doc);
node->name = libxml_copy_dicted_string(src_doc->dict, dst_doc->dict, BAD_CAST node->name);
node->content = libxml_copy_dicted_string(src_doc->dict, NULL, node->content);
}
}

static void libxml_fixup_name_and_content_outer(xmlDocPtr src_doc, xmlDocPtr dst_doc, xmlNodePtr node)
{
libxml_fixup_name_and_content(src_doc, dst_doc, node);

if (node->type == XML_ELEMENT_NODE) {
for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) {
libxml_fixup_name_and_content(src_doc, dst_doc, (xmlNodePtr) attr);
for (xmlNodePtr attr_child = attr->children; attr_child != NULL; attr_child = attr_child->next) {
libxml_fixup_name_and_content(src_doc, dst_doc, attr_child);
}
}
}

if (node->type == XML_ELEMENT_NODE || node->type == XML_ATTRIBUTE_NODE) {
for (xmlNodePtr child = node->children; child != NULL; child = child->next) {
libxml_fixup_name_and_content_outer(src_doc, dst_doc, child);
}
}
}
#endif

bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, xmlDocPtr new_document)
{
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
if (nodep->doc != new_document) {
xmlDocPtr old_doc = nodep->doc;

php_libxml_invalidate_node_list_cache_from_doc(old_doc);
if (old_doc != new_document) {
php_libxml_invalidate_node_list_cache(dom_object_new_document->document);

/* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
Expand All @@ -1089,6 +1141,11 @@ bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, x
return false;
}

#if LIBXML_VERSION < 21300
/* Must be first before transferring the ref to ensure the old document dictionary stays alive. */
libxml_fixup_name_and_content_outer(old_doc, new_document, nodep);
#endif

php_dom_transfer_document_ref(nodep, dom_object_new_document->document);
} else {
xmlUnlinkNode(nodep);
Expand Down
30 changes: 30 additions & 0 deletions ext/dom/tests/gh19612.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
GH-19612 (Mitigate libxml2 tree dictionary bug)
--EXTENSIONS--
dom
--FILE--
<?php
$xml = new DOMDocument;
$xml->loadXML(<<<XML
<!DOCTYPE root [
<!ENTITY foo "foo">
]>
<root><el x="&foo;"/></root>
XML);
$html = new DOMDocument;
$html->loadHTML('<p>foo</p>', LIBXML_NOERROR);
$p = $html->documentElement->firstChild->firstChild;
$p->appendChild($html->adoptNode($xml->documentElement->firstElementChild->cloneNode(true)));

echo $html->saveXML();
echo $xml->saveXML();
?>
--EXPECT--
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p>foo<el x="&foo;"/></p></body></html>
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY foo "foo">
]>
<root><el x="&foo;"/></root>
Loading