From 494998dad2d05d1fccb9befcb6fb33a166b5fd07 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:26:50 +0100 Subject: [PATCH] Fix GH-21077: Accessing Dom\Node::baseURI can throw TypeError Prior to this patch there was a common read handler, and it relied on the dom class set in the intern document. However, Dom\Implementation allows creating DTDs unassociated with a document, so we can't rely on an intern document and the check fails. This causes the ZVAL_NULL() path to be taken. To solve this, just split the handler. --- ext/dom/dom_properties.h | 1 + ext/dom/node.c | 25 ++++++++++++----- ext/dom/php_dom.c | 2 +- ext/dom/tests/modern/common/gh21077.phpt | 28 +++++++++++++++++++ ...ent_implementation_createDocumentType.phpt | 8 +++--- ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt | 2 +- 6 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 ext/dom/tests/modern/common/gh21077.phpt diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h index 338b4acc449f0..e8fd7535db8cd 100644 --- a/ext/dom/dom_properties.h +++ b/ext/dom/dom_properties.h @@ -134,6 +134,7 @@ zend_result dom_modern_node_prefix_read(dom_object *obj, zval *retval); zend_result dom_node_prefix_write(dom_object *obj, zval *newval); zend_result dom_node_local_name_read(dom_object *obj, zval *retval); zend_result dom_node_base_uri_read(dom_object *obj, zval *retval); +zend_result dom_modern_node_base_uri_read(dom_object *obj, zval *retval); zend_result dom_node_text_content_read(dom_object *obj, zval *retval); zend_result dom_node_text_content_write(dom_object *obj, zval *newval); diff --git a/ext/dom/node.c b/ext/dom/node.c index 0dd1b959752ef..b6a794edcdd88 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -659,14 +659,25 @@ zend_result dom_node_base_uri_read(dom_object *obj, zval *retval) ZVAL_STRING(retval, (const char *) baseuri); xmlFree(baseuri); } else { - if (php_dom_follow_spec_intern(obj)) { - if (nodep->doc->URL) { - ZVAL_STRING(retval, (const char *) nodep->doc->URL); - } else { - ZVAL_STRING(retval, "about:blank"); - } + ZVAL_NULL(retval); + } + + return SUCCESS; +} + +zend_result dom_modern_node_base_uri_read(dom_object *obj, zval *retval) +{ + DOM_PROP_NODE(xmlNodePtr, nodep, obj); + + xmlChar *baseuri = xmlNodeGetBase(nodep->doc, nodep); + if (baseuri) { + ZVAL_STRING(retval, (const char *) baseuri); + xmlFree(baseuri); + } else { + if (nodep->doc && nodep->doc->URL) { + ZVAL_STRING(retval, (const char *) nodep->doc->URL); } else { - ZVAL_NULL(retval); + ZVAL_STRING(retval, "about:blank"); } } diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 22d52f123a13d..a4dbec0863161 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -892,7 +892,7 @@ PHP_MINIT_FUNCTION(dom) zend_hash_init(&dom_modern_node_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nodeType", dom_node_node_type_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nodeName", dom_node_node_name_read, NULL); - DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "baseURI", dom_node_base_uri_read, NULL); + DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "baseURI", dom_modern_node_base_uri_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "isConnected", dom_node_is_connected_read, NULL); 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); diff --git a/ext/dom/tests/modern/common/gh21077.phpt b/ext/dom/tests/modern/common/gh21077.phpt new file mode 100644 index 0000000000000..d9f9478925ee4 --- /dev/null +++ b/ext/dom/tests/modern/common/gh21077.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-21077 (Accessing Dom\Node::baseURI can throw TypeError) +--EXTENSIONS-- +dom +--CREDITS-- +mbeccati +--FILE-- +createDocumentType('html', 'publicId', 'systemId'); + +var_dump($node->baseURI); + +$dom = Dom\XMLDocument::createEmpty(); +$dom->append($node = $dom->importNode($node)); + +var_dump($dom->saveXML()); + +var_dump($node->baseURI); + +?> +--EXPECT-- +string(11) "about:blank" +string(84) " + +" +string(11) "about:blank" diff --git a/ext/dom/tests/modern/spec/Document_implementation_createDocumentType.phpt b/ext/dom/tests/modern/spec/Document_implementation_createDocumentType.phpt index bfb150b5cca89..74deed8bdc349 100644 --- a/ext/dom/tests/modern/spec/Document_implementation_createDocumentType.phpt +++ b/ext/dom/tests/modern/spec/Document_implementation_createDocumentType.phpt @@ -43,7 +43,7 @@ object(Dom\DocumentType)#3 (19) { ["nodeName"]=> string(5) "qname" ["baseURI"]=> - NULL + string(11) "about:blank" ["isConnected"]=> bool(false) ["parentNode"]=> @@ -86,7 +86,7 @@ object(Dom\DocumentType)#2 (19) { ["nodeName"]=> string(5) "qname" ["baseURI"]=> - NULL + string(11) "about:blank" ["isConnected"]=> bool(false) ["parentNode"]=> @@ -129,7 +129,7 @@ object(Dom\DocumentType)#1 (19) { ["nodeName"]=> string(5) "qname" ["baseURI"]=> - NULL + string(11) "about:blank" ["isConnected"]=> bool(false) ["parentNode"]=> @@ -172,7 +172,7 @@ object(Dom\DocumentType)#4 (19) { ["nodeName"]=> string(5) "qname" ["baseURI"]=> - NULL + string(11) "about:blank" ["isConnected"]=> bool(false) ["parentNode"]=> diff --git a/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt b/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt index fb0853939f88e..f9bb1f7a996e6 100644 --- a/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt +++ b/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt @@ -148,7 +148,7 @@ object(Dom\Notation)#4 (13) { ["nodeName"]=> string(3) "GIF" ["baseURI"]=> - NULL + string(11) "about:blank" ["isConnected"]=> bool(false) ["parentNode"]=>