Skip to content

Commit 6f989cd

Browse files
committed
Merge branch 'PHP-8.3'
* PHP-8.3: Fix crash when calling childNodes next() when iterator is exhausted Fix references not handled correctly in C14N Fix crashes when entity declaration is removed while still having entity references
2 parents afa034d + 461d890 commit 6f989cd

11 files changed

+244
-8
lines changed

ext/dom/dom_iterators.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@ static zend_result php_dom_iterator_valid(zend_object_iterator *iter) /* {{{ */
114114
zval *php_dom_iterator_current_data(zend_object_iterator *iter) /* {{{ */
115115
{
116116
php_dom_iterator *iterator = (php_dom_iterator *)iter;
117-
118-
return &iterator->curobj;
117+
return Z_ISUNDEF(iterator->curobj) ? NULL : &iterator->curobj;
119118
}
120119
/* }}} */
121120

ext/dom/dom_properties.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ zend_result dom_entity_actual_encoding_read(dom_object *obj, zval *retval);
8989
zend_result dom_entity_encoding_read(dom_object *obj, zval *retval);
9090
zend_result dom_entity_version_read(dom_object *obj, zval *retval);
9191

92+
/* entity reference properties */
93+
int dom_entity_reference_child_read(dom_object *obj, zval *retval);
94+
int dom_entity_reference_text_content_read(dom_object *obj, zval *retval);
95+
int dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval);
96+
9297
/* namednodemap properties */
9398
zend_result dom_namednodemap_length_read(dom_object *obj, zval *retval);
9499

ext/dom/entityreference.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "php.h"
2323
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
2424
#include "php_dom.h"
25+
#include "dom_properties.h"
2526

2627
/*
2728
* class DOMEntityReference extends DOMNode
@@ -65,4 +66,63 @@ PHP_METHOD(DOMEntityReference, __construct)
6566
}
6667
/* }}} end DOMEntityReference::__construct */
6768

69+
/* The following property handlers are necessary because of special lifetime management with entities and entity
70+
* references. The issue is that entity references hold a reference to an entity declaration, but don't
71+
* register that reference anywhere. When the entity declaration disappears we have no way of notifying the
72+
* entity references. Override the property handlers for the declaration-accessing properties to fix this problem. */
73+
74+
xmlEntityPtr dom_entity_reference_fetch_and_sync_declaration(xmlNodePtr reference)
75+
{
76+
xmlEntityPtr entity = xmlGetDocEntity(reference->doc, reference->name);
77+
reference->children = (xmlNodePtr) entity;
78+
reference->last = (xmlNodePtr) entity;
79+
reference->content = entity ? entity->content : NULL;
80+
return entity;
81+
}
82+
83+
int dom_entity_reference_child_read(dom_object *obj, zval *retval)
84+
{
85+
xmlNodePtr nodep = dom_object_get_node(obj);
86+
87+
if (nodep == NULL) {
88+
php_dom_throw_error(INVALID_STATE_ERR, true);
89+
return FAILURE;
90+
}
91+
92+
xmlEntityPtr entity = dom_entity_reference_fetch_and_sync_declaration(nodep);
93+
if (entity == NULL) {
94+
ZVAL_NULL(retval);
95+
return SUCCESS;
96+
}
97+
98+
php_dom_create_object((xmlNodePtr) entity, retval, obj);
99+
return SUCCESS;
100+
}
101+
102+
int dom_entity_reference_text_content_read(dom_object *obj, zval *retval)
103+
{
104+
xmlNodePtr nodep = dom_object_get_node(obj);
105+
106+
if (nodep == NULL) {
107+
php_dom_throw_error(INVALID_STATE_ERR, true);
108+
return FAILURE;
109+
}
110+
111+
dom_entity_reference_fetch_and_sync_declaration(nodep);
112+
return dom_node_text_content_read(obj, retval);
113+
}
114+
115+
int dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval)
116+
{
117+
xmlNodePtr nodep = dom_object_get_node(obj);
118+
119+
if (nodep == NULL) {
120+
php_dom_throw_error(INVALID_STATE_ERR, true);
121+
return FAILURE;
122+
}
123+
124+
dom_entity_reference_fetch_and_sync_declaration(nodep);
125+
return dom_node_child_nodes_read(obj, retval);
126+
}
127+
68128
#endif

ext/dom/node.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2131,7 +2131,7 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
21312131
char *xquery;
21322132

21332133
/* Find "query" key */
2134-
tmp = zend_hash_find(ht, ZSTR_KNOWN(ZEND_STR_QUERY));
2134+
tmp = zend_hash_find_deref(ht, ZSTR_KNOWN(ZEND_STR_QUERY));
21352135
if (!tmp) {
21362136
/* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
21372137
zend_argument_value_error(3 + mode, "must have a \"query\" key");
@@ -2147,12 +2147,13 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
21472147
ctxp = xmlXPathNewContext(docp);
21482148
ctxp->node = nodep;
21492149

2150-
tmp = zend_hash_str_find(ht, "namespaces", sizeof("namespaces")-1);
2150+
tmp = zend_hash_str_find_deref(ht, "namespaces", sizeof("namespaces")-1);
21512151
if (tmp && Z_TYPE_P(tmp) == IS_ARRAY && !HT_IS_PACKED(Z_ARRVAL_P(tmp))) {
21522152
zval *tmpns;
21532153
zend_string *prefix;
21542154

21552155
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), prefix, tmpns) {
2156+
ZVAL_DEREF(tmpns);
21562157
if (Z_TYPE_P(tmpns) == IS_STRING) {
21572158
if (prefix) {
21582159
xmlXPathRegisterNs(ctxp, BAD_CAST ZSTR_VAL(prefix), BAD_CAST Z_STRVAL_P(tmpns));

ext/dom/nodelist.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ static zend_always_inline void reset_objmap_cache(dom_nnodemap_object *objmap)
5050
objmap->cached_length = -1;
5151
}
5252

53+
static xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep)
54+
{
55+
if (nodep->type == XML_ENTITY_REF_NODE) {
56+
/* See entityreference.c */
57+
dom_entity_reference_fetch_and_sync_declaration(nodep);
58+
}
59+
60+
return nodep->children;
61+
}
62+
5363
zend_long php_dom_get_nodelist_length(dom_object *obj)
5464
{
5565
dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr;
@@ -84,7 +94,7 @@ zend_long php_dom_get_nodelist_length(dom_object *obj)
8494

8595
int count = 0;
8696
if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) {
87-
xmlNodePtr curnode = nodep->children;
97+
xmlNodePtr curnode = dom_nodelist_iter_start_first_child(nodep);
8898
if (curnode) {
8999
count++;
90100
while (curnode->next != NULL) {
@@ -170,7 +180,7 @@ void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long
170180
int count = 0;
171181
if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) {
172182
if (restart) {
173-
nodep = nodep->children;
183+
nodep = dom_nodelist_iter_start_first_child(nodep);
174184
}
175185
while (count < relative_index && nodep != NULL) {
176186
count++;

ext/dom/php_dom.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ static HashTable dom_documentfragment_prop_handlers;
107107
static HashTable dom_modern_documentfragment_prop_handlers;
108108
static HashTable dom_node_prop_handlers;
109109
static HashTable dom_modern_node_prop_handlers;
110+
static HashTable dom_entity_reference_prop_handlers;
111+
static HashTable dom_modern_entity_reference_prop_handlers;
110112
static HashTable dom_nodelist_prop_handlers;
111113
static HashTable dom_namednodemap_prop_handlers;
112114
static HashTable dom_characterdata_prop_handlers;
@@ -1146,12 +1148,26 @@ PHP_MINIT_FUNCTION(dom)
11461148
dom_entityreference_class_entry = register_class_DOMEntityReference(dom_node_class_entry);
11471149
dom_entityreference_class_entry->create_object = dom_objects_new;
11481150
dom_entityreference_class_entry->default_object_handlers = &dom_object_handlers;
1149-
zend_hash_add_new_ptr(&classes, dom_entityreference_class_entry->name, &dom_node_prop_handlers);
1151+
1152+
zend_hash_init(&dom_entity_reference_prop_handlers, 0, NULL, NULL, true);
1153+
zend_hash_merge(&dom_entity_reference_prop_handlers, &dom_node_prop_handlers, NULL, false);
1154+
DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "firstChild", dom_entity_reference_child_read, NULL);
1155+
DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "lastChild", dom_entity_reference_child_read, NULL);
1156+
DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "textContent", dom_entity_reference_text_content_read, NULL);
1157+
DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "childNodes", dom_entity_reference_child_nodes_read, NULL);
1158+
zend_hash_add_new_ptr(&classes, dom_entityreference_class_entry->name, &dom_entity_reference_prop_handlers);
11501159

11511160
dom_modern_entityreference_class_entry = register_class_DOM_EntityReference(dom_modern_node_class_entry);
11521161
dom_modern_entityreference_class_entry->create_object = dom_objects_new;
11531162
dom_modern_entityreference_class_entry->default_object_handlers = &dom_object_handlers;
1154-
zend_hash_add_new_ptr(&classes, dom_modern_entityreference_class_entry->name, &dom_modern_node_prop_handlers);
1163+
1164+
zend_hash_init(&dom_modern_entity_reference_prop_handlers, 0, NULL, NULL, true);
1165+
zend_hash_merge(&dom_modern_entity_reference_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
1166+
DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "firstChild", dom_entity_reference_child_read, NULL);
1167+
DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "lastChild", dom_entity_reference_child_read, NULL);
1168+
DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "textContent", dom_entity_reference_text_content_read, NULL);
1169+
DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "childNodes", dom_entity_reference_child_nodes_read, NULL);
1170+
zend_hash_add_new_ptr(&classes, dom_modern_entityreference_class_entry->name, &dom_modern_entity_reference_prop_handlers);
11551171

11561172
dom_processinginstruction_class_entry = register_class_DOMProcessingInstruction(dom_node_class_entry);
11571173
dom_processinginstruction_class_entry->create_object = dom_objects_new;
@@ -1240,6 +1256,8 @@ PHP_MSHUTDOWN_FUNCTION(dom) /* {{{ */
12401256
zend_hash_destroy(&dom_modern_documentfragment_prop_handlers);
12411257
zend_hash_destroy(&dom_node_prop_handlers);
12421258
zend_hash_destroy(&dom_modern_node_prop_handlers);
1259+
zend_hash_destroy(&dom_entity_reference_prop_handlers);
1260+
zend_hash_destroy(&dom_modern_entity_reference_prop_handlers);
12431261
zend_hash_destroy(&dom_namespace_node_prop_handlers);
12441262
zend_hash_destroy(&dom_nodelist_prop_handlers);
12451263
zend_hash_destroy(&dom_namednodemap_prop_handlers);

ext/dom/php_dom.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ xmlChar *php_dom_libxml_fix_file_path(xmlChar *path);
173173
void dom_document_convert_to_modern(php_libxml_ref_obj *document, xmlDocPtr lxml_doc);
174174
dom_object *php_dom_instantiate_object_helper(zval *return_value, zend_class_entry *ce, xmlNodePtr obj, dom_object *parent);
175175
xmlDocPtr php_dom_create_html_doc(void);
176+
xmlEntityPtr dom_entity_reference_fetch_and_sync_declaration(xmlNodePtr reference);
176177

177178
xmlChar *dom_attr_value(const xmlAttr *attr, bool *free);
178179
bool dom_compare_value(const xmlAttr *attr, const xmlChar *value);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Test: Canonicalization - C14N() with references
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
// Adapted from canonicalization.phpt
8+
9+
$xml = <<<EOXML
10+
<?xml version="1.0" encoding="ISO-8859-1" ?>
11+
<foo xmlns="http://www.example.com/ns/foo"
12+
xmlns:fubar="http://www.example.com/ns/fubar" xmlns:test="urn::test"><contain>
13+
<bar><test1 /></bar>
14+
<bar><test2 /></bar>
15+
<fubar:bar xmlns:fubar="http://www.example.com/ns/fubar"><test3 /></fubar:bar>
16+
<fubar:bar><test4 /></fubar:bar>
17+
</contain>
18+
</foo>
19+
EOXML;
20+
21+
$dom = new DOMDocument();
22+
$dom->loadXML($xml);
23+
$doc = $dom->documentElement->firstChild;
24+
25+
$xpath = [
26+
'query' => '(//a:contain | //a:bar | .//namespace::*)',
27+
'namespaces' => ['a' => 'http://www.example.com/ns/foo'],
28+
];
29+
$prefixes = ['test'];
30+
31+
foreach ($xpath['namespaces'] as $k => &$v);
32+
unset($v);
33+
foreach ($xpath as $k => &$v);
34+
unset($v);
35+
foreach ($prefixes as $k => &$v);
36+
unset($v);
37+
38+
echo $doc->C14N(true, false, $xpath, $prefixes);
39+
?>
40+
--EXPECT--
41+
<contain xmlns="http://www.example.com/ns/foo"><bar></bar><bar></bar></contain>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Crash in childNodes iterator current()
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = new DOMDocument;
9+
$dom->loadXML('<foo>foo1</foo>');
10+
11+
$nodes = $dom->documentElement->childNodes;
12+
$iter = $nodes->getIterator();
13+
14+
var_dump($iter->valid());
15+
var_dump($iter->current()?->wholeText);
16+
$iter->next();
17+
var_dump($iter->valid());
18+
var_dump($iter->current()?->wholeText);
19+
20+
?>
21+
--EXPECT--
22+
bool(true)
23+
string(4) "foo1"
24+
bool(false)
25+
NULL
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Entity references with stale entity declaration 01
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = new DOMDocument;
9+
$dom->loadXML(<<<XML
10+
<!DOCTYPE foo [
11+
<!ENTITY foo "bar">
12+
]>
13+
<foo>&foo;</foo>
14+
XML);
15+
16+
$ref = $dom->documentElement->firstChild;
17+
$decl = $ref->firstChild;
18+
19+
$nodes = $ref->childNodes;
20+
$dom->removeChild($dom->doctype);
21+
unset($decl);
22+
23+
var_dump($nodes);
24+
var_dump($ref->firstChild);
25+
var_dump($ref->lastChild);
26+
var_dump($ref->textContent);
27+
var_dump($ref->childNodes);
28+
29+
?>
30+
--EXPECT--
31+
object(DOMNodeList)#4 (1) {
32+
["length"]=>
33+
int(0)
34+
}
35+
NULL
36+
NULL
37+
string(0) ""
38+
object(DOMNodeList)#2 (1) {
39+
["length"]=>
40+
int(0)
41+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
Entity references with stale entity declaration 02
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = new DOMDocument;
9+
$dom->loadXML(<<<XML
10+
<!DOCTYPE foo [
11+
<!ENTITY foo1 "bar1">
12+
<!ENTITY foo2 "bar2">
13+
<!ENTITY foo3 "bar3">
14+
]>
15+
<foo>&foo1;</foo>
16+
XML);
17+
18+
$ref = $dom->documentElement->firstChild;
19+
$decl = $ref->firstChild;
20+
21+
$nodes = $ref->childNodes;
22+
$iter = $nodes->getIterator();
23+
$iter->next();
24+
$dom->removeChild($dom->doctype);
25+
unset($decl);
26+
27+
try {
28+
var_dump($iter->current()->publicId);
29+
} catch (Error $e) {
30+
echo $e->getMessage(), "\n";
31+
}
32+
33+
?>
34+
--EXPECT--
35+
NULL

0 commit comments

Comments
 (0)