From b8fc6bd1c8ed62080ff7775194c735ea9f230a1e Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sun, 1 Feb 2026 10:41:37 +0100 Subject: [PATCH 1/2] Fix GH-21097: Accessing Dom\Node properties can can throw TypeError(s) Split the handler again, or defer to instanceof when performance doesn't matter. Closes GH-21108. --- NEWS | 2 + ext/dom/documenttype.c | 4 +- ext/dom/dom_properties.h | 2 + ext/dom/entityreference.c | 8 ++++ ext/dom/node.c | 13 ++++++- ext/dom/php_dom.c | 4 +- ext/dom/tests/modern/common/gh21097.phpt | 49 ++++++++++++++++++++++++ 7 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 ext/dom/tests/modern/common/gh21097.phpt diff --git a/NEWS b/NEWS index a2fec5115dda2..6416ddedb980d 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,8 @@ PHP NEWS - DOM: . Fixed bug GH-21077 (Accessing Dom\Node::baseURI can throw TypeError). (ndossche) + . Fixed bug GH-21097 (Accessing Dom\Node properties can can throw TypeError). + (ndossche) - PDO_PGSQL: . Fixed bug GH-21055 (connection attribute status typo for GSS negotiation). diff --git a/ext/dom/documenttype.c b/ext/dom/documenttype.c index 666dae56dc0f2..6af23fd73c418 100644 --- a/ext/dom/documenttype.c +++ b/ext/dom/documenttype.c @@ -47,7 +47,7 @@ zend_result dom_documenttype_entities_read(dom_object *obj, zval *retval) { DOM_PROP_NODE(xmlDtdPtr, dtdptr, obj); - php_dom_create_iterator(retval, DOM_DTD_NAMEDNODEMAP, php_dom_follow_spec_intern(obj)); + php_dom_create_iterator(retval, DOM_DTD_NAMEDNODEMAP, instanceof_function(obj->std.ce, dom_modern_documenttype_class_entry)); xmlHashTable *entityht = (xmlHashTable *) dtdptr->entities; @@ -68,7 +68,7 @@ zend_result dom_documenttype_notations_read(dom_object *obj, zval *retval) { DOM_PROP_NODE(xmlDtdPtr, dtdptr, obj); - php_dom_create_iterator(retval, DOM_DTD_NAMEDNODEMAP, php_dom_follow_spec_intern(obj)); + php_dom_create_iterator(retval, DOM_DTD_NAMEDNODEMAP, instanceof_function(obj->std.ce, dom_modern_documenttype_class_entry)); xmlHashTable *notationht = (xmlHashTable *) dtdptr->notations; diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h index e8fd7535db8cd..f82ce2645af1d 100644 --- a/ext/dom/dom_properties.h +++ b/ext/dom/dom_properties.h @@ -102,6 +102,7 @@ zend_result dom_entity_version_read(dom_object *obj, zval *retval); zend_result dom_entity_reference_child_read(dom_object *obj, zval *retval); zend_result dom_entity_reference_text_content_read(dom_object *obj, zval *retval); zend_result dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval); +zend_result dom_modern_entity_reference_child_nodes_read(dom_object *obj, zval *retval); /* namednodemap properties */ zend_result dom_namednodemap_length_read(dom_object *obj, zval *retval); @@ -119,6 +120,7 @@ zend_result dom_node_node_type_read(dom_object *obj, zval *retval); zend_result dom_node_parent_node_read(dom_object *obj, zval *retval); zend_result dom_node_parent_element_read(dom_object *obj, zval *retval); zend_result dom_node_child_nodes_read(dom_object *obj, zval *retval); +zend_result dom_modern_node_child_nodes_read(dom_object *obj, zval *retval); zend_result dom_node_first_child_read(dom_object *obj, zval *retval); zend_result dom_node_last_child_read(dom_object *obj, zval *retval); zend_result dom_node_previous_sibling_read(dom_object *obj, zval *retval); diff --git a/ext/dom/entityreference.c b/ext/dom/entityreference.c index bea49d85d0f62..215df208f5305 100644 --- a/ext/dom/entityreference.c +++ b/ext/dom/entityreference.c @@ -106,4 +106,12 @@ zend_result dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval) return dom_node_child_nodes_read(obj, retval); } +zend_result dom_modern_entity_reference_child_nodes_read(dom_object *obj, zval *retval) +{ + DOM_PROP_NODE(xmlNodePtr, nodep, obj); + + dom_entity_reference_fetch_and_sync_declaration(nodep); + return dom_modern_node_child_nodes_read(obj, retval); +} + #endif diff --git a/ext/dom/node.c b/ext/dom/node.c index b6a794edcdd88..9c1a508d669e9 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -287,7 +287,18 @@ zend_result dom_node_child_nodes_read(dom_object *obj, zval *retval) { DOM_PROP_NODE(xmlNodePtr, nodep, obj); - php_dom_create_iterator(retval, DOM_NODELIST, php_dom_follow_spec_intern(obj)); + php_dom_create_iterator(retval, DOM_NODELIST, false); + dom_object *intern = Z_DOMOBJ_P(retval); + dom_namednode_iter(obj, XML_ELEMENT_NODE, intern, NULL, NULL, 0, NULL, 0); + + return SUCCESS; +} + +zend_result dom_modern_node_child_nodes_read(dom_object *obj, zval *retval) +{ + DOM_PROP_NODE(xmlNodePtr, nodep, obj); + + php_dom_create_iterator(retval, DOM_NODELIST, true); dom_object *intern = Z_DOMOBJ_P(retval); dom_namednode_iter(obj, XML_ELEMENT_NODE, intern, NULL, NULL, 0, NULL, 0); diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index a4dbec0863161..135b3cdc5caa4 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -897,7 +897,7 @@ PHP_MINIT_FUNCTION(dom) DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL); - DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "childNodes", dom_node_child_nodes_read, NULL); + DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "childNodes", dom_modern_node_child_nodes_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "firstChild", dom_node_first_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "lastChild", dom_node_last_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "previousSibling", dom_node_previous_sibling_read, NULL); @@ -1294,7 +1294,7 @@ PHP_MINIT_FUNCTION(dom) 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); + DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "childNodes", dom_modern_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); diff --git a/ext/dom/tests/modern/common/gh21097.phpt b/ext/dom/tests/modern/common/gh21097.phpt new file mode 100644 index 0000000000000..db6abd12fcf7e --- /dev/null +++ b/ext/dom/tests/modern/common/gh21097.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-21097 (Accessing Dom\Node properties can can throw TypeError(s)) +--EXTENSIONS-- +dom +--CREDITS-- +mbeccati +--FILE-- +createDocumentType('html', 'publicId', 'systemId'); + +$r = new \ReflectionClass($node); +foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) { + echo $p->getName(), ": "; + var_dump($node->{$p->getName()}); +} + +?> +--EXPECT-- +nodeType: int(10) +nodeName: string(4) "html" +baseURI: string(11) "about:blank" +isConnected: bool(false) +ownerDocument: NULL +parentNode: NULL +parentElement: NULL +childNodes: object(Dom\NodeList)#24 (1) { + ["length"]=> + int(0) +} +firstChild: NULL +lastChild: NULL +previousSibling: NULL +nextSibling: NULL +nodeValue: NULL +textContent: string(0) "" +name: string(4) "html" +entities: object(Dom\DtdNamedNodeMap)#24 (1) { + ["length"]=> + int(0) +} +notations: object(Dom\DtdNamedNodeMap)#24 (1) { + ["length"]=> + int(0) +} +publicId: string(8) "publicId" +systemId: string(8) "systemId" +internalSubset: NULL From a5a0ff6448b22752409698184e8af3d1b305225f Mon Sep 17 00:00:00 2001 From: Arshid Date: Tue, 3 Feb 2026 23:57:10 +0530 Subject: [PATCH 2/2] ext/mysqli: raise ValueError for invalid option in mysqli_options() (#20971) Closes GH-20968 Co-authored-by: Ilija Tovilo Co-authored-by: Kamil Tekiela Co-authored-by: Gina Peter Banyard --- ext/mysqli/mysqli_api.c | 5 +++++ ext/mysqli/tests/gh20968.phpt | 26 ++++++++++++++++++++++++++ ext/mysqli/tests/mysqli_options.phpt | 13 ++++++++----- ext/mysqli/tests/mysqli_set_opt.phpt | 9 +++++++-- 4 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 ext/mysqli/tests/gh20968.phpt diff --git a/ext/mysqli/mysqli_api.c b/ext/mysqli/mysqli_api.c index 1bf74dd77eeab..77a06370cc738 100644 --- a/ext/mysqli/mysqli_api.c +++ b/ext/mysqli/mysqli_api.c @@ -1198,6 +1198,11 @@ PHP_FUNCTION(mysqli_options) MYSQLI_FETCH_RESOURCE_CONN(mysql, mysql_link, MYSQLI_STATUS_INITIALIZED); expected_type = mysqli_options_get_option_zval_type(mysql_option); + if (expected_type == IS_NULL) { + zend_argument_value_error(ERROR_ARG_POS(2), "must be MYSQLI_INIT_COMMAND, MYSQLI_SET_CHARSET_NAME, MYSQLI_SERVER_PUBLIC_KEY, or one of the MYSQLI_OPT_* constants"); + RETURN_THROWS(); + } + if (expected_type != Z_TYPE_P(mysql_value)) { switch (expected_type) { case IS_STRING: diff --git a/ext/mysqli/tests/gh20968.phpt b/ext/mysqli/tests/gh20968.phpt new file mode 100644 index 0000000000000..e78e1378091ee --- /dev/null +++ b/ext/mysqli/tests/gh20968.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-20968 mysqli_options() with invalid option should triggers ValueError +--EXTENSIONS-- +mysqli +--CONFLICTS-- +mysqli +--SKIPIF-- + +--FILE-- +options(10, 'invalid_option'); + var_dump($value); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECTF-- +mysqli::options(): Argument #%d ($option) must be MYSQLI_INIT_COMMAND, MYSQLI_SET_CHARSET_NAME, MYSQLI_SERVER_PUBLIC_KEY, or one of the MYSQLI_OPT_* constants diff --git a/ext/mysqli/tests/mysqli_options.phpt b/ext/mysqli/tests/mysqli_options.phpt index 64e42dc752b1a..44647f80d1946 100644 --- a/ext/mysqli/tests/mysqli_options.phpt +++ b/ext/mysqli/tests/mysqli_options.phpt @@ -60,7 +60,13 @@ var_dump("MYSQLI_OPT_LOCAL_INFILE", mysqli_options($link, MYSQLI_OPT_LOCAL_INFIL var_dump("MYSQLI_INIT_COMMAND", mysqli_options($link, MYSQLI_INIT_COMMAND, 'SET AUTOCOMMIT=0')); /* mysqli_real_connect() */ -var_dump("MYSQLI_CLIENT_SSL", mysqli_options($link, MYSQLI_CLIENT_SSL, 'not a mysqli_option')); +var_dump("MYSQLI_CLIENT_SSL"); + +try { + var_dump(mysqli_options($link, MYSQLI_CLIENT_SSL, 'not a mysqli_option')); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} mysqli_close($link); @@ -81,9 +87,6 @@ try { echo $e->getMessage() . "\n"; } -// invalid options do not generate errors -mysqli_options($link, -1, "Invalid option"); - print "done!"; ?> --EXPECTF-- @@ -110,7 +113,7 @@ bool(true) %s(19) "MYSQLI_INIT_COMMAND" bool(true) %s(17) "MYSQLI_CLIENT_SSL" -bool(false) +mysqli_options(): Argument #%d ($option) must be MYSQLI_INIT_COMMAND, MYSQLI_SET_CHARSET_NAME, MYSQLI_SERVER_PUBLIC_KEY, or one of the MYSQLI_OPT_* constants Link closed mysqli object is already closed Unknown character set diff --git a/ext/mysqli/tests/mysqli_set_opt.phpt b/ext/mysqli/tests/mysqli_set_opt.phpt index 218e9f5177869..4518201e4d1f8 100644 --- a/ext/mysqli/tests/mysqli_set_opt.phpt +++ b/ext/mysqli/tests/mysqli_set_opt.phpt @@ -24,7 +24,12 @@ require_once 'skipifconnectfailure.inc'; var_dump(mysqli_set_opt($link, MYSQLI_OPT_CONNECT_TIMEOUT, 10)); var_dump(mysqli_set_opt($link, MYSQLI_OPT_LOCAL_INFILE, 1)); var_dump(mysqli_set_opt($link, MYSQLI_INIT_COMMAND, 'SET AUTOCOMMIT=0')); - var_dump(mysqli_set_opt($link, MYSQLI_CLIENT_SSL, 'not an mysqli_option')); + + try { + var_dump(mysqli_set_opt($link, MYSQLI_CLIENT_SSL, 'not an mysqli_option')); + } catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; + } mysqli_close($link); @@ -48,6 +53,6 @@ bool(true) bool(true) bool(true) bool(true) -bool(false) +mysqli_set_opt(): Argument #2 ($option) must be MYSQLI_INIT_COMMAND, MYSQLI_SET_CHARSET_NAME, MYSQLI_SERVER_PUBLIC_KEY, or one of the MYSQLI_OPT_* constants mysqli object is already closed done!