Commit d73b2f782e6 for php.net

commit d73b2f782e6e2487fde0d6d770454dc798dde335
Author: Niels Dossche <7771979+ndossche@users.noreply.github.com>
Date:   Thu Jan 29 18:26:50 2026 +0100

    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.

    Closes GH-21082.

diff --git a/NEWS b/NEWS
index dbac366b38a..cb2206ec1f5 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-21023 (CURLOPT_XFERINFOFUNCTION crash with a null callback).
     (David Carlier)

+- DOM:
+  . Fixed bug GH-21077 (Accessing Dom\Node::baseURI can throw TypeError).
+    (ndossche)
+
 - PDO_PGSQL:
   . Fixed bug GH-21055 (connection attribute status typo for GSS negotiation).
     (lsaos)
diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h
index 338b4acc449..e8fd7535db8 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 0dd1b959752..b6a794edcdd 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 22d52f123a1..a4dbec08631 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 00000000000..d9f9478925e
--- /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--
+<?php
+
+$implementation = new Dom\Implementation();
+$node = $implementation->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) "<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "publicId" "systemId">
+"
+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 bfb150b5cca..74deed8bdc3 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 @@
   ["nodeName"]=>
   string(5) "qname"
   ["baseURI"]=>
-  NULL
+  string(11) "about:blank"
   ["isConnected"]=>
   bool(false)
   ["parentNode"]=>
@@ -86,7 +86,7 @@
   ["nodeName"]=>
   string(5) "qname"
   ["baseURI"]=>
-  NULL
+  string(11) "about:blank"
   ["isConnected"]=>
   bool(false)
   ["parentNode"]=>
@@ -129,7 +129,7 @@
   ["nodeName"]=>
   string(5) "qname"
   ["baseURI"]=>
-  NULL
+  string(11) "about:blank"
   ["isConnected"]=>
   bool(false)
   ["parentNode"]=>
@@ -172,7 +172,7 @@
   ["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 fb0853939f8..f9bb1f7a996 100644
--- a/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt
+++ b/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt
@@ -148,7 +148,7 @@
   ["nodeName"]=>
   string(3) "GIF"
   ["baseURI"]=>
-  NULL
+  string(11) "about:blank"
   ["isConnected"]=>
   bool(false)
   ["parentNode"]=>