xml-content-2006-2.patch
text/x-patch
Filename: xml-content-2006-2.patch
Type: text/x-patch
Part: 1
Patch
Format: unified
Series: patch 2006
| File | + | − |
|---|---|---|
| doc/src/sgml/datatype.sgml | 6 | 19 |
| src/backend/catalog/sql_features.txt | 1 | 1 |
| src/backend/utils/adt/xml.c | 109 | 3 |
| src/test/regress/expected/xml_1.out | 42 | 0 |
| src/test/regress/expected/xml_2.out | 46 | 0 |
| src/test/regress/expected/xml.out | 46 | 0 |
| src/test/regress/sql/xml.sql | 7 | 0 |
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 94b46a6..752eadf 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4208,9 +4208,11 @@ a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
<para>
The <type>xml</type> type can store well-formed
<quote>documents</quote>, as defined by the XML standard, as well
- as <quote>content</quote> fragments, which are defined by the
- production <literal>XMLDecl? content</literal> in the XML
- standard. Roughly, this means that content fragments can have
+ as <quote>content</quote> fragments, which are defined by reference to the
+ more permissive
+ <ulink url="https://www.w3.org/TR/2010/REC-xpath-datamodel-20101214/#DocumentNode"><quote>document node</quote></ulink>
+ of the XQuery and XPath data model.
+ Roughly, this means that content fragments can have
more than one top-level element or character node. The expression
<literal><replaceable>xmlvalue</replaceable> IS DOCUMENT</literal>
can be used to evaluate whether a particular <type>xml</type>
@@ -4282,24 +4284,9 @@ SET XML OPTION { DOCUMENT | CONTENT };
SET xmloption TO { DOCUMENT | CONTENT };
</synopsis>
The default is <literal>CONTENT</literal>, so all forms of XML
- data are allowed except as noted below.
+ data are allowed.
</para>
- <note>
- <para>
- In the SQL:2006 and later standard, the <literal>CONTENT</literal> form
- is a proper superset of the <literal>DOCUMENT</literal> form, and so the
- default XML option setting would allow casting to XML from character
- strings in either form. <productname>PostgreSQL</productname>, however,
- uses the SQL:2003 definition in which <literal>CONTENT</literal> form
- cannot contain a document type declaration. Therefore, there is no one
- setting of the XML option that will allow casting to XML from every valid
- character string. The default will work almost always, but for a document
- with a DTD, it will be necessary to change the XML option or
- use <literal>XMLPARSE</literal> specifying <literal>DOCUMENT</literal>.
- </para>
- </note>
-
</sect2>
<sect2>
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index e947613..32908c1 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -569,7 +569,7 @@ X056 Advanced table mapping: data mapping YES
X057 Advanced table mapping: metadata mapping YES
X058 Advanced table mapping: base64 encoding of binary strings YES
X059 Advanced table mapping: hex encoding of binary strings YES
-X060 XMLParse: character string input and CONTENT option YES uses SQL:2003 definition, so a document with a DTD cannot be parsed as CONTENT
+X060 XMLParse: character string input and CONTENT option YES
X061 XMLParse: character string input and DOCUMENT option YES
X065 XMLParse: BLOB input and CONTENT option NO
X066 XMLParse: BLOB input and DOCUMENT option NO
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 28b3eaa..23f7dcb 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -110,6 +110,8 @@ struct PgXmlErrorContext
/* current error status and accumulated message, if any */
bool err_occurred;
StringInfoData err_buf;
+ /* set in xml_parse if trying to parse CONTENT; reset in err hdlr if DTD */
+ bool tentatively_content;
/* previous libxml error handling state (saved by pg_xml_init) */
xmlStructuredErrorFunc saved_errfunc;
void *saved_errcxt;
@@ -120,6 +122,7 @@ struct PgXmlErrorContext
static xmlParserInputPtr xmlPgEntityLoader(const char *URL, const char *ID,
xmlParserCtxtPtr ctxt);
static void xml_errorHandler(void *data, xmlErrorPtr error);
+static bool xml_onlyPrologNodes(xmlDocPtr doc);
static void xml_ereport_by_code(int level, int sqlcode,
const char *msg, int errcode);
static void chopStringInfoNewlines(StringInfo str);
@@ -1002,6 +1005,7 @@ pg_xml_init(PgXmlStrictness strictness)
errcxt->magic = ERRCXT_MAGIC;
errcxt->strictness = strictness;
errcxt->err_occurred = false;
+ errcxt->tentatively_content = false;
initStringInfo(&errcxt->err_buf);
/*
@@ -1433,6 +1437,9 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
xmlChar *string;
xmlChar *utf8string;
PgXmlErrorContext *xmlerrcxt;
+ int errcode_document_parse = ERRCODE_INVALID_XML_DOCUMENT;
+ char const *errmsg_document_parse = "invalid XML document";
+ char const *errmsg_content_parse = "invalid XML content";
volatile xmlParserCtxtPtr ctxt = NULL;
volatile xmlDocPtr doc = NULL;
@@ -1457,6 +1464,7 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate parser context");
+try_other_xmloption:
if (xmloption_arg == XMLOPTION_DOCUMENT)
{
/*
@@ -1472,8 +1480,8 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
XML_PARSE_NOENT | XML_PARSE_DTDATTR
| (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS));
if (doc == NULL || xmlerrcxt->err_occurred)
- xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
- "invalid XML document");
+ xml_ereport(xmlerrcxt, ERROR, errcode_document_parse,
+ errmsg_document_parse);
}
else
{
@@ -1497,11 +1505,42 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
/* allow empty content */
if (*(utf8string + count))
{
+ xmlerrcxt->tentatively_content = true;
res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0,
utf8string + count, NULL);
+ /*
+ * If tentatively_content is now false, the error handler has
+ * reset it, which will only happen for one reason: a DTD was
+ * found in the input. The SQL/XML:2003 definition of CONTENT
+ * is not satisfied by a document with a DTD, which is a bit of
+ * a wart, as it means the CONTENT type is not a proper superset
+ * of DOCUMENT. SQL/XML:2006 and later fix that, by declaring
+ * that any DOCUMENT value is indeed also a CONTENT value. That
+ * definition is more useful, as CONTENT becomes usable for
+ * parsing input of unknown form (think pg_restore). Without
+ * bringing the whole implementation along to 2006+, we can
+ * provide the 2006+ definition of CONTENT easily enough, by
+ * catching this error case and simply retrying the parse
+ * as DOCUMENT.
+ */
+ if ( ! xmlerrcxt->tentatively_content )
+ {
+ /*
+ * xmlParseBalanced... was not passed our ctxt; it
+ * creates one internally for its own use, so we have
+ * no need to clear/reset ctxt here.
+ */
+ xmlFreeDoc(doc);
+ xmloption_arg = XMLOPTION_DOCUMENT;
+ errcode_document_parse = ERRCODE_INVALID_XML_CONTENT;
+ errmsg_document_parse = errmsg_content_parse;
+ goto try_other_xmloption;
+ }
if (res_code != 0 || xmlerrcxt->err_occurred)
+ {
xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_CONTENT,
- "invalid XML content");
+ errmsg_content_parse);
+ }
}
}
}
@@ -1699,6 +1738,27 @@ xml_errorHandler(void *data, xmlErrorPtr error)
switch (domain)
{
case XML_FROM_PARSER:
+ /*
+ * One very specific error from the parser will be intercepted here
+ * if strictness is PG_XML_STRICTNESS_WELLFORMED (meaning the call
+ * originates in xml_parse), the input is being tentatively parsed
+ * as content, and the parser has stumbled on what looks like a DTD.
+ * For that case, just change tentatively_content to false and
+ * return, and xml_parse will retry the input as a document.
+ * See xml_parse for motivation.
+ */
+ if (xmlerrcxt->strictness == PG_XML_STRICTNESS_WELLFORMED
+ && xmlerrcxt->tentatively_content
+ && error->code == XML_ERR_NAME_REQUIRED
+ && input != NULL
+ && input->cur != NULL
+ && 0 == xmlStrncmp(input->cur, BAD_CAST"!DOCTYPE", 8)
+ && xml_onlyPrologNodes(ctxt->myDoc))
+ {
+ xmlerrcxt->tentatively_content = false;
+ return;
+ }
+ /* FALLTHROUGH */
case XML_FROM_NONE:
case XML_FROM_MEMORY:
case XML_FROM_IO:
@@ -1811,6 +1871,52 @@ xml_errorHandler(void *data, xmlErrorPtr error)
pfree(errorBuf);
}
+/*
+ * Check (from the error handler) that the document node's only children already
+ * parsed are those that can precede a DTD.
+ *
+ * To deliver the SQL/XML:2006+ definition of 'content', the error handler above
+ * can cause a reparse as 'document' if a tentative parse as 'content' trips
+ * over something that looks like a DTD. Without this check, goofy input like
+ * <a><!DOCTYPE b></a> could also cause an attempted 'document' reparse, which
+ * would correctly fail, but the error message could give the wrong reason.
+ *
+ * This explores the document that is being constructed. If anything about its
+ * structure is not as expected, the return is false, so the libxml error will
+ * be reported, with no other attempt at special handling.
+ */
+static bool
+xml_onlyPrologNodes(xmlDocPtr doc)
+{
+ xmlNodePtr n;
+
+ if ( doc == NULL )
+ return false;
+
+ n = doc->children; /* parseBalancedChunk creates a temporary "pseudoroot" */
+ if ( n == NULL || n->type != XML_ELEMENT_NODE )
+ return false;
+ if ( n->name == NULL || 0 != xmlStrcmp(n->name, BAD_CAST"pseudoroot") )
+ return false;
+
+ for ( n = n->children; n != NULL; n = n->next )
+ {
+ switch (n->type)
+ {
+ case XML_PI_NODE:
+ case XML_COMMENT_NODE:
+ continue;
+ case XML_TEXT_NODE:
+ if ( xmlIsBlankNode(n) )
+ continue;
+ /* FALLTHROUGH */
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* Wrapper for "ereport" function for XML-related errors. The "msg"
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 2085fa0..0aae600 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -532,6 +532,13 @@ LINE 1: EXECUTE foo ('bad');
DETAIL: line 1: Start tag expected, '<' not found
bad
^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML document
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
SET XML OPTION CONTENT;
EXECUTE foo ('<bar/>');
xmlconcat
@@ -545,6 +552,45 @@ EXECUTE foo ('good');
<foo/>good
(1 row)
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+ xml
+--------------------------------------------------------------------
+ <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+ xml
+------------------------------
+ <!-- hi--> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!DOCTYPE a><a/>';
+ xml
+------------------
+ <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> oops <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> <oops/> <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
-- Test backwards parsing
CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 7f86696..d1a03b5 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -429,11 +429,53 @@ EXECUTE foo ('<bar/>');
ERROR: prepared statement "foo" does not exist
EXECUTE foo ('bad');
ERROR: prepared statement "foo" does not exist
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
SET XML OPTION CONTENT;
EXECUTE foo ('<bar/>');
ERROR: prepared statement "foo" does not exist
EXECUTE foo ('good');
ERROR: prepared statement "foo" does not exist
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!DOCTYPE a><a/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
-- Test backwards parsing
CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 510b09b..9756b6e 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -512,6 +512,13 @@ LINE 1: EXECUTE foo ('bad');
DETAIL: line 1: Start tag expected, '<' not found
bad
^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML document
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
SET XML OPTION CONTENT;
EXECUTE foo ('<bar/>');
xmlconcat
@@ -525,6 +532,45 @@ EXECUTE foo ('good');
<foo/>good
(1 row)
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+ xml
+--------------------------------------------------------------------
+ <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+ xml
+------------------------------
+ <!-- hi--> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!DOCTYPE a><a/>';
+ xml
+------------------
+ <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> oops <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> <oops/> <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
-- Test backwards parsing
CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 8057a46..71431d8 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -149,10 +149,17 @@ PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
SET XML OPTION DOCUMENT;
EXECUTE foo ('<bar/>');
EXECUTE foo ('bad');
+SELECT xml '<!DOCTYPE a><a/><b/>';
SET XML OPTION CONTENT;
EXECUTE foo ('<bar/>');
EXECUTE foo ('good');
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+SELECT xml '<!DOCTYPE a><a/>';
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+SELECT xml '<!DOCTYPE a><a/><b/>';
-- Test backwards parsing