diff --git a/ext/dom/dom_iterators.c b/ext/dom/dom_iterators.c index ef81ecaaf73..9881647e7f6 100644 --- a/ext/dom/dom_iterators.c +++ b/ext/dom/dom_iterators.c @@ -114,8 +114,7 @@ static zend_result php_dom_iterator_valid(zend_object_iterator *iter) /* {{{ */ zval *php_dom_iterator_current_data(zend_object_iterator *iter) /* {{{ */ { php_dom_iterator *iterator = (php_dom_iterator *)iter; - - return &iterator->curobj; + return Z_ISUNDEF(iterator->curobj) ? NULL : &iterator->curobj; } /* }}} */ diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h index a580b024796..ce2edaebc4b 100644 --- a/ext/dom/dom_properties.h +++ b/ext/dom/dom_properties.h @@ -89,6 +89,11 @@ zend_result dom_entity_actual_encoding_read(dom_object *obj, zval *retval); zend_result dom_entity_encoding_read(dom_object *obj, zval *retval); zend_result dom_entity_version_read(dom_object *obj, zval *retval); +/* entity reference properties */ +int dom_entity_reference_child_read(dom_object *obj, zval *retval); +int dom_entity_reference_text_content_read(dom_object *obj, zval *retval); +int dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval); + /* namednodemap properties */ zend_result dom_namednodemap_length_read(dom_object *obj, zval *retval); diff --git a/ext/dom/entityreference.c b/ext/dom/entityreference.c index 2a11f693660..9935594453a 100644 --- a/ext/dom/entityreference.c +++ b/ext/dom/entityreference.c @@ -22,6 +22,7 @@ #include "php.h" #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "php_dom.h" +#include "dom_properties.h" /* * class DOMEntityReference extends DOMNode @@ -65,4 +66,63 @@ PHP_METHOD(DOMEntityReference, __construct) } /* }}} end DOMEntityReference::__construct */ +/* The following property handlers are necessary because of special lifetime management with entities and entity + * references. The issue is that entity references hold a reference to an entity declaration, but don't + * register that reference anywhere. When the entity declaration disappears we have no way of notifying the + * entity references. Override the property handlers for the declaration-accessing properties to fix this problem. */ + +xmlEntityPtr dom_entity_reference_fetch_and_sync_declaration(xmlNodePtr reference) +{ + xmlEntityPtr entity = xmlGetDocEntity(reference->doc, reference->name); + reference->children = (xmlNodePtr) entity; + reference->last = (xmlNodePtr) entity; + reference->content = entity ? entity->content : NULL; + return entity; +} + +int dom_entity_reference_child_read(dom_object *obj, zval *retval) +{ + xmlNodePtr nodep = dom_object_get_node(obj); + + if (nodep == NULL) { + php_dom_throw_error(INVALID_STATE_ERR, true); + return FAILURE; + } + + xmlEntityPtr entity = dom_entity_reference_fetch_and_sync_declaration(nodep); + if (entity == NULL) { + ZVAL_NULL(retval); + return SUCCESS; + } + + php_dom_create_object((xmlNodePtr) entity, retval, obj); + return SUCCESS; +} + +int dom_entity_reference_text_content_read(dom_object *obj, zval *retval) +{ + xmlNodePtr nodep = dom_object_get_node(obj); + + if (nodep == NULL) { + php_dom_throw_error(INVALID_STATE_ERR, true); + return FAILURE; + } + + dom_entity_reference_fetch_and_sync_declaration(nodep); + return dom_node_text_content_read(obj, retval); +} + +int dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval) +{ + xmlNodePtr nodep = dom_object_get_node(obj); + + if (nodep == NULL) { + php_dom_throw_error(INVALID_STATE_ERR, true); + return FAILURE; + } + + dom_entity_reference_fetch_and_sync_declaration(nodep); + return dom_node_child_nodes_read(obj, retval); +} + #endif diff --git a/ext/dom/node.c b/ext/dom/node.c index fca91c8f9ca..882ba7f89d2 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -2131,7 +2131,7 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ char *xquery; /* Find "query" key */ - tmp = zend_hash_find(ht, ZSTR_KNOWN(ZEND_STR_QUERY)); + tmp = zend_hash_find_deref(ht, ZSTR_KNOWN(ZEND_STR_QUERY)); if (!tmp) { /* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */ zend_argument_value_error(3 + mode, "must have a \"query\" key"); @@ -2147,12 +2147,13 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ ctxp = xmlXPathNewContext(docp); ctxp->node = nodep; - tmp = zend_hash_str_find(ht, "namespaces", sizeof("namespaces")-1); + tmp = zend_hash_str_find_deref(ht, "namespaces", sizeof("namespaces")-1); if (tmp && Z_TYPE_P(tmp) == IS_ARRAY && !HT_IS_PACKED(Z_ARRVAL_P(tmp))) { zval *tmpns; zend_string *prefix; ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), prefix, tmpns) { + ZVAL_DEREF(tmpns); if (Z_TYPE_P(tmpns) == IS_STRING) { if (prefix) { xmlXPathRegisterNs(ctxp, BAD_CAST ZSTR_VAL(prefix), BAD_CAST Z_STRVAL_P(tmpns)); diff --git a/ext/dom/nodelist.c b/ext/dom/nodelist.c index d2856577dde..2ee177c89bd 100644 --- a/ext/dom/nodelist.c +++ b/ext/dom/nodelist.c @@ -50,6 +50,16 @@ static zend_always_inline void reset_objmap_cache(dom_nnodemap_object *objmap) objmap->cached_length = -1; } +static xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep) +{ + if (nodep->type == XML_ENTITY_REF_NODE) { + /* See entityreference.c */ + dom_entity_reference_fetch_and_sync_declaration(nodep); + } + + return nodep->children; +} + zend_long php_dom_get_nodelist_length(dom_object *obj) { dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr; @@ -84,7 +94,7 @@ zend_long php_dom_get_nodelist_length(dom_object *obj) int count = 0; if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) { - xmlNodePtr curnode = nodep->children; + xmlNodePtr curnode = dom_nodelist_iter_start_first_child(nodep); if (curnode) { count++; while (curnode->next != NULL) { @@ -170,7 +180,7 @@ void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long int count = 0; if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) { if (restart) { - nodep = nodep->children; + nodep = dom_nodelist_iter_start_first_child(nodep); } while (count < relative_index && nodep != NULL) { count++; diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 978fbe96dec..063c0aa1c6e 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -107,6 +107,8 @@ static HashTable dom_documentfragment_prop_handlers; static HashTable dom_modern_documentfragment_prop_handlers; static HashTable dom_node_prop_handlers; static HashTable dom_modern_node_prop_handlers; +static HashTable dom_entity_reference_prop_handlers; +static HashTable dom_modern_entity_reference_prop_handlers; static HashTable dom_nodelist_prop_handlers; static HashTable dom_namednodemap_prop_handlers; static HashTable dom_characterdata_prop_handlers; @@ -1146,12 +1148,26 @@ PHP_MINIT_FUNCTION(dom) dom_entityreference_class_entry = register_class_DOMEntityReference(dom_node_class_entry); dom_entityreference_class_entry->create_object = dom_objects_new; dom_entityreference_class_entry->default_object_handlers = &dom_object_handlers; - zend_hash_add_new_ptr(&classes, dom_entityreference_class_entry->name, &dom_node_prop_handlers); + + zend_hash_init(&dom_entity_reference_prop_handlers, 0, NULL, NULL, true); + zend_hash_merge(&dom_entity_reference_prop_handlers, &dom_node_prop_handlers, NULL, false); + DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "firstChild", dom_entity_reference_child_read, NULL); + DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "lastChild", dom_entity_reference_child_read, NULL); + DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "textContent", dom_entity_reference_text_content_read, NULL); + DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "childNodes", dom_entity_reference_child_nodes_read, NULL); + zend_hash_add_new_ptr(&classes, dom_entityreference_class_entry->name, &dom_entity_reference_prop_handlers); dom_modern_entityreference_class_entry = register_class_DOM_EntityReference(dom_modern_node_class_entry); dom_modern_entityreference_class_entry->create_object = dom_objects_new; dom_modern_entityreference_class_entry->default_object_handlers = &dom_object_handlers; - zend_hash_add_new_ptr(&classes, dom_modern_entityreference_class_entry->name, &dom_modern_node_prop_handlers); + + zend_hash_init(&dom_modern_entity_reference_prop_handlers, 0, NULL, NULL, true); + zend_hash_merge(&dom_modern_entity_reference_prop_handlers, &dom_modern_node_prop_handlers, NULL, false); + DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "firstChild", dom_entity_reference_child_read, NULL); + DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "lastChild", dom_entity_reference_child_read, NULL); + DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "textContent", dom_entity_reference_text_content_read, NULL); + DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "childNodes", dom_entity_reference_child_nodes_read, NULL); + zend_hash_add_new_ptr(&classes, dom_modern_entityreference_class_entry->name, &dom_modern_entity_reference_prop_handlers); dom_processinginstruction_class_entry = register_class_DOMProcessingInstruction(dom_node_class_entry); dom_processinginstruction_class_entry->create_object = dom_objects_new; @@ -1240,6 +1256,8 @@ PHP_MSHUTDOWN_FUNCTION(dom) /* {{{ */ zend_hash_destroy(&dom_modern_documentfragment_prop_handlers); zend_hash_destroy(&dom_node_prop_handlers); zend_hash_destroy(&dom_modern_node_prop_handlers); + zend_hash_destroy(&dom_entity_reference_prop_handlers); + zend_hash_destroy(&dom_modern_entity_reference_prop_handlers); zend_hash_destroy(&dom_namespace_node_prop_handlers); zend_hash_destroy(&dom_nodelist_prop_handlers); zend_hash_destroy(&dom_namednodemap_prop_handlers); diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index 5aa2a9ce949..261354082d3 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -173,6 +173,7 @@ xmlChar *php_dom_libxml_fix_file_path(xmlChar *path); void dom_document_convert_to_modern(php_libxml_ref_obj *document, xmlDocPtr lxml_doc); dom_object *php_dom_instantiate_object_helper(zval *return_value, zend_class_entry *ce, xmlNodePtr obj, dom_object *parent); xmlDocPtr php_dom_create_html_doc(void); +xmlEntityPtr dom_entity_reference_fetch_and_sync_declaration(xmlNodePtr reference); xmlChar *dom_attr_value(const xmlAttr *attr, bool *free); bool dom_compare_value(const xmlAttr *attr, const xmlChar *value); diff --git a/ext/dom/tests/DOMNode_C14N_references.phpt b/ext/dom/tests/DOMNode_C14N_references.phpt new file mode 100644 index 00000000000..514e22be636 --- /dev/null +++ b/ext/dom/tests/DOMNode_C14N_references.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test: Canonicalization - C14N() with references +--EXTENSIONS-- +dom +--FILE-- + + + + + + + + +EOXML; + +$dom = new DOMDocument(); +$dom->loadXML($xml); +$doc = $dom->documentElement->firstChild; + +$xpath = [ + 'query' => '(//a:contain | //a:bar | .//namespace::*)', + 'namespaces' => ['a' => 'http://www.example.com/ns/foo'], +]; +$prefixes = ['test']; + +foreach ($xpath['namespaces'] as $k => &$v); +unset($v); +foreach ($xpath as $k => &$v); +unset($v); +foreach ($prefixes as $k => &$v); +unset($v); + +echo $doc->C14N(true, false, $xpath, $prefixes); +?> +--EXPECT-- + diff --git a/ext/dom/tests/childNodes_current_crash.phpt b/ext/dom/tests/childNodes_current_crash.phpt new file mode 100644 index 00000000000..aa93cf33a64 --- /dev/null +++ b/ext/dom/tests/childNodes_current_crash.phpt @@ -0,0 +1,25 @@ +--TEST-- +Crash in childNodes iterator current() +--EXTENSIONS-- +dom +--FILE-- +loadXML('foo1'); + +$nodes = $dom->documentElement->childNodes; +$iter = $nodes->getIterator(); + +var_dump($iter->valid()); +var_dump($iter->current()?->wholeText); +$iter->next(); +var_dump($iter->valid()); +var_dump($iter->current()?->wholeText); + +?> +--EXPECT-- +bool(true) +string(4) "foo1" +bool(false) +NULL diff --git a/ext/dom/tests/entity_reference_stale_01.phpt b/ext/dom/tests/entity_reference_stale_01.phpt new file mode 100644 index 00000000000..dc1828c3cd9 --- /dev/null +++ b/ext/dom/tests/entity_reference_stale_01.phpt @@ -0,0 +1,41 @@ +--TEST-- +Entity references with stale entity declaration 01 +--EXTENSIONS-- +dom +--FILE-- +loadXML(<< +]> +&foo; +XML); + +$ref = $dom->documentElement->firstChild; +$decl = $ref->firstChild; + +$nodes = $ref->childNodes; +$dom->removeChild($dom->doctype); +unset($decl); + +var_dump($nodes); +var_dump($ref->firstChild); +var_dump($ref->lastChild); +var_dump($ref->textContent); +var_dump($ref->childNodes); + +?> +--EXPECT-- +object(DOMNodeList)#4 (1) { + ["length"]=> + int(0) +} +NULL +NULL +string(0) "" +object(DOMNodeList)#2 (1) { + ["length"]=> + int(0) +} diff --git a/ext/dom/tests/entity_reference_stale_02.phpt b/ext/dom/tests/entity_reference_stale_02.phpt new file mode 100644 index 00000000000..9e25a2c0e94 --- /dev/null +++ b/ext/dom/tests/entity_reference_stale_02.phpt @@ -0,0 +1,35 @@ +--TEST-- +Entity references with stale entity declaration 02 +--EXTENSIONS-- +dom +--FILE-- +loadXML(<< + + +]> +&foo1; +XML); + +$ref = $dom->documentElement->firstChild; +$decl = $ref->firstChild; + +$nodes = $ref->childNodes; +$iter = $nodes->getIterator(); +$iter->next(); +$dom->removeChild($dom->doctype); +unset($decl); + +try { + var_dump($iter->current()->publicId); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +NULL