Merge branch 'PHP-8.2'

* PHP-8.2:
  Fix #80332: Completely broken array access functionality with DOMNamedNodeMap
This commit is contained in:
Niels Dossche 2023-06-18 15:18:52 +02:00
commit a37ad648b8
7 changed files with 320 additions and 123 deletions

View File

@ -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 @@ Since:
*/
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;
}
/* }}} */
xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, const char *named, bool may_transform)
{
xmlNodePtr itemnode = NULL;
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, (const xmlChar *) named);
} else {
xmlNotationPtr notep = xmlHashLookup(objmap->ht, (const xmlChar *) named);
if (notep) {
if (may_transform) {
itemnode = create_notation(notep->name, notep->PublicID, notep->SystemID);
} else {
itemnode = (xmlNodePtr) notep;
}
}
}
}
} else {
xmlNodePtr nodep = dom_object_get_node(objmap->baseobj);
if (nodep) {
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);
} else {
RETURN_NULL();
}
}
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1074577549
Since:
*/
PHP_METHOD(DOMNamedNodeMap, getNamedItem)
{
zval *id;
int ret;
size_t namedlen=0;
dom_object *intern;
xmlNodePtr itemnode = NULL;
size_t namedlen;
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);
} else {
notep = (xmlNotation *)xmlHashLookup(objmap->ht, (xmlChar *) named);
if (notep) {
itemnode = create_notation(notep->name, notep->PublicID, notep->SystemID);
}
}
}
} else {
nodep = dom_object_get_node(objmap->baseobj);
if (nodep) {
itemnode = (xmlNodePtr)xmlHasProp(nodep, (xmlChar *) named);
}
}
}
if (itemnode) {
DOM_RET_OBJ(itemnode, &ret, objmap->baseobj);
return;
} else {
RETVAL_NULL();
}
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 */
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-349467F9
Since:
*/
PHP_METHOD(DOMNamedNodeMap, item)
xmlNodePtr php_dom_named_node_map_get_item(dom_nnodemap_object *objmap, zend_long index)
{
zval *id;
zend_long index;
int ret;
dom_object *intern;
xmlNodePtr itemnode = NULL;
dom_nnodemap_object *objmap;
xmlNodePtr nodep, curnode;
int count;
id = ZEND_THIS;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(index)
ZEND_PARSE_PARAMETERS_END();
if (index < 0 || ZEND_LONG_INT_OVFL(index)) {
zend_argument_value_error(1, "must be between 0 and %d", INT_MAX);
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) {
@ -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;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(index)
ZEND_PARSE_PARAMETERS_END();
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 */

View File

@ -49,7 +49,7 @@ static zend_always_inline void reset_objmap_cache(dom_nnodemap_object *objmap)
objmap->cached_length = -1;
}
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) {
@ -114,7 +114,7 @@ Since:
*/
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;
}
@ -131,37 +131,16 @@ 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;
bool cache_itemnode = false;
dom_object *intern;
xmlNodePtr itemnode = NULL;
dom_nnodemap_object *objmap;
xmlNodePtr basep;
int count = 0;
id = ZEND_THIS;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(index)
ZEND_PARSE_PARAMETERS_END();
bool cache_itemnode = false;
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) {
@ -178,7 +157,7 @@ PHP_METHOD(DOMNodeList, item)
return;
}
} else if (objmap->baseobj) {
basep = dom_object_get_node(objmap->baseobj);
xmlNodePtr basep = dom_object_get_node(objmap->baseobj);
if (basep) {
xmlNodePtr nodep = basep;
/* For now we're only able to use cache for forward search.
@ -203,6 +182,7 @@ PHP_METHOD(DOMNodeList, item)
nodep = cached_obj_xml_node;
}
}
int count = 0;
if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) {
if (restart) {
nodep = nodep->children;
@ -255,6 +235,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;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(index)
ZEND_PARSE_PARAMETERS_END();
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)

View File

@ -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;
@ -580,6 +581,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);
@ -600,8 +603,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);
@ -698,7 +705,7 @@ PHP_MINIT_FUNCTION(dom)
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->default_object_handlers = &dom_nnodemap_object_handlers;
dom_nodelist_class_entry->default_object_handlers = &dom_nodelist_object_handlers;
dom_nodelist_class_entry->get_iterator = php_dom_get_iterator;
zend_hash_init(&dom_nodelist_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1);
@ -1110,7 +1117,7 @@ void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */
}
/* }}} */
zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) /* {{{ */
zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type)
{
dom_object *intern;
dom_nnodemap_object *objmap;
@ -1133,7 +1140,6 @@ zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) /* {{{ */
return &intern->std;
}
/* }}} */
void php_dom_create_iterator(zval *return_value, int ce_type) /* {{{ */
{
@ -1712,34 +1718,50 @@ xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr origina
return attrp;
}
static bool dom_nodemap_or_nodelist_process_offset_as_named(zval *offset, zend_long *lval)
{
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;
}
static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */
{
zval offset_copy;
if (!offset) {
zend_throw_error(NULL, "Cannot access node list without offset");
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 */
void dom_remove_all_children(xmlNodePtr nodep)
@ -1752,4 +1774,39 @@ void dom_remove_all_children(xmlNodePtr nodep)
}
}
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 */

View File

@ -157,6 +157,15 @@ void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc);
void dom_remove_all_children(xmlNodePtr nodep);
/* 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)) { \

View File

@ -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==

View File

@ -0,0 +1,84 @@
--TEST--
Bug #80332 (Completely broken array access functionality with DOMNamedNodeMap) - DOMNamedNodeMap variation
--EXTENSIONS--
dom
--FILE--
<?php
$doc = new DOMDocument;
$doc->loadHTML('<span attr1="value1" attr2="value2"></span>');
$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

View File

@ -0,0 +1,47 @@
--TEST--
Bug #80332 (Completely broken array access functionality with DOMNamedNodeMap) - DOMNodeList variation
--EXTENSIONS--
dom
--FILE--
<?php
$doc = new DOMDocument;
$doc->loadXML('<?xml version="1.0"?><span><strong id="1"/><strong id="2"/></span>');
$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: /