From 9f7d88802e693c61e569988ef8a0b015d32c7668 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 17 Jun 2023 20:52:52 +0200 Subject: [PATCH] Fix #80332: Completely broken array access functionality with DOMNamedNodeMap The problem is the usage of zval_get_long(). In particular, if the string is non-numeric the result of zval_get_long() will be 0 without giving an error or warning. This is misleading for users: users get the impression that they can use strings to access the map because it coincidentally works for the first item (which is at index 0). Of course, this fails with any other index which causes confusion and bugs. This patch adds proper support for using string offsets while accessing the map. It does so by detecting if it's a non-numeric string, and then using the getNamedItem() method instead of item(). I had to split up the array access implementation code for DOMNodeList and DOMNamedNodeMap first to be able to do this. Closes GH-11468. --- NEWS | 2 + ext/dom/namednodemap.c | 122 ++++++++++++++++++---------------- ext/dom/nodelist.c | 50 +++++++------- ext/dom/php_dom.c | 109 ++++++++++++++++++++++++------ ext/dom/php_dom.h | 9 +++ ext/dom/tests/bug67949.phpt | 2 +- ext/dom/tests/bug80332_1.phpt | 84 +++++++++++++++++++++++ ext/dom/tests/bug80332_2.phpt | 47 +++++++++++++ 8 files changed, 318 insertions(+), 107 deletions(-) create mode 100644 ext/dom/tests/bug80332_1.phpt create mode 100644 ext/dom/tests/bug80332_2.phpt diff --git a/NEWS b/NEWS index 16dbfb22c3b99..dd949084cce9c 100644 --- a/NEWS +++ b/NEWS @@ -40,6 +40,8 @@ PHP NEWS issues). (nielsdos) . Fixed bug GH-11404 (DOMDocument::saveXML and friends omit xmlns="" declaration for null namespace). (nielsdos) + . Fixed bug #80332 (Completely broken array access functionality with + DOMNamedNodeMap). (nielsdos) - Opcache: . Fix allocation loop in zend_shared_alloc_startup(). (nielsdos) diff --git a/ext/dom/namednodemap.c b/ext/dom/namednodemap.c index 99103ce30b7ad..31b915243c5f7 100644 --- a/ext/dom/namednodemap.c +++ b/ext/dom/namednodemap.c @@ -31,7 +31,7 @@ * Since: */ -static int get_namednodemap_length(dom_object *obj) +int php_dom_get_namednodemap_length(dom_object *obj) { dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr; if (!objmap) { @@ -65,95 +65,74 @@ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core- */ int dom_namednodemap_length_read(dom_object *obj, zval *retval) { - ZVAL_LONG(retval, get_namednodemap_length(obj)); + ZVAL_LONG(retval, php_dom_get_namednodemap_length(obj)); return SUCCESS; } /* }}} */ -/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1074577549 -Since: -*/ -PHP_METHOD(DOMNamedNodeMap, getNamedItem) +xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, const char *named, bool may_transform) { - zval *id; - int ret; - size_t namedlen=0; - dom_object *intern; xmlNodePtr itemnode = NULL; - char *named; - - dom_nnodemap_object *objmap; - xmlNodePtr nodep; - xmlNotation *notep = NULL; - - id = ZEND_THIS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &named, &namedlen) == FAILURE) { - RETURN_THROWS(); - } - - intern = Z_DOMOBJ_P(id); - - objmap = (dom_nnodemap_object *)intern->ptr; - if (objmap != NULL) { if ((objmap->nodetype == XML_NOTATION_NODE) || objmap->nodetype == XML_ENTITY_NODE) { if (objmap->ht) { if (objmap->nodetype == XML_ENTITY_NODE) { - itemnode = (xmlNodePtr)xmlHashLookup(objmap->ht, (xmlChar *) named); + itemnode = (xmlNodePtr)xmlHashLookup(objmap->ht, (const xmlChar *) named); } else { - notep = (xmlNotation *)xmlHashLookup(objmap->ht, (xmlChar *) named); + xmlNotationPtr notep = xmlHashLookup(objmap->ht, (const xmlChar *) named); if (notep) { - itemnode = create_notation(notep->name, notep->PublicID, notep->SystemID); + if (may_transform) { + itemnode = create_notation(notep->name, notep->PublicID, notep->SystemID); + } else { + itemnode = (xmlNodePtr) notep; + } } } } } else { - nodep = dom_object_get_node(objmap->baseobj); + xmlNodePtr nodep = dom_object_get_node(objmap->baseobj); if (nodep) { - itemnode = (xmlNodePtr)xmlHasProp(nodep, (xmlChar *) named); + itemnode = (xmlNodePtr)xmlHasProp(nodep, (const xmlChar *) named); } } } + return itemnode; +} +void php_dom_named_node_map_get_named_item_into_zval(dom_nnodemap_object *objmap, const char *named, zval *return_value) +{ + int ret; + xmlNodePtr itemnode = php_dom_named_node_map_get_named_item(objmap, named, true); if (itemnode) { DOM_RET_OBJ(itemnode, &ret, objmap->baseobj); - return; } else { - RETVAL_NULL(); + RETURN_NULL(); } } -/* }}} end dom_namednodemap_get_named_item */ -/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-349467F9 +/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1074577549 Since: */ -PHP_METHOD(DOMNamedNodeMap, item) +PHP_METHOD(DOMNamedNodeMap, getNamedItem) { - zval *id; - zend_long index; - int ret; - dom_object *intern; - xmlNodePtr itemnode = NULL; - - dom_nnodemap_object *objmap; - xmlNodePtr nodep, curnode; - int count; + size_t namedlen; + char *named; - id = ZEND_THIS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { - RETURN_THROWS(); - } - if (index < 0 || ZEND_LONG_INT_OVFL(index)) { - zend_argument_value_error(1, "must be between 0 and %d", INT_MAX); + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &named, &namedlen) == FAILURE) { RETURN_THROWS(); } - intern = Z_DOMOBJ_P(id); - - objmap = (dom_nnodemap_object *)intern->ptr; + zval *id = ZEND_THIS; + dom_nnodemap_object *objmap = Z_DOMOBJ_P(id)->ptr; + php_dom_named_node_map_get_named_item_into_zval(objmap, named, return_value); +} +/* }}} end dom_namednodemap_get_named_item */ +xmlNodePtr php_dom_named_node_map_get_item(dom_nnodemap_object *objmap, zend_long index) +{ + xmlNodePtr itemnode = NULL; if (objmap != NULL) { if ((objmap->nodetype == XML_NOTATION_NODE) || objmap->nodetype == XML_ENTITY_NODE) { @@ -165,10 +144,10 @@ PHP_METHOD(DOMNamedNodeMap, item) } } } else { - nodep = dom_object_get_node(objmap->baseobj); + xmlNodePtr nodep = dom_object_get_node(objmap->baseobj); if (nodep) { - curnode = (xmlNodePtr)nodep->properties; - count = 0; + xmlNodePtr curnode = (xmlNodePtr)nodep->properties; + zend_long count = 0; while (count < index && curnode != NULL) { count++; curnode = (xmlNodePtr)curnode->next; @@ -177,13 +156,38 @@ PHP_METHOD(DOMNamedNodeMap, item) } } } + return itemnode; +} +void php_dom_named_node_map_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value) +{ + int ret; + xmlNodePtr itemnode = php_dom_named_node_map_get_item(objmap, index); if (itemnode) { DOM_RET_OBJ(itemnode, &ret, objmap->baseobj); - return; + } else { + RETURN_NULL(); + } +} + +/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-349467F9 +Since: +*/ +PHP_METHOD(DOMNamedNodeMap, item) +{ + zend_long index; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { + RETURN_THROWS(); + } + if (index < 0 || ZEND_LONG_INT_OVFL(index)) { + zend_argument_value_error(1, "must be between 0 and %d", INT_MAX); + RETURN_THROWS(); } - RETVAL_NULL(); + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + dom_nnodemap_object *objmap = intern->ptr; + php_dom_named_node_map_get_item_into_zval(objmap, index, return_value); } /* }}} end dom_namednodemap_item */ @@ -254,7 +258,7 @@ PHP_METHOD(DOMNamedNodeMap, count) } intern = Z_DOMOBJ_P(id); - RETURN_LONG(get_namednodemap_length(intern)); + RETURN_LONG(php_dom_get_namednodemap_length(intern)); } /* }}} end dom_namednodemap_count */ diff --git a/ext/dom/nodelist.c b/ext/dom/nodelist.c index b03ebe1acd90a..20f6320a54607 100644 --- a/ext/dom/nodelist.c +++ b/ext/dom/nodelist.c @@ -31,7 +31,7 @@ * Since: */ -static int get_nodelist_length(dom_object *obj) +int php_dom_get_nodelist_length(dom_object *obj) { dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr; if (!objmap) { @@ -82,7 +82,7 @@ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-20 */ int dom_nodelist_length_read(dom_object *obj, zval *retval) { - ZVAL_LONG(retval, get_nodelist_length(obj)); + ZVAL_LONG(retval, php_dom_get_nodelist_length(obj)); return SUCCESS; } @@ -99,36 +99,15 @@ PHP_METHOD(DOMNodeList, count) } intern = Z_DOMOBJ_P(id); - RETURN_LONG(get_nodelist_length(intern)); + RETURN_LONG(php_dom_get_nodelist_length(intern)); } /* }}} end dom_nodelist_count */ -/* }}} */ - -/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-844377136 -Since: -*/ -PHP_METHOD(DOMNodeList, item) +void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value) { - zval *id; - zend_long index; int ret; - dom_object *intern; xmlNodePtr itemnode = NULL; - - dom_nnodemap_object *objmap; - xmlNodePtr nodep, curnode; - int count = 0; - - id = ZEND_THIS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { - RETURN_THROWS(); - } - if (index >= 0) { - intern = Z_DOMOBJ_P(id); - - objmap = (dom_nnodemap_object *)intern->ptr; if (objmap != NULL) { if (objmap->ht) { if (objmap->nodetype == XML_ENTITY_NODE) { @@ -145,10 +124,11 @@ PHP_METHOD(DOMNodeList, item) return; } } else if (objmap->baseobj) { - nodep = dom_object_get_node(objmap->baseobj); + xmlNodePtr nodep = dom_object_get_node(objmap->baseobj); if (nodep) { + int count = 0; if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) { - curnode = nodep->children; + xmlNodePtr curnode = nodep->children; while (count < index && curnode != NULL) { count++; curnode = curnode->next; @@ -175,6 +155,22 @@ PHP_METHOD(DOMNodeList, item) RETVAL_NULL(); } + +/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-844377136 +Since: +*/ +PHP_METHOD(DOMNodeList, item) +{ + zend_long index; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { + RETURN_THROWS(); + } + + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + dom_nnodemap_object *objmap = intern->ptr; + php_dom_nodelist_get_item_into_zval(objmap, index, return_value); +} /* }}} end dom_nodelist_item */ ZEND_METHOD(DOMNodeList, getIterator) diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 1cd035b62f51f..be0534eb644e4 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -61,6 +61,7 @@ PHP_DOM_EXPORT zend_class_entry *dom_namespace_node_class_entry; zend_object_handlers dom_object_handlers; zend_object_handlers dom_nnodemap_object_handlers; +zend_object_handlers dom_nodelist_object_handlers; zend_object_handlers dom_object_namespace_node_handlers; #ifdef LIBXML_XPATH_ENABLED zend_object_handlers dom_xpath_object_handlers; @@ -90,6 +91,7 @@ static HashTable dom_xpath_prop_handlers; static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type); static void dom_object_namespace_node_free_storage(zend_object *object); static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original); +static zend_object *dom_nodelist_objects_new(zend_class_entry *class_type); typedef int (*dom_read_t)(dom_object *obj, zval *retval); typedef int (*dom_write_t)(dom_object *obj, zval *newval); @@ -577,6 +579,8 @@ void dom_objects_free_storage(zend_object *object); void dom_nnodemap_objects_free_storage(zend_object *object); static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv); static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty); +static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv); +static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty); static zend_object *dom_objects_store_clone_obj(zend_object *zobject); #ifdef LIBXML_XPATH_ENABLED void dom_xpath_objects_free_storage(zend_object *object); @@ -597,8 +601,12 @@ PHP_MINIT_FUNCTION(dom) memcpy(&dom_nnodemap_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); dom_nnodemap_object_handlers.free_obj = dom_nnodemap_objects_free_storage; - dom_nnodemap_object_handlers.read_dimension = dom_nodelist_read_dimension; - dom_nnodemap_object_handlers.has_dimension = dom_nodelist_has_dimension; + dom_nnodemap_object_handlers.read_dimension = dom_nodemap_read_dimension; + dom_nnodemap_object_handlers.has_dimension = dom_nodemap_has_dimension; + + memcpy(&dom_nodelist_object_handlers, &dom_nnodemap_object_handlers, sizeof(zend_object_handlers)); + dom_nodelist_object_handlers.read_dimension = dom_nodelist_read_dimension; + dom_nodelist_object_handlers.has_dimension = dom_nodelist_has_dimension; memcpy(&dom_object_namespace_node_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); dom_object_namespace_node_handlers.offset = XtOffsetOf(dom_object_namespace_node, dom.std); @@ -694,7 +702,7 @@ PHP_MINIT_FUNCTION(dom) zend_hash_add_ptr(&classes, dom_document_class_entry->name, &dom_document_prop_handlers); dom_nodelist_class_entry = register_class_DOMNodeList(zend_ce_aggregate, zend_ce_countable); - dom_nodelist_class_entry->create_object = dom_nnodemap_objects_new; + dom_nodelist_class_entry->create_object = dom_nodelist_objects_new; dom_nodelist_class_entry->get_iterator = php_dom_get_iterator; zend_hash_init(&dom_nodelist_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1); @@ -1129,7 +1137,7 @@ void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */ } /* }}} */ -zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) /* {{{ */ +static zend_object *dom_nodemap_or_nodelist_objects_new(zend_class_entry *class_type, const zend_object_handlers *handlers) { dom_object *intern; dom_nnodemap_object *objmap; @@ -1144,12 +1152,22 @@ zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) /* {{{ */ objmap->local = NULL; objmap->ns = NULL; - intern->std.handlers = &dom_nnodemap_object_handlers; + intern->std.handlers = handlers; return &intern->std; } + +zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) /* {{{ */ +{ + return dom_nodemap_or_nodelist_objects_new(class_type, &dom_nnodemap_object_handlers); +} /* }}} */ +zend_object *dom_nodelist_objects_new(zend_class_entry *class_type) +{ + return dom_nodemap_or_nodelist_objects_new(class_type, &dom_nodelist_object_handlers); +} + void php_dom_create_iterator(zval *return_value, int ce_type) /* {{{ */ { zend_class_entry *ce; @@ -1683,34 +1701,85 @@ xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr origina return attrp; } -static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */ +static bool dom_nodemap_or_nodelist_process_offset_as_named(zval *offset, zend_long *lval) { - zval offset_copy; + if (Z_TYPE_P(offset) == IS_STRING) { + /* See zval_get_long_func() */ + double dval; + zend_uchar is_numeric_string_type; + if (0 == (is_numeric_string_type = is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), lval, &dval, true))) { + return true; + } else if (is_numeric_string_type == IS_DOUBLE) { + *lval = zend_dval_to_lval_cap(dval); + } + } else { + *lval = zval_get_long(offset); + } + return false; +} - if (!offset) { - zend_throw_error(NULL, "Cannot access node list without offset"); +static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */ +{ + if (UNEXPECTED(!offset)) { + zend_throw_error(NULL, "Cannot access DOMNodeList without offset"); return NULL; } - ZVAL_LONG(&offset_copy, zval_get_long(offset)); - - zend_call_method_with_1_params(object, object->ce, NULL, "item", rv, &offset_copy); + zend_long lval; + if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) { + /* does not support named lookup */ + ZVAL_NULL(rv); + return rv; + } + php_dom_nodelist_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv); return rv; } /* }}} end dom_nodelist_read_dimension */ static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty) { - zend_long offset = zval_get_long(member); - zval rv; - - if (offset < 0) { + zend_long offset; + if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) { + /* does not support named lookup */ return 0; - } else { - zval *length = zend_read_property( - object->ce, object, "length", sizeof("length") - 1, 0, &rv); - return length && offset < Z_LVAL_P(length); } + + return offset >= 0 && offset < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); } /* }}} end dom_nodelist_has_dimension */ +static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */ +{ + if (UNEXPECTED(!offset)) { + zend_throw_error(NULL, "Cannot access DOMNamedNodeMap without offset"); + return NULL; + } + + zend_long lval; + if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) { + /* exceptional case, switch to named lookup */ + php_dom_named_node_map_get_named_item_into_zval(php_dom_obj_from_obj(object)->ptr, Z_STRVAL_P(offset), rv); + return rv; + } + + /* see PHP_METHOD(DOMNamedNodeMap, item) */ + if (UNEXPECTED(lval < 0 || ZEND_LONG_INT_OVFL(lval))) { + zend_value_error("must be between 0 and %d", INT_MAX); + return NULL; + } + + php_dom_named_node_map_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv); + return rv; +} /* }}} end dom_nodemap_read_dimension */ + +static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty) +{ + zend_long offset; + if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) { + /* exceptional case, switch to named lookup */ + return php_dom_named_node_map_get_named_item(php_dom_obj_from_obj(object)->ptr, Z_STRVAL_P(member), false) != NULL; + } + + return offset >= 0 && offset < php_dom_get_namednodemap_length(php_dom_obj_from_obj(object)); +} /* }}} end dom_nodemap_has_dimension */ + #endif /* HAVE_DOM */ diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index 6ed382b6f84af..9a57996729dbf 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -147,6 +147,15 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc); void dom_child_node_remove(dom_object *context); void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc); +/* nodemap and nodelist APIs */ +xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, const char *named, bool may_transform); +void php_dom_named_node_map_get_named_item_into_zval(dom_nnodemap_object *objmap, const char *named, zval *return_value); +xmlNodePtr php_dom_named_node_map_get_item(dom_nnodemap_object *objmap, zend_long index); +void php_dom_named_node_map_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value); +void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value); +int php_dom_get_namednodemap_length(dom_object *obj); +int php_dom_get_nodelist_length(dom_object *obj); + #define DOM_GET_OBJ(__ptr, __id, __prtype, __intern) { \ __intern = Z_DOMOBJ_P(__id); \ if (__intern->ptr == NULL || !(__ptr = (__prtype)((php_libxml_node_ptr *)__intern->ptr)->node)) { \ diff --git a/ext/dom/tests/bug67949.phpt b/ext/dom/tests/bug67949.phpt index 394c9888bd352..270beb02b494c 100644 --- a/ext/dom/tests/bug67949.phpt +++ b/ext/dom/tests/bug67949.phpt @@ -86,7 +86,7 @@ bool(true) string(4) "data" string(4) "test" testing read_dimension with null offset -Cannot access node list without offset +Cannot access DOMNodeList without offset testing attribute access string(4) "href" ==DONE== diff --git a/ext/dom/tests/bug80332_1.phpt b/ext/dom/tests/bug80332_1.phpt new file mode 100644 index 0000000000000..f0484399781e2 --- /dev/null +++ b/ext/dom/tests/bug80332_1.phpt @@ -0,0 +1,84 @@ +--TEST-- +Bug #80332 (Completely broken array access functionality with DOMNamedNodeMap) - DOMNamedNodeMap variation +--EXTENSIONS-- +dom +--FILE-- +loadHTML(''); + +$x = new DOMXPath($doc); +$span = $x->query('//span')[0]; + +print "Node name: {$span->nodeName}\n"; + +function test($span, $key) { + $key_formatted = match ($key) { + false => 'false', + true => 'true', + null => 'null', + default => is_string($key) ? "'$key'" : $key, + }; + echo "Attribute [{$key_formatted}] name: ", $span->attributes[$key]->nodeName ?? '/', "\n"; + echo "Attribute [{$key_formatted}] value: ", $span->attributes[$key]->nodeValue ?? '/', "\n"; + echo "Attribute [{$key_formatted}] isset: ", isset($span->attributes[$key]) ? "yes" : "no", "\n"; + echo "\n"; +} + +test($span, 0); +test($span, false); +test($span, true); +test($span, null); +test($span, 'attr2'); +// This one should fail because there is no 'hi' attribute +test($span, 'hi'); +test($span, '0'); +test($span, '0.5'); +test($span, '1'); +// This one should fail because it's out of bounds +test($span, '2147483647'); + +?> +--EXPECT-- +Node name: span +Attribute [0] name: attr1 +Attribute [0] value: value1 +Attribute [0] isset: yes + +Attribute [false] name: attr1 +Attribute [false] value: value1 +Attribute [false] isset: yes + +Attribute [true] name: attr2 +Attribute [true] value: value2 +Attribute [true] isset: yes + +Attribute [null] name: attr1 +Attribute [null] value: value1 +Attribute [null] isset: yes + +Attribute ['attr2'] name: attr2 +Attribute ['attr2'] value: value2 +Attribute ['attr2'] isset: yes + +Attribute ['hi'] name: / +Attribute ['hi'] value: / +Attribute ['hi'] isset: no + +Attribute ['0'] name: attr1 +Attribute ['0'] value: value1 +Attribute ['0'] isset: yes + +Attribute ['0.5'] name: attr1 +Attribute ['0.5'] value: value1 +Attribute ['0.5'] isset: yes + +Attribute ['1'] name: attr2 +Attribute ['1'] value: value2 +Attribute ['1'] isset: yes + +Attribute ['2147483647'] name: / +Attribute ['2147483647'] value: / +Attribute ['2147483647'] isset: no diff --git a/ext/dom/tests/bug80332_2.phpt b/ext/dom/tests/bug80332_2.phpt new file mode 100644 index 0000000000000..f1793e43b5931 --- /dev/null +++ b/ext/dom/tests/bug80332_2.phpt @@ -0,0 +1,47 @@ +--TEST-- +Bug #80332 (Completely broken array access functionality with DOMNamedNodeMap) - DOMNodeList variation +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + +$list = $doc->getElementsByTagName('strong'); + +function test($list, $key) { + $key_formatted = match ($key) { + false => 'false', + true => 'true', + null => 'null', + default => is_string($key) ? "'$key'" : $key, + }; + echo "list[$key_formatted] id attribute: ", $list[$key] ? $list[$key]->getAttribute('id') : '/', "\n"; +} + +test($list, 0); +test($list, false); +test($list, true); +test($list, null); +test($list, '0'); +test($list, '0.5'); +test($list, '1'); +// These two should fail because there's no named lookup possible here +test($list, 'attr2'); +test($list, 'hi'); +// This one should fail because it's out of bounds +test($list, '2147483647'); + +?> +--EXPECT-- +list[0] id attribute: 1 +list[false] id attribute: 1 +list[true] id attribute: 2 +list[null] id attribute: 1 +list['0'] id attribute: 1 +list['0.5'] id attribute: 1 +list['1'] id attribute: 2 +list['attr2'] id attribute: / +list['hi'] id attribute: / +list['2147483647'] id attribute: /