From ba2b0e36c18eb88dd2cad72fd6471551b0027019 Mon Sep 17 00:00:00 2001 From: Rui Zhao Date: Fri, 9 Oct 2026 14:10:43 +0800 Subject: [PATCH] Fix loss of leading U+FEFF in XML content parsing With libxml2 2.9.7, xmlParseInNodeContext() uses doc->encoding to start UTF-8 conversion, which discards a leading U+FEFF as a BOM. xml_parse() has already skipped the XML declaration, so that character can be part of the content. XMLSERIALIZE ... INDENT then loses it. Set doc->encoding after parsing instead. The content is already UTF-8, which the parser accepts without an explicit encoding. Add a test with U+FEFF after the XML declaration. --- src/backend/utils/adt/xml.c | 17 ++++++++++++----- src/test/regress/expected/xml.out | 16 ++++++++++++++++ src/test/regress/sql/xml.sql | 16 ++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index dcda62c4606..3b28070843b 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -1893,11 +1893,12 @@ xml_parse(text *data, XmlOptionType xmloption_arg, xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, "could not allocate XML document"); - Assert(doc->encoding == NULL); - doc->encoding = xmlStrdup((const xmlChar *) "UTF-8"); - if (doc->encoding == NULL || xmlerrcxt->err_occurred) - xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, - "could not allocate XML document"); + /* + * Leave doc->encoding unset while parsing the UTF-8 content. + * Older libxml2 versions otherwise strip a leading U+FEFF as a + * BOM, even when it followed the XML declaration in the original + * input. + */ doc->standalone = standalone; root = xmlNewNode(NULL, (const xmlChar *) "content-root"); @@ -1941,6 +1942,12 @@ xml_parse(text *data, XmlOptionType xmloption_arg, goto fail; } } + + Assert(doc->encoding == NULL); + doc->encoding = xmlStrdup((const xmlChar *) "UTF-8"); + if (doc->encoding == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate XML document"); } fail: diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 350941f7172..7576a84b7d8 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -618,6 +618,22 @@ SELECT xmlserialize(CONTENT ' (1 row) +-- U+FEFF after an XML declaration is content, not a byte-order mark. +DO $$ +DECLARE + result text; +BEGIN + IF getdatabaseencoding() <> 'UTF8' THEN + RETURN; + END IF; + + result := xmlserialize(CONTENT xmlparse(CONTENT + '' || chr(65279) || 'abc') AS text INDENT); + IF result <> 'abc' THEN + RAISE EXCEPTION 'U+FEFF was not preserved: %', result; + END IF; +END +$$; -- indent containing DOCTYPE declaration SELECT xmlserialize(DOCUMENT '' AS text INDENT); xmlserialize diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index ea0438aa45d..edd5f5e7022 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -166,6 +166,22 @@ SELECT xmlserialize(CONTENT NULL AS text INDENT); -- indent with XML declaration SELECT xmlserialize(DOCUMENT '73' AS text INDENT); SELECT xmlserialize(CONTENT '73' AS text INDENT); +-- U+FEFF after an XML declaration is content, not a byte-order mark. +DO $$ +DECLARE + result text; +BEGIN + IF getdatabaseencoding() <> 'UTF8' THEN + RETURN; + END IF; + + result := xmlserialize(CONTENT xmlparse(CONTENT + '' || chr(65279) || 'abc') AS text INDENT); + IF result <> 'abc' THEN + RAISE EXCEPTION 'U+FEFF was not preserved: %', result; + END IF; +END +$$; -- indent containing DOCTYPE declaration SELECT xmlserialize(DOCUMENT '' AS text INDENT); SELECT xmlserialize(CONTENT '' AS text INDENT); -- 2.43.7