Skip to content

Commit 43310cb

Browse files
committed
Template contents leaking into the host element's tree.
Upward walks followed the fragment's parent link to its host, placing template contents in the host document instead of rooting them at the fragment. They now stop at a fragment via php_dom_parent_node(), except the cycle check which the spec resolves against host-including ancestors.
1 parent 09390e7 commit 43310cb

4 files changed

Lines changed: 74 additions & 12 deletions

File tree

ext/dom/node.c

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ bool php_dom_is_node_connected(const xmlNode *node)
6666
if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
6767
return true;
6868
}
69-
node = node->parent;
69+
node = php_dom_parent_node(node);
7070
} while (node != NULL);
7171
return false;
7272
}
@@ -244,7 +244,7 @@ static zend_result dom_node_parent_get(dom_object *obj, zval *retval, bool only_
244244
{
245245
DOM_PROP_NODE(xmlNodePtr, nodep, obj);
246246

247-
xmlNodePtr nodeparent = nodep->parent;
247+
xmlNodePtr nodeparent = php_dom_parent_node(nodep);
248248
if (!nodeparent || (only_element && nodeparent->type != XML_ELEMENT_NODE)) {
249249
ZVAL_NULL(retval);
250250
return SUCCESS;
@@ -2412,7 +2412,7 @@ static bool dom_node_contains(xmlNodePtr thisp, xmlNodePtr otherp)
24122412
if (otherp == thisp) {
24132413
return true;
24142414
}
2415-
otherp = otherp->parent;
2415+
otherp = php_dom_parent_node(otherp);
24162416
} while (otherp);
24172417

24182418
return false;
@@ -2470,7 +2470,7 @@ PHP_METHOD(Dom_Node, contains)
24702470
PHP_METHOD(DOMNode, getRootNode)
24712471
{
24722472
zval *id;
2473-
xmlNodePtr thisp;
2473+
xmlNodePtr thisp, tmp;
24742474
dom_object *intern;
24752475
/* Unused now because we don't support the shadow DOM nodes. Options only influence shadow DOM nodes. */
24762476
zval *options;
@@ -2482,8 +2482,8 @@ PHP_METHOD(DOMNode, getRootNode)
24822482

24832483
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
24842484

2485-
while (thisp->parent) {
2486-
thisp = thisp->parent;
2485+
while ((tmp = php_dom_parent_node(thisp))) {
2486+
thisp = tmp;
24872487
}
24882488

24892489
DOM_RET_OBJ(thisp, intern);
@@ -2559,9 +2559,9 @@ static void dom_node_compare_document_position(INTERNAL_FUNCTION_PARAMETERS, zen
25592559
}
25602560
bool node2_is_ancestor_of_node1 = false;
25612561
size_t node1_depth = 0;
2562-
xmlNodePtr node1_root = node1;
2563-
while (node1_root->parent) {
2564-
node1_root = node1_root->parent;
2562+
xmlNodePtr node1_root = node1, tmp;
2563+
while ((tmp = php_dom_parent_node(node1_root))) {
2564+
node1_root = tmp;
25652565
if (node1_root == node2) {
25662566
node2_is_ancestor_of_node1 = true;
25672567
}
@@ -2570,8 +2570,8 @@ static void dom_node_compare_document_position(INTERNAL_FUNCTION_PARAMETERS, zen
25702570
bool node1_is_ancestor_of_node2 = false;
25712571
size_t node2_depth = 0;
25722572
xmlNodePtr node2_root = node2;
2573-
while (node2_root->parent) {
2574-
node2_root = node2_root->parent;
2573+
while ((tmp = php_dom_parent_node(node2_root))) {
2574+
node2_root = tmp;
25752575
if (node2_root == node1) {
25762576
node1_is_ancestor_of_node2 = true;
25772577
}

ext/dom/parentnode/css_selectors.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ static const xmlNode *dom_query_closest(
200200
ret = current;
201201
break;
202202
}
203-
current = current->parent;
203+
current = php_dom_parent_node(current);
204204
}
205205
}
206206

ext/dom/php_dom.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep);
247247
__ptr = (__prtype)((php_libxml_node_ptr *)__intern->ptr)->node; \
248248
}
249249

250+
static zend_always_inline xmlNodePtr php_dom_parent_node(const xmlNode *nodep)
251+
{
252+
return nodep->type == XML_DOCUMENT_FRAG_NODE ? NULL : nodep->parent;
253+
}
254+
250255
static zend_always_inline bool php_dom_is_cache_tag_stale_from_doc_ptr(const php_libxml_cache_tag *cache_tag, const php_libxml_ref_obj *doc_ptr)
251256
{
252257
ZEND_ASSERT(doc_ptr != NULL);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
GH-23334 (template contents must not be part of the host element's tree)
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
$doc = Dom\HTMLDocument::createFromString(
8+
'<!doctype html><body><div id="host"><template id="tpl"><b id="inner">X</b></template></div>',
9+
LIBXML_NOERROR
10+
);
11+
$tpl = $doc->getElementById('tpl');
12+
$inner = $doc->getElementById('inner');
13+
$frag = $inner->parentNode;
14+
15+
var_dump($frag::class);
16+
var_dump($frag->parentNode);
17+
var_dump($frag->parentElement);
18+
var_dump($frag->getRootNode() === $frag);
19+
var_dump($inner->getRootNode() === $frag);
20+
var_dump($frag->isConnected);
21+
var_dump($inner->isConnected);
22+
23+
var_dump($frag->contains($inner));
24+
var_dump($tpl->contains($inner));
25+
var_dump($doc->body->contains($inner));
26+
27+
var_dump($inner->closest('b')?->nodeName);
28+
var_dump($inner->closest('#host'));
29+
var_dump($inner->closest('body'));
30+
31+
$pos = $tpl->compareDocumentPosition($inner);
32+
var_dump((bool) ($pos & 1));
33+
var_dump((bool) ($pos & 0x10));
34+
35+
try {
36+
$frag->appendChild($tpl);
37+
} catch (\Throwable $e) {
38+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
39+
}
40+
?>
41+
--EXPECT--
42+
string(20) "Dom\DocumentFragment"
43+
NULL
44+
NULL
45+
bool(true)
46+
bool(true)
47+
bool(false)
48+
bool(false)
49+
bool(true)
50+
bool(false)
51+
bool(false)
52+
string(1) "B"
53+
NULL
54+
NULL
55+
bool(true)
56+
bool(false)
57+
DOMException: Hierarchy Request Error

0 commit comments

Comments
 (0)