From 7492b39c54f51c01323111daadbbf3ce8353609e Mon Sep 17 00:00:00 2001
From: Jim Jones <jim.jones@uni-muenster.de>
Date: Thu, 29 Oct 2026 11:37:06 +0100
Subject: [PATCH v1] Expand entity references in xpath(), xmlexists() and
 XMLTABLE

xpath_internal() and XmlTableSetDocument() parse the document without
XML_PARSE_NOENT, so references to internal entities stay in the tree
as entity reference nodes. xml_parse() uses XML_PARSE_NOENT, so XPath
evaluation sees a different document than the rest of the xml code.

Parse with XML_PARSE_NOENT in both places, as xml_parse() and
contrib/xml2 already do.  This changes results for documents that use
entity references; e.g. /r/text() on <r>u&e;</r> now returns "uy"
instead of "u".  Documents without entity references are not affected.
---
 src/backend/utils/adt/xml.c       |  5 +++--
 src/test/regress/expected/xml.out | 26 ++++++++++++++++++++++++++
 src/test/regress/sql/xml.sql      |  6 ++++++
 3 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index dab00954c6f..99bdbe18ee7 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -4479,7 +4479,7 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
 			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
 						"could not allocate parser context");
 		doc = xmlCtxtReadMemory(ctxt, (char *) string + xmldecl_len,
-								len - xmldecl_len, NULL, NULL, 0);
+								len - xmldecl_len, NULL, NULL, XML_PARSE_NOENT);
 		if (doc == NULL || xmlerrcxt->err_occurred)
 			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
 						"could not parse XML document");
@@ -4816,7 +4816,8 @@ XmlTableSetDocument(TableFuncScanState *state, Datum value)
 
 	PG_TRY();
 	{
-		doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0);
+		doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL,
+								XML_PARSE_NOENT);
 		if (doc == NULL || xtCxt->xmlerrcxt->err_occurred)
 			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
 						"could not parse XML document");
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 6998745ee03..0e2c5e02680 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1309,6 +1309,32 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>
 (1 row)
 
+-- Entity references are expanded before XPath evaluation.
+SELECT xpath('/r/text()', '<!DOCTYPE r [<!ENTITY e "y">]><r>u&e;</r>');
+ xpath 
+-------
+ {uy}
+(1 row)
+
+SELECT xpath('count(//b)', '<!DOCTYPE r [<!ENTITY e "<b>z</b>">]><r>&e;&e;</r>');
+ xpath 
+-------
+ {2}
+(1 row)
+
+SELECT xmlexists('//b' PASSING BY REF '<!DOCTYPE r [<!ENTITY e "<b>z</b>">]><r>&e;</r>');
+ xmlexists 
+-----------
+ t
+(1 row)
+
+SELECT * FROM XMLTABLE('//b' PASSING '<!DOCTYPE r [<!ENTITY e "<b>z</b>">]><r>&e;&e;</r>' COLUMNS b text PATH '.');
+ b 
+---
+ z
+ z
+(2 rows)
+
 -- XMLPATH tests
 CREATE TABLE xmldata(data xml);
 INSERT INTO xmldata VALUES('<ROWS>
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 0581d21b03f..251eafbae17 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -408,6 +408,12 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]
 -- This might or might not load the requested DTD, but it mustn't throw error.
 SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>');
 
+-- Entity references are expanded before XPath evaluation.
+SELECT xpath('/r/text()', '<!DOCTYPE r [<!ENTITY e "y">]><r>u&e;</r>');
+SELECT xpath('count(//b)', '<!DOCTYPE r [<!ENTITY e "<b>z</b>">]><r>&e;&e;</r>');
+SELECT xmlexists('//b' PASSING BY REF '<!DOCTYPE r [<!ENTITY e "<b>z</b>">]><r>&e;</r>');
+SELECT * FROM XMLTABLE('//b' PASSING '<!DOCTYPE r [<!ENTITY e "<b>z</b>">]><r>&e;&e;</r>' COLUMNS b text PATH '.');
+
 -- XMLPATH tests
 CREATE TABLE xmldata(data xml);
 INSERT INTO xmldata VALUES('<ROWS>
-- 
2.56.0

