Rewrite libxml error handling to be more robust.

libxml reports some errors (like invalid xmlns attributes) via the error
handler hook, but still returns a success indicator to the library caller.
This causes us to miss some errors that are important to report.  Since the
"generic" error handler hook doesn't know whether the message it's getting
is for an error, warning, or notice, stop using that and instead start
using the "structured" error handler hook, which gets enough information
to be useful.

While at it, arrange to save and restore the error handler hook setting in
each libxml-using function, rather than assuming we can set and forget the
hook.  This should improve the odds of working nicely with third-party
libraries that also use libxml.

In passing, volatile-ize some local variables that get modified within
PG_TRY blocks.  I noticed this while testing with an older gcc version
than I'd previously tried to compile xml.c with.

Florian Pflug and Tom Lane, with extensive review/testing by Noah Misch
This commit is contained in:
Tom Lane 2011-07-20 13:03:12 -04:00
parent d79a601fd9
commit cacd42d62c
10 changed files with 941 additions and 221 deletions

69
configure vendored
View File

@ -23655,6 +23655,75 @@ fi
# Older versions of libxml2 lack the xmlStructuredErrorContext variable
# (which could be a macro referring to a function, if threading is enabled)
if test "$with_libxml" = yes ; then
{ $as_echo "$as_me:$LINENO: checking for xmlStructuredErrorContext" >&5
$as_echo_n "checking for xmlStructuredErrorContext... " >&6; }
if test "${pgac_cv_libxml_structerrctx+set}" = set; then
$as_echo_n "(cached) " >&6
else
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
#include <libxml/globals.h>
void *globptr;
int
main ()
{
globptr = xmlStructuredErrorContext
;
return 0;
}
_ACEOF
rm -f conftest.$ac_objext conftest$ac_exeext
if { (ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\""
$as_echo "$ac_try_echo") >&5
(eval "$ac_link") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
$as_echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_c_werror_flag" ||
test ! -s conftest.err
} && test -s conftest$ac_exeext && {
test "$cross_compiling" = yes ||
$as_test_x conftest$ac_exeext
}; then
pgac_cv_libxml_structerrctx=yes
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
pgac_cv_libxml_structerrctx=no
fi
rm -rf conftest.dSYM
rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
conftest$ac_exeext conftest.$ac_ext
fi
{ $as_echo "$as_me:$LINENO: result: $pgac_cv_libxml_structerrctx" >&5
$as_echo "$pgac_cv_libxml_structerrctx" >&6; }
if test x"$pgac_cv_libxml_structerrctx" = x"yes"; then
cat >>confdefs.h <<\_ACEOF
#define HAVE_XMLSTRUCTUREDERRORCONTEXT 1
_ACEOF
fi
fi
# This test makes sure that run tests work at all. Sometimes a shared # This test makes sure that run tests work at all. Sometimes a shared
# library is found by the linker, but the runtime linker can't find it. # library is found by the linker, but the runtime linker can't find it.
# This check should come after all modifications of compiler or linker # This check should come after all modifications of compiler or linker

View File

@ -1519,6 +1519,23 @@ AC_SUBST(LDAP_LIBS_FE)
AC_SUBST(LDAP_LIBS_BE) AC_SUBST(LDAP_LIBS_BE)
# Older versions of libxml2 lack the xmlStructuredErrorContext variable
# (which could be a macro referring to a function, if threading is enabled)
if test "$with_libxml" = yes ; then
AC_CACHE_CHECK([for xmlStructuredErrorContext], pgac_cv_libxml_structerrctx,
[AC_TRY_LINK([#include <libxml/globals.h>
void *globptr;],
[globptr = xmlStructuredErrorContext],
[pgac_cv_libxml_structerrctx=yes],
[pgac_cv_libxml_structerrctx=no])])
if test x"$pgac_cv_libxml_structerrctx" = x"yes"; then
AC_DEFINE(HAVE_XMLSTRUCTUREDERRORCONTEXT,
1,
[Define to 1 if your libxml has xmlStructuredErrorContext.])
fi
fi
# This test makes sure that run tests work at all. Sometimes a shared # This test makes sure that run tests work at all. Sometimes a shared
# library is found by the linker, but the runtime linker can't find it. # library is found by the linker, but the runtime linker can't find it.
# This check should come after all modifications of compiler or linker # This check should come after all modifications of compiler or linker

View File

@ -38,7 +38,7 @@ Datum xpath_table(PG_FUNCTION_ARGS);
/* exported for use by xslt_proc.c */ /* exported for use by xslt_proc.c */
void pgxml_parser_init(void); PgXmlErrorContext *pgxml_parser_init(PgXmlStrictness strictness);
/* workspace for pgxml_xpath() */ /* workspace for pgxml_xpath() */
@ -68,18 +68,27 @@ static void cleanup_workspace(xpath_workspace *workspace);
/* /*
* Initialize for xml parsing. * Initialize for xml parsing.
*
* As with the underlying pg_xml_init function, calls to this MUST be followed
* by a PG_TRY block that guarantees that pg_xml_done is called.
*/ */
void PgXmlErrorContext *
pgxml_parser_init(void) pgxml_parser_init(PgXmlStrictness strictness)
{ {
PgXmlErrorContext *xmlerrcxt;
/* Set up error handling (we share the core's error handler) */ /* Set up error handling (we share the core's error handler) */
pg_xml_init(); xmlerrcxt = pg_xml_init(strictness);
/* Note: we're assuming an elog cannot be thrown by the following calls */
/* Initialize libxml */ /* Initialize libxml */
xmlInitParser(); xmlInitParser();
xmlSubstituteEntitiesDefault(1); xmlSubstituteEntitiesDefault(1);
xmlLoadExtDtdDefaultValue = 1; xmlLoadExtDtdDefaultValue = 1;
return xmlerrcxt;
} }
@ -98,16 +107,33 @@ Datum
xml_is_well_formed(PG_FUNCTION_ARGS) xml_is_well_formed(PG_FUNCTION_ARGS)
{ {
text *t = PG_GETARG_TEXT_P(0); /* document buffer */ text *t = PG_GETARG_TEXT_P(0); /* document buffer */
bool result = false;
int32 docsize = VARSIZE(t) - VARHDRSZ; int32 docsize = VARSIZE(t) - VARHDRSZ;
xmlDocPtr doctree; xmlDocPtr doctree;
PgXmlErrorContext *xmlerrcxt;
pgxml_parser_init(); xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
doctree = xmlParseMemory((char *) VARDATA(t), docsize); PG_TRY();
if (doctree == NULL) {
PG_RETURN_BOOL(false); /* i.e. not well-formed */ doctree = xmlParseMemory((char *) VARDATA(t), docsize);
xmlFreeDoc(doctree);
PG_RETURN_BOOL(true); result = (doctree != NULL);
if (doctree != NULL)
xmlFreeDoc(doctree);
}
PG_CATCH();
{
pg_xml_done(xmlerrcxt, true);
PG_RE_THROW();
}
PG_END_TRY();
pg_xml_done(xmlerrcxt, false);
PG_RETURN_BOOL(result);
} }
@ -399,41 +425,52 @@ static xmlXPathObjectPtr
pgxml_xpath(text *document, xmlChar *xpath, xpath_workspace *workspace) pgxml_xpath(text *document, xmlChar *xpath, xpath_workspace *workspace)
{ {
int32 docsize = VARSIZE(document) - VARHDRSZ; int32 docsize = VARSIZE(document) - VARHDRSZ;
xmlXPathObjectPtr res; PgXmlErrorContext *xmlerrcxt;
xmlXPathCompExprPtr comppath; xmlXPathCompExprPtr comppath;
workspace->doctree = NULL; workspace->doctree = NULL;
workspace->ctxt = NULL; workspace->ctxt = NULL;
workspace->res = NULL; workspace->res = NULL;
pgxml_parser_init(); xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
workspace->doctree = xmlParseMemory((char *) VARDATA(document), docsize); PG_TRY();
if (workspace->doctree == NULL) {
return NULL; /* not well-formed */ workspace->doctree = xmlParseMemory((char *) VARDATA(document),
docsize);
if (workspace->doctree != NULL)
{
workspace->ctxt = xmlXPathNewContext(workspace->doctree);
workspace->ctxt->node = xmlDocGetRootElement(workspace->doctree);
workspace->ctxt = xmlXPathNewContext(workspace->doctree); /* compile the path */
workspace->ctxt->node = xmlDocGetRootElement(workspace->doctree); comppath = xmlXPathCompile(xpath);
if (comppath == NULL)
xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
"XPath Syntax Error");
/* compile the path */ /* Now evaluate the path expression. */
comppath = xmlXPathCompile(xpath); workspace->res = xmlXPathCompiledEval(comppath, workspace->ctxt);
if (comppath == NULL)
xmlXPathFreeCompExpr(comppath);
}
}
PG_CATCH();
{ {
cleanup_workspace(workspace); cleanup_workspace(workspace);
xml_ereport(ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
"XPath Syntax Error"); pg_xml_done(xmlerrcxt, true);
PG_RE_THROW();
} }
PG_END_TRY();
/* Now evaluate the path expression. */ if (workspace->res == NULL)
res = xmlXPathCompiledEval(comppath, workspace->ctxt);
workspace->res = res;
xmlXPathFreeCompExpr(comppath);
if (res == NULL)
cleanup_workspace(workspace); cleanup_workspace(workspace);
return res; pg_xml_done(xmlerrcxt, false);
return workspace->res;
} }
/* Clean up after processing the result of pgxml_xpath() */ /* Clean up after processing the result of pgxml_xpath() */
@ -534,6 +571,8 @@ xpath_table(PG_FUNCTION_ARGS)
* document */ * document */
bool had_values; /* To determine end of nodeset results */ bool had_values; /* To determine end of nodeset results */
StringInfoData query_buf; StringInfoData query_buf;
PgXmlErrorContext *xmlerrcxt;
volatile xmlDocPtr doctree = NULL;
/* We only have a valid tuple description in table function mode */ /* We only have a valid tuple description in table function mode */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@ -659,14 +698,15 @@ xpath_table(PG_FUNCTION_ARGS)
* Setup the parser. This should happen after we are done evaluating the * Setup the parser. This should happen after we are done evaluating the
* query, in case it calls functions that set up libxml differently. * query, in case it calls functions that set up libxml differently.
*/ */
pgxml_parser_init(); xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
PG_TRY();
{
/* For each row i.e. document returned from SPI */ /* For each row i.e. document returned from SPI */
for (i = 0; i < proc; i++) for (i = 0; i < proc; i++)
{ {
char *pkey; char *pkey;
char *xmldoc; char *xmldoc;
xmlDocPtr doctree;
xmlXPathContextPtr ctxt; xmlXPathContextPtr ctxt;
xmlXPathObjectPtr res; xmlXPathObjectPtr res;
xmlChar *resstr; xmlChar *resstr;
@ -718,11 +758,9 @@ xpath_table(PG_FUNCTION_ARGS)
/* compile the path */ /* compile the path */
comppath = xmlXPathCompile(xpaths[j]); comppath = xmlXPathCompile(xpaths[j]);
if (comppath == NULL) if (comppath == NULL)
{ xml_ereport(xmlerrcxt, ERROR,
xmlFreeDoc(doctree); ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
xml_ereport(ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
"XPath Syntax Error"); "XPath Syntax Error");
}
/* Now evaluate the path expression. */ /* Now evaluate the path expression. */
res = xmlXPathCompiledEval(comppath, ctxt); res = xmlXPathCompiledEval(comppath, ctxt);
@ -737,8 +775,7 @@ xpath_table(PG_FUNCTION_ARGS)
if (res->nodesetval != NULL && if (res->nodesetval != NULL &&
rownr < res->nodesetval->nodeNr) rownr < res->nodesetval->nodeNr)
{ {
resstr = resstr = xmlXPathCastNodeToString(res->nodesetval->nodeTab[rownr]);
xmlXPathCastNodeToString(res->nodesetval->nodeTab[rownr]);
had_values = true; had_values = true;
} }
else else
@ -776,13 +813,31 @@ xpath_table(PG_FUNCTION_ARGS)
} while (had_values); } while (had_values);
} }
xmlFreeDoc(doctree); if (doctree != NULL)
xmlFreeDoc(doctree);
doctree = NULL;
if (pkey) if (pkey)
pfree(pkey); pfree(pkey);
if (xmldoc) if (xmldoc)
pfree(xmldoc); pfree(xmldoc);
} }
}
PG_CATCH();
{
if (doctree != NULL)
xmlFreeDoc(doctree);
pg_xml_done(xmlerrcxt, true);
PG_RE_THROW();
}
PG_END_TRY();
if (doctree != NULL)
xmlFreeDoc(doctree);
pg_xml_done(xmlerrcxt, false);
tuplestore_donestoring(tupstore); tuplestore_donestoring(tupstore);

View File

@ -38,7 +38,7 @@ Datum xslt_process(PG_FUNCTION_ARGS);
#ifdef USE_LIBXSLT #ifdef USE_LIBXSLT
/* declarations to come from xpath.c */ /* declarations to come from xpath.c */
extern void pgxml_parser_init(void); extern PgXmlErrorContext *pgxml_parser_init(PgXmlStrictness strictness);
/* local defs */ /* local defs */
static const char **parse_params(text *paramstr); static const char **parse_params(text *paramstr);
@ -56,13 +56,14 @@ xslt_process(PG_FUNCTION_ARGS)
text *ssheet = PG_GETARG_TEXT_P(1); text *ssheet = PG_GETARG_TEXT_P(1);
text *paramstr; text *paramstr;
const char **params; const char **params;
xsltStylesheetPtr stylesheet = NULL; PgXmlErrorContext *xmlerrcxt;
xmlDocPtr doctree; volatile xsltStylesheetPtr stylesheet = NULL;
xmlDocPtr restree; volatile xmlDocPtr doctree = NULL;
xmlDocPtr ssdoc = NULL; volatile xmlDocPtr restree = NULL;
xmlChar *resstr; volatile xmlDocPtr ssdoc = NULL;
int resstat; volatile int resstat = -1;
int reslen; xmlChar *resstr = NULL;
int reslen = 0;
if (fcinfo->nargs == 3) if (fcinfo->nargs == 3)
{ {
@ -77,9 +78,11 @@ xslt_process(PG_FUNCTION_ARGS)
} }
/* Setup parser */ /* Setup parser */
pgxml_parser_init(); xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
/* Check to see if document is a file or a literal */ PG_TRY();
{
/* Check to see if document is a file or a literal */
if (VARDATA(doct)[0] == '<') if (VARDATA(doct)[0] == '<')
doctree = xmlParseMemory((char *) VARDATA(doct), VARSIZE(doct) - VARHDRSZ); doctree = xmlParseMemory((char *) VARDATA(doct), VARSIZE(doct) - VARHDRSZ);
@ -87,7 +90,7 @@ xslt_process(PG_FUNCTION_ARGS)
doctree = xmlParseFile(text_to_cstring(doct)); doctree = xmlParseFile(text_to_cstring(doct));
if (doctree == NULL) if (doctree == NULL)
xml_ereport(ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
"error parsing XML document"); "error parsing XML document");
/* Same for stylesheet */ /* Same for stylesheet */
@ -96,35 +99,44 @@ xslt_process(PG_FUNCTION_ARGS)
ssdoc = xmlParseMemory((char *) VARDATA(ssheet), ssdoc = xmlParseMemory((char *) VARDATA(ssheet),
VARSIZE(ssheet) - VARHDRSZ); VARSIZE(ssheet) - VARHDRSZ);
if (ssdoc == NULL) if (ssdoc == NULL)
{ xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
xmlFreeDoc(doctree);
xml_ereport(ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
"error parsing stylesheet as XML document"); "error parsing stylesheet as XML document");
}
stylesheet = xsltParseStylesheetDoc(ssdoc); stylesheet = xsltParseStylesheetDoc(ssdoc);
} }
else else
stylesheet = xsltParseStylesheetFile((xmlChar *) text_to_cstring(ssheet)); stylesheet = xsltParseStylesheetFile((xmlChar *) text_to_cstring(ssheet));
if (stylesheet == NULL) if (stylesheet == NULL)
{ xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
xmlFreeDoc(doctree);
xsltCleanupGlobals();
xml_ereport(ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
"failed to parse stylesheet"); "failed to parse stylesheet");
}
restree = xsltApplyStylesheet(stylesheet, doctree, params); restree = xsltApplyStylesheet(stylesheet, doctree, params);
resstat = xsltSaveResultToString(&resstr, &reslen, restree, stylesheet); resstat = xsltSaveResultToString(&resstr, &reslen, restree, stylesheet);
}
PG_CATCH();
{
if (stylesheet != NULL)
xsltFreeStylesheet(stylesheet);
if (restree != NULL)
xmlFreeDoc(restree);
if (doctree != NULL)
xmlFreeDoc(doctree);
xsltCleanupGlobals();
pg_xml_done(xmlerrcxt, true);
PG_RE_THROW();
}
PG_END_TRY();
xsltFreeStylesheet(stylesheet); xsltFreeStylesheet(stylesheet);
xmlFreeDoc(restree); xmlFreeDoc(restree);
xmlFreeDoc(doctree); xmlFreeDoc(doctree);
xsltCleanupGlobals(); xsltCleanupGlobals();
pg_xml_done(xmlerrcxt, false);
if (resstat < 0) if (resstat < 0)
PG_RETURN_NULL(); PG_RETURN_NULL();

View File

@ -85,11 +85,27 @@ int xmloption;
#ifdef USE_LIBXML #ifdef USE_LIBXML
static StringInfo xml_err_buf = NULL; /* random number to identify PgXmlErrorContext */
#define ERRCXT_MAGIC 68275028
static void xml_errorHandler(void *ctxt, const char *msg,...); struct PgXmlErrorContext
{
int magic;
/* strictness argument passed to pg_xml_init */
PgXmlStrictness strictness;
/* current error status and accumulated message, if any */
bool err_occurred;
StringInfoData err_buf;
/* previous libxml error handling state (saved by pg_xml_init) */
xmlStructuredErrorFunc saved_errfunc;
void *saved_errcxt;
};
static void xml_errorHandler(void *data, xmlErrorPtr error);
static void xml_ereport_by_code(int level, int sqlcode, static void xml_ereport_by_code(int level, int sqlcode,
const char *msg, int errcode); const char *msg, int errcode);
static void chopStringInfoNewlines(StringInfo str);
static void appendStringInfoLineSeparator(StringInfo str);
#ifdef USE_LIBXMLCONTEXT #ifdef USE_LIBXMLCONTEXT
@ -552,8 +568,9 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
int i; int i;
ListCell *arg; ListCell *arg;
ListCell *narg; ListCell *narg;
xmlBufferPtr buf = NULL; PgXmlErrorContext *xmlerrcxt;
xmlTextWriterPtr writer = NULL; volatile xmlBufferPtr buf = NULL;
volatile xmlTextWriterPtr writer = NULL;
/* /*
* We first evaluate all the arguments, then start up libxml and create * We first evaluate all the arguments, then start up libxml and create
@ -598,17 +615,17 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
} }
/* now safe to run libxml */ /* now safe to run libxml */
pg_xml_init(); xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
PG_TRY(); PG_TRY();
{ {
buf = xmlBufferCreate(); buf = xmlBufferCreate();
if (!buf) if (buf == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate xmlBuffer"); "could not allocate xmlBuffer");
writer = xmlNewTextWriterMemory(buf, 0); writer = xmlNewTextWriterMemory(buf, 0);
if (!writer) if (writer == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate xmlTextWriter"); "could not allocate xmlTextWriter");
xmlTextWriterStartElement(writer, (xmlChar *) xexpr->name); xmlTextWriterStartElement(writer, (xmlChar *) xexpr->name);
@ -645,12 +662,17 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
xmlFreeTextWriter(writer); xmlFreeTextWriter(writer);
if (buf) if (buf)
xmlBufferFree(buf); xmlBufferFree(buf);
pg_xml_done(xmlerrcxt, true);
PG_RE_THROW(); PG_RE_THROW();
} }
PG_END_TRY(); PG_END_TRY();
xmlBufferFree(buf); xmlBufferFree(buf);
pg_xml_done(xmlerrcxt, false);
return result; return result;
#else #else
NO_XML_SUPPORT(); NO_XML_SUPPORT();
@ -800,7 +822,7 @@ xml_is_document(xmltype *arg)
{ {
#ifdef USE_LIBXML #ifdef USE_LIBXML
bool result; bool result;
xmlDocPtr doc = NULL; volatile xmlDocPtr doc = NULL;
MemoryContext ccxt = CurrentMemoryContext; MemoryContext ccxt = CurrentMemoryContext;
/* We want to catch ereport(INVALID_XML_DOCUMENT) and return false */ /* We want to catch ereport(INVALID_XML_DOCUMENT) and return false */
@ -844,30 +866,24 @@ xml_is_document(xmltype *arg)
#ifdef USE_LIBXML #ifdef USE_LIBXML
/* /*
* pg_xml_init --- set up for use of libxml * pg_xml_init_library --- set up for use of libxml
* *
* This should be called by each function that is about to use libxml * This should be called by each function that is about to use libxml
* facilities. It has two responsibilities: verify compatibility with the * facilities but doesn't require error handling. It initializes libxml
* loaded libxml version (done on first call in a session) and establish * and verifies compatibility with the loaded libxml version. These are
* or re-establish our libxml error handler. The latter needs to be done * once-per-session activities.
* anytime we might have passed control to add-on modules (eg libperl) which
* might have set their own error handler for libxml.
*
* This is exported for use by contrib/xml2, as well as other code that might
* wish to share use of this module's libxml error handler.
* *
* TODO: xmlChar is utf8-char, make proper tuning (initdb with enc!=utf8 and * TODO: xmlChar is utf8-char, make proper tuning (initdb with enc!=utf8 and
* check) * check)
*/ */
void void
pg_xml_init(void) pg_xml_init_library(void)
{ {
static bool first_time = true; static bool first_time = true;
if (first_time) if (first_time)
{ {
/* Stuff we need do only once per session */ /* Stuff we need do only once per session */
MemoryContext oldcontext;
/* /*
* Currently, we have no pure UTF-8 support for internals -- check if * Currently, we have no pure UTF-8 support for internals -- check if
@ -879,16 +895,8 @@ pg_xml_init(void)
errdetail("libxml2 has incompatible char type: sizeof(char)=%u, sizeof(xmlChar)=%u.", errdetail("libxml2 has incompatible char type: sizeof(char)=%u, sizeof(xmlChar)=%u.",
(int) sizeof(char), (int) sizeof(xmlChar)))); (int) sizeof(char), (int) sizeof(xmlChar))));
/* create error buffer in permanent context */
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
xml_err_buf = makeStringInfo();
MemoryContextSwitchTo(oldcontext);
/* Now that xml_err_buf exists, safe to call xml_errorHandler */
xmlSetGenericErrorFunc(NULL, xml_errorHandler);
#ifdef USE_LIBXMLCONTEXT #ifdef USE_LIBXMLCONTEXT
/* Set up memory allocation our way, too */ /* Set up libxml's memory allocation our way */
xml_memory_init(); xml_memory_init();
#endif #endif
@ -897,21 +905,119 @@ pg_xml_init(void)
first_time = false; first_time = false;
} }
else }
{
/* Reset pre-existing buffer to empty */
Assert(xml_err_buf != NULL);
resetStringInfo(xml_err_buf);
/* /*
* We re-establish the error callback function every time. This makes * pg_xml_init --- set up for use of libxml and register an error handler
* it safe for other subsystems (PL/Perl, say) to also use libxml with *
* their own callbacks ... so long as they likewise set up the * This should be called by each function that is about to use libxml
* callbacks on every use. It's cheap enough to not be worth worrying * facilities and requires error handling. It initializes libxml with
* about, anyway. * pg_xml_init_library() and establishes our libxml error handler.
*/ *
xmlSetGenericErrorFunc(NULL, xml_errorHandler); * strictness determines which errors are reported and which are ignored.
} *
* Calls to this function MUST be followed by a PG_TRY block that guarantees
* that pg_xml_done() is called during either normal or error exit.
*
* This is exported for use by contrib/xml2, as well as other code that might
* wish to share use of this module's libxml error handler.
*/
PgXmlErrorContext *
pg_xml_init(PgXmlStrictness strictness)
{
PgXmlErrorContext *errcxt;
/* Do one-time setup if needed */
pg_xml_init_library();
/* Create error handling context structure */
errcxt = (PgXmlErrorContext *) palloc(sizeof(PgXmlErrorContext));
errcxt->magic = ERRCXT_MAGIC;
errcxt->strictness = strictness;
errcxt->err_occurred = false;
initStringInfo(&errcxt->err_buf);
/*
* Save original error handler and install ours. libxml originally didn't
* distinguish between the contexts for generic and for structured error
* handlers. If we're using an old libxml version, we must thus save
* the generic error context, even though we're using a structured
* error handler.
*/
errcxt->saved_errfunc = xmlStructuredError;
#ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT
errcxt->saved_errcxt = xmlStructuredErrorContext;
#else
errcxt->saved_errcxt = xmlGenericErrorContext;
#endif
xmlSetStructuredErrorFunc((void *) errcxt, xml_errorHandler);
return errcxt;
}
/*
* pg_xml_done --- restore previous libxml error handling
*
* Resets libxml's global error-handling state to what it was before
* pg_xml_init() was called.
*
* This routine verifies that all pending errors have been dealt with
* (in assert-enabled builds, anyway).
*/
void
pg_xml_done(PgXmlErrorContext *errcxt, bool isError)
{
void *cur_errcxt;
/* An assert seems like enough protection here */
Assert(errcxt->magic == ERRCXT_MAGIC);
/*
* In a normal exit, there should be no un-handled libxml errors. But we
* shouldn't try to enforce this during error recovery, since the longjmp
* could have been thrown before xml_ereport had a chance to run.
*/
Assert(!errcxt->err_occurred || isError);
/*
* Check that libxml's global state is correct, warn if not. This is
* a real test and not an Assert because it has a higher probability
* of happening.
*/
#ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT
cur_errcxt = xmlStructuredErrorContext;
#else
cur_errcxt = xmlGenericErrorContext;
#endif
if (cur_errcxt != (void *) errcxt)
elog(WARNING, "libxml error handling state is out of sync with xml.c");
/* Restore the saved handler */
xmlSetStructuredErrorFunc(errcxt->saved_errcxt, errcxt->saved_errfunc);
/*
* Mark the struct as invalid, just in case somebody somehow manages to
* call xml_errorHandler or xml_ereport with it.
*/
errcxt->magic = 0;
/* Release memory */
pfree(errcxt->err_buf.data);
pfree(errcxt);
}
/*
* pg_xml_error_occurred() --- test the error flag
*/
bool
pg_xml_error_occurred(PgXmlErrorContext *errcxt)
{
return errcxt->err_occurred;
} }
@ -970,7 +1076,13 @@ parse_xml_decl(const xmlChar *str, size_t *lenp,
int utf8char; int utf8char;
int utf8len; int utf8len;
pg_xml_init(); /*
* Only initialize libxml. We don't need error handling here, but we do
* need to make sure libxml is initialized before calling any of its
* functions. Note that this is safe (and a no-op) if caller has already
* done pg_xml_init().
*/
pg_xml_init_library();
/* Initialize output arguments to "not present" */ /* Initialize output arguments to "not present" */
if (version) if (version)
@ -1124,8 +1236,6 @@ static bool
print_xml_decl(StringInfo buf, const xmlChar *version, print_xml_decl(StringInfo buf, const xmlChar *version,
pg_enc encoding, int standalone) pg_enc encoding, int standalone)
{ {
pg_xml_init(); /* why is this here? */
if ((version && strcmp((char *) version, PG_XML_DEFAULT_VERSION) != 0) if ((version && strcmp((char *) version, PG_XML_DEFAULT_VERSION) != 0)
|| (encoding && encoding != PG_UTF8) || (encoding && encoding != PG_UTF8)
|| standalone != -1) || standalone != -1)
@ -1176,8 +1286,9 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
int32 len; int32 len;
xmlChar *string; xmlChar *string;
xmlChar *utf8string; xmlChar *utf8string;
xmlParserCtxtPtr ctxt; PgXmlErrorContext *xmlerrcxt;
xmlDocPtr doc; volatile xmlParserCtxtPtr ctxt = NULL;
volatile xmlDocPtr doc = NULL;
len = VARSIZE(data) - VARHDRSZ; /* will be useful later */ len = VARSIZE(data) - VARHDRSZ; /* will be useful later */
string = xml_text2xmlChar(data); string = xml_text2xmlChar(data);
@ -1187,18 +1298,19 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
encoding, encoding,
PG_UTF8); PG_UTF8);
/* Start up libxml and its parser (no-ops if already done) */ /* Start up libxml and its parser */
pg_xml_init(); xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_WELLFORMED);
xmlInitParser();
ctxt = xmlNewParserCtxt(); /* Use a TRY block to ensure we clean up correctly */
if (ctxt == NULL)
xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate parser context");
/* Use a TRY block to ensure the ctxt is released */
PG_TRY(); PG_TRY();
{ {
xmlInitParser();
ctxt = xmlNewParserCtxt();
if (ctxt == NULL || xmlerrcxt->err_occurred)
xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate parser context");
if (xmloption_arg == XMLOPTION_DOCUMENT) if (xmloption_arg == XMLOPTION_DOCUMENT)
{ {
/* /*
@ -1213,8 +1325,8 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
"UTF-8", "UTF-8",
XML_PARSE_NOENT | XML_PARSE_DTDATTR XML_PARSE_NOENT | XML_PARSE_DTDATTR
| (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS)); | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS));
if (doc == NULL) if (doc == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
"invalid XML document"); "invalid XML document");
} }
else else
@ -1238,23 +1350,28 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0,
utf8string + count, NULL); utf8string + count, NULL);
if (res_code != 0) if (res_code != 0 || xmlerrcxt->err_occurred)
{ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_CONTENT,
xmlFreeDoc(doc);
xml_ereport(ERROR, ERRCODE_INVALID_XML_CONTENT,
"invalid XML content"); "invalid XML content");
}
} }
} }
PG_CATCH(); PG_CATCH();
{ {
xmlFreeParserCtxt(ctxt); if (doc != NULL)
xmlFreeDoc(doc);
if (ctxt != NULL)
xmlFreeParserCtxt(ctxt);
pg_xml_done(xmlerrcxt, true);
PG_RE_THROW(); PG_RE_THROW();
} }
PG_END_TRY(); PG_END_TRY();
xmlFreeParserCtxt(ctxt); xmlFreeParserCtxt(ctxt);
pg_xml_done(xmlerrcxt, false);
return doc; return doc;
} }
@ -1335,70 +1452,180 @@ xml_pstrdup(const char *string)
* handler. Note that pg_xml_init() *must* have been called previously. * handler. Note that pg_xml_init() *must* have been called previously.
*/ */
void void
xml_ereport(int level, int sqlcode, const char *msg) xml_ereport(PgXmlErrorContext *errcxt, int level, int sqlcode, const char *msg)
{ {
char *detail; char *detail;
/* /* Defend against someone passing us a bogus context struct */
* It might seem that we should just pass xml_err_buf->data directly to if (errcxt->magic != ERRCXT_MAGIC)
* errdetail. However, we want to clean out xml_err_buf before throwing elog(ERROR, "xml_ereport called with invalid PgXmlErrorContext");
* error, in case there is another function using libxml further down the
* call stack. /* Flag that the current libxml error has been reported */
*/ errcxt->err_occurred = false;
if (xml_err_buf->len > 0)
{ /* Include detail only if we have some text from libxml */
detail = pstrdup(xml_err_buf->data); if (errcxt->err_buf.len > 0)
resetStringInfo(xml_err_buf); detail = errcxt->err_buf.data;
}
else else
detail = NULL; detail = NULL;
if (detail) ereport(level,
{ (errcode(sqlcode),
size_t len; errmsg_internal("%s", msg),
detail ? errdetail_internal("%s", detail) : 0));
/* libxml error messages end in '\n'; get rid of it */
len = strlen(detail);
if (len > 0 && detail[len - 1] == '\n')
detail[len - 1] = '\0';
ereport(level,
(errcode(sqlcode),
errmsg_internal("%s", msg),
errdetail_internal("%s", detail)));
}
else
{
ereport(level,
(errcode(sqlcode),
errmsg_internal("%s", msg)));
}
} }
/* /*
* Error handler for libxml error messages * Error handler for libxml errors and warnings
*/ */
static void static void
xml_errorHandler(void *ctxt, const char *msg,...) xml_errorHandler(void *data, xmlErrorPtr error)
{ {
/* Append the formatted text to xml_err_buf */ PgXmlErrorContext *xmlerrcxt = (PgXmlErrorContext *) data;
for (;;) xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) error->ctxt;
xmlParserInputPtr input = (ctxt != NULL) ? ctxt->input : NULL;
xmlNodePtr node = error->node;
const xmlChar *name = (node != NULL &&
node->type == XML_ELEMENT_NODE) ? node->name : NULL;
int domain = error->domain;
int level = error->level;
StringInfo errorBuf;
/* Defend against someone passing us a bogus context struct */
if (xmlerrcxt->magic != ERRCXT_MAGIC)
elog(ERROR, "xml_errorHandler called with invalid PgXmlErrorContext");
/*----------
* Older libxml versions report some errors differently.
* First, some errors were previously reported as coming from the parser
* domain but are now reported as coming from the namespace domain.
* Second, some warnings were upgraded to errors.
* We attempt to compensate for that here.
*----------
*/
switch (error->code)
{ {
va_list args; case XML_WAR_NS_URI:
bool success; level = XML_ERR_ERROR;
domain = XML_FROM_NAMESPACE;
/* Try to format the data. */
va_start(args, msg);
success = appendStringInfoVA(xml_err_buf, msg, args);
va_end(args);
if (success)
break; break;
/* Double the buffer size and try again. */ case XML_ERR_NS_DECL_ERROR:
enlargeStringInfo(xml_err_buf, xml_err_buf->maxlen); case XML_WAR_NS_URI_RELATIVE:
case XML_WAR_NS_COLUMN:
case XML_NS_ERR_XML_NAMESPACE:
case XML_NS_ERR_UNDEFINED_NAMESPACE:
case XML_NS_ERR_QNAME:
case XML_NS_ERR_ATTRIBUTE_REDEFINED:
case XML_NS_ERR_EMPTY:
domain = XML_FROM_NAMESPACE;
break;
} }
/* Decide whether to act on the error or not */
switch (domain)
{
case XML_FROM_PARSER:
case XML_FROM_NONE:
case XML_FROM_MEMORY:
case XML_FROM_IO:
/* Accept error regardless of the parsing purpose */
break;
default:
/* Ignore error if only doing well-formedness check */
if (xmlerrcxt->strictness == PG_XML_STRICTNESS_WELLFORMED)
return;
break;
}
/* Prepare error message in errorBuf */
errorBuf = makeStringInfo();
if (error->line > 0)
appendStringInfo(errorBuf, "line %d: ", error->line);
if (name != NULL)
appendStringInfo(errorBuf, "element %s: ", name);
appendStringInfoString(errorBuf, error->message);
/*
* Append context information to errorBuf.
*
* xmlParserPrintFileContext() uses libxml's "generic" error handler to
* write the context. Since we don't want to duplicate libxml
* functionality here, we set up a generic error handler temporarily.
*
* We use appendStringInfo() directly as libxml's generic error handler.
* This should work because it has essentially the same signature as
* libxml expects, namely (void *ptr, const char *msg, ...).
*/
if (input != NULL)
{
xmlGenericErrorFunc errFuncSaved = xmlGenericError;
void *errCtxSaved = xmlGenericErrorContext;
xmlSetGenericErrorFunc((void *) errorBuf,
(xmlGenericErrorFunc) appendStringInfo);
/* Add context information to errorBuf */
appendStringInfoLineSeparator(errorBuf);
xmlParserPrintFileContext(input);
/* Restore generic error func */
xmlSetGenericErrorFunc(errCtxSaved, errFuncSaved);
}
/* Get rid of any trailing newlines in errorBuf */
chopStringInfoNewlines(errorBuf);
/*
* Legacy error handling mode. err_occurred is never set, we just add the
* message to err_buf. This mode exists because the xml2 contrib module
* uses our error-handling infrastructure, but we don't want to change its
* behaviour since it's deprecated anyway. This is also why we don't
* distinguish between notices, warnings and errors here --- the old-style
* generic error handler wouldn't have done that either.
*/
if (xmlerrcxt->strictness == PG_XML_STRICTNESS_LEGACY)
{
appendStringInfoLineSeparator(&xmlerrcxt->err_buf);
appendStringInfoString(&xmlerrcxt->err_buf, errorBuf->data);
pfree(errorBuf->data);
pfree(errorBuf);
return;
}
/*
* We don't want to ereport() here because that'd probably leave libxml in
* an inconsistent state. Instead, we remember the error and ereport()
* from xml_ereport().
*
* Warnings and notices can be reported immediately since they won't cause
* a longjmp() out of libxml.
*/
if (level >= XML_ERR_ERROR)
{
appendStringInfoLineSeparator(&xmlerrcxt->err_buf);
appendStringInfoString(&xmlerrcxt->err_buf, errorBuf->data);
xmlerrcxt->err_occurred = true;
}
else if (level >= XML_ERR_WARNING)
{
ereport(WARNING,
(errmsg_internal("%s", errorBuf->data)));
}
else
{
ereport(NOTICE,
(errmsg_internal("%s", errorBuf->data)));
}
pfree(errorBuf->data);
pfree(errorBuf);
} }
@ -1447,6 +1674,29 @@ xml_ereport_by_code(int level, int sqlcode,
} }
/*
* Remove all trailing newlines from a StringInfo string
*/
static void
chopStringInfoNewlines(StringInfo str)
{
while (str->len > 0 && str->data[str->len - 1] == '\n')
str->data[--str->len] = '\0';
}
/*
* Append a newline after removing any existing trailing newlines
*/
static void
appendStringInfoLineSeparator(StringInfo str)
{
chopStringInfoNewlines(str);
if (str->len > 0)
appendStringInfoChar(str, '\n');
}
/* /*
* Convert one char in the current server encoding to a Unicode codepoint. * Convert one char in the current server encoding to a Unicode codepoint.
*/ */
@ -1753,21 +2003,22 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
case BYTEAOID: case BYTEAOID:
{ {
bytea *bstr = DatumGetByteaPP(value); bytea *bstr = DatumGetByteaPP(value);
xmlBufferPtr buf = NULL; PgXmlErrorContext *xmlerrcxt;
xmlTextWriterPtr writer = NULL; volatile xmlBufferPtr buf = NULL;
volatile xmlTextWriterPtr writer = NULL;
char *result; char *result;
pg_xml_init(); xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
PG_TRY(); PG_TRY();
{ {
buf = xmlBufferCreate(); buf = xmlBufferCreate();
if (!buf) if (buf == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate xmlBuffer"); "could not allocate xmlBuffer");
writer = xmlNewTextWriterMemory(buf, 0); writer = xmlNewTextWriterMemory(buf, 0);
if (!writer) if (writer == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate xmlTextWriter"); "could not allocate xmlTextWriter");
if (xmlbinary == XMLBINARY_BASE64) if (xmlbinary == XMLBINARY_BASE64)
@ -1789,12 +2040,17 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
xmlFreeTextWriter(writer); xmlFreeTextWriter(writer);
if (buf) if (buf)
xmlBufferFree(buf); xmlBufferFree(buf);
pg_xml_done(xmlerrcxt, true);
PG_RE_THROW(); PG_RE_THROW();
} }
PG_END_TRY(); PG_END_TRY();
xmlBufferFree(buf); xmlBufferFree(buf);
pg_xml_done(xmlerrcxt, false);
return result; return result;
} }
#endif /* USE_LIBXML */ #endif /* USE_LIBXML */
@ -3312,11 +3568,12 @@ static void
xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
int *res_nitems, ArrayBuildState **astate) int *res_nitems, ArrayBuildState **astate)
{ {
xmlParserCtxtPtr ctxt = NULL; PgXmlErrorContext *xmlerrcxt;
xmlDocPtr doc = NULL; volatile xmlParserCtxtPtr ctxt = NULL;
xmlXPathContextPtr xpathctx = NULL; volatile xmlDocPtr doc = NULL;
xmlXPathCompExprPtr xpathcomp = NULL; volatile xmlXPathContextPtr xpathctx = NULL;
xmlXPathObjectPtr xpathobj = NULL; volatile xmlXPathCompExprPtr xpathcomp = NULL;
volatile xmlXPathObjectPtr xpathobj = NULL;
char *datastr; char *datastr;
int32 len; int32 len;
int32 xpath_len; int32 xpath_len;
@ -3382,30 +3639,31 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len); memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
xpath_expr[xpath_len] = '\0'; xpath_expr[xpath_len] = '\0';
pg_xml_init(); xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
xmlInitParser();
PG_TRY(); PG_TRY();
{ {
xmlInitParser();
/* /*
* redundant XML parsing (two parsings for the same value during one * redundant XML parsing (two parsings for the same value during one
* command execution are possible) * command execution are possible)
*/ */
ctxt = xmlNewParserCtxt(); ctxt = xmlNewParserCtxt();
if (ctxt == NULL) if (ctxt == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate parser context"); "could not allocate parser context");
doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0); doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0);
if (doc == NULL) if (doc == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
"could not parse XML document"); "could not parse XML document");
xpathctx = xmlXPathNewContext(doc); xpathctx = xmlXPathNewContext(doc);
if (xpathctx == NULL) if (xpathctx == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
"could not allocate XPath context"); "could not allocate XPath context");
xpathctx->node = xmlDocGetRootElement(doc); xpathctx->node = xmlDocGetRootElement(doc);
if (xpathctx->node == NULL) if (xpathctx->node == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
"could not find root XML element"); "could not find root XML element");
/* register namespaces, if any */ /* register namespaces, if any */
@ -3433,8 +3691,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
} }
xpathcomp = xmlXPathCompile(xpath_expr); xpathcomp = xmlXPathCompile(xpath_expr);
if (xpathcomp == NULL) /* TODO: show proper XPath error details */ if (xpathcomp == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
"invalid XPath expression"); "invalid XPath expression");
/* /*
@ -3445,8 +3703,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
* the same. * the same.
*/ */
xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx); xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
if (xpathobj == NULL) /* TODO: reason? */ if (xpathobj == NULL || xmlerrcxt->err_occurred)
xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
"could not create XPath object"); "could not create XPath object");
/* return empty array in cases when nothing is found */ /* return empty array in cases when nothing is found */
@ -3482,6 +3740,9 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
xmlFreeDoc(doc); xmlFreeDoc(doc);
if (ctxt) if (ctxt)
xmlFreeParserCtxt(ctxt); xmlFreeParserCtxt(ctxt);
pg_xml_done(xmlerrcxt, true);
PG_RE_THROW(); PG_RE_THROW();
} }
PG_END_TRY(); PG_END_TRY();
@ -3491,6 +3752,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
xmlXPathFreeContext(xpathctx); xmlXPathFreeContext(xpathctx);
xmlFreeDoc(doc); xmlFreeDoc(doc);
xmlFreeParserCtxt(ctxt); xmlFreeParserCtxt(ctxt);
pg_xml_done(xmlerrcxt, false);
} }
#endif /* USE_LIBXML */ #endif /* USE_LIBXML */
@ -3579,7 +3842,7 @@ static bool
wellformed_xml(text *data, XmlOptionType xmloption_arg) wellformed_xml(text *data, XmlOptionType xmloption_arg)
{ {
bool result; bool result;
xmlDocPtr doc = NULL; volatile xmlDocPtr doc = NULL;
/* We want to catch any exceptions and return false */ /* We want to catch any exceptions and return false */
PG_TRY(); PG_TRY();

View File

@ -671,6 +671,9 @@
/* Define to 1 if you have the <winldap.h> header file. */ /* Define to 1 if you have the <winldap.h> header file. */
#undef HAVE_WINLDAP_H #undef HAVE_WINLDAP_H
/* Define to 1 if your libxml has xmlStructuredErrorContext. */
#undef HAVE_XMLSTRUCTUREDERRORCONTEXT
/* Define to the appropriate snprintf format for 64-bit ints. */ /* Define to the appropriate snprintf format for 64-bit ints. */
#undef INT64_FORMAT #undef INT64_FORMAT

View File

@ -21,6 +21,31 @@
typedef struct varlena xmltype; typedef struct varlena xmltype;
typedef enum
{
XML_STANDALONE_YES,
XML_STANDALONE_NO,
XML_STANDALONE_NO_VALUE,
XML_STANDALONE_OMITTED
} XmlStandaloneType;
typedef enum
{
XMLBINARY_BASE64,
XMLBINARY_HEX
} XmlBinaryType;
typedef enum
{
PG_XML_STRICTNESS_LEGACY, /* ignore errors unless function result
* indicates error condition */
PG_XML_STRICTNESS_WELLFORMED, /* ignore non-parser messages */
PG_XML_STRICTNESS_ALL /* report all notices/warnings/errors */
} PgXmlStrictness;
/* struct PgXmlErrorContext is private to xml.c */
typedef struct PgXmlErrorContext PgXmlErrorContext;
#define DatumGetXmlP(X) ((xmltype *) PG_DETOAST_DATUM(X)) #define DatumGetXmlP(X) ((xmltype *) PG_DETOAST_DATUM(X))
#define XmlPGetDatum(X) PointerGetDatum(X) #define XmlPGetDatum(X) PointerGetDatum(X)
@ -60,16 +85,13 @@ extern Datum database_to_xml(PG_FUNCTION_ARGS);
extern Datum database_to_xmlschema(PG_FUNCTION_ARGS); extern Datum database_to_xmlschema(PG_FUNCTION_ARGS);
extern Datum database_to_xml_and_xmlschema(PG_FUNCTION_ARGS); extern Datum database_to_xml_and_xmlschema(PG_FUNCTION_ARGS);
typedef enum extern void pg_xml_init_library(void);
{ extern PgXmlErrorContext *pg_xml_init(PgXmlStrictness strictness);
XML_STANDALONE_YES, extern void pg_xml_done(PgXmlErrorContext *errcxt, bool isError);
XML_STANDALONE_NO, extern bool pg_xml_error_occurred(PgXmlErrorContext *errcxt);
XML_STANDALONE_NO_VALUE, extern void xml_ereport(PgXmlErrorContext *errcxt, int level, int sqlcode,
XML_STANDALONE_OMITTED const char *msg);
} XmlStandaloneType;
extern void pg_xml_init(void);
extern void xml_ereport(int level, int sqlcode, const char *msg);
extern xmltype *xmlconcat(List *args); extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExprState *xmlExpr, ExprContext *econtext); extern xmltype *xmlelement(XmlExprState *xmlExpr, ExprContext *econtext);
extern xmltype *xmlparse(text *data, XmlOptionType xmloption, bool preserve_whitespace); extern xmltype *xmlparse(text *data, XmlOptionType xmloption, bool preserve_whitespace);
@ -83,12 +105,6 @@ extern char *map_sql_identifier_to_xml_name(char *ident, bool fully_escaped, boo
extern char *map_xml_name_to_sql_identifier(char *name); extern char *map_xml_name_to_sql_identifier(char *name);
extern char *map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings); extern char *map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings);
typedef enum
{
XMLBINARY_BASE64,
XMLBINARY_HEX
} XmlBinaryType;
extern int xmlbinary; /* XmlBinaryType, but int for guc enum */ extern int xmlbinary; /* XmlBinaryType, but int for guc enum */
extern int xmloption; /* XmlOptionType, but int for guc enum */ extern int xmloption; /* XmlOptionType, but int for guc enum */

View File

@ -8,7 +8,7 @@ INSERT INTO xmltest VALUES (3, '<wrong');
ERROR: invalid XML content ERROR: invalid XML content
LINE 1: INSERT INTO xmltest VALUES (3, '<wrong'); LINE 1: INSERT INTO xmltest VALUES (3, '<wrong');
^ ^
DETAIL: Entity: line 1: parser error : Couldn't find end of Start Tag wrong line 1 DETAIL: line 1: Couldn't find end of Start Tag wrong line 1
<wrong <wrong
^ ^
SELECT * FROM xmltest; SELECT * FROM xmltest;
@ -62,7 +62,7 @@ SELECT xmlconcat('bad', '<syntax');
ERROR: invalid XML content ERROR: invalid XML content
LINE 1: SELECT xmlconcat('bad', '<syntax'); LINE 1: SELECT xmlconcat('bad', '<syntax');
^ ^
DETAIL: Entity: line 1: parser error : Couldn't find end of Start Tag syntax line 1 DETAIL: line 1: Couldn't find end of Start Tag syntax line 1
<syntax <syntax
^ ^
SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>'); SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
@ -206,9 +206,54 @@ SELECT xmlparse(content '<abc>x</abc>');
<abc>x</abc> <abc>x</abc>
(1 row) (1 row)
SELECT xmlparse(content '<invalidentity>&</invalidentity>');
ERROR: invalid XML content
DETAIL: line 1: xmlParseEntityRef: no name
<invalidentity>&</invalidentity>
^
line 1: chunk is not well balanced
<invalidentity>&</invalidentity>
^
SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
ERROR: invalid XML content
DETAIL: line 1: Entity 'idontexist' not defined
<undefinedentity>&idontexist;</undefinedentity>
^
line 1: chunk is not well balanced
<undefinedentity>&idontexist;</undefinedentity>
^
SELECT xmlparse(content '<invalidns xmlns=''&lt;''/>');
xmlparse
---------------------------
<invalidns xmlns='&lt;'/>
(1 row)
SELECT xmlparse(content '<relativens xmlns=''relative''/>');
xmlparse
--------------------------------
<relativens xmlns='relative'/>
(1 row)
SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
ERROR: invalid XML content
DETAIL: line 1: Entity 'idontexist' not defined
<twoerrors>&idontexist;</unbalanced>
^
line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
<twoerrors>&idontexist;</unbalanced>
^
line 1: chunk is not well balanced
<twoerrors>&idontexist;</unbalanced>
^
SELECT xmlparse(content '<nosuchprefix:tag/>');
xmlparse
---------------------
<nosuchprefix:tag/>
(1 row)
SELECT xmlparse(document 'abc'); SELECT xmlparse(document 'abc');
ERROR: invalid XML document ERROR: invalid XML document
DETAIL: Entity: line 1: parser error : Start tag expected, '<' not found DETAIL: line 1: Start tag expected, '<' not found
abc abc
^ ^
SELECT xmlparse(document '<abc>x</abc>'); SELECT xmlparse(document '<abc>x</abc>');
@ -217,6 +262,48 @@ SELECT xmlparse(document '<abc>x</abc>');
<abc>x</abc> <abc>x</abc>
(1 row) (1 row)
SELECT xmlparse(document '<invalidentity>&</abc>');
ERROR: invalid XML document
DETAIL: line 1: xmlParseEntityRef: no name
<invalidentity>&</abc>
^
line 1: Opening and ending tag mismatch: invalidentity line 1 and abc
<invalidentity>&</abc>
^
SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
ERROR: invalid XML document
DETAIL: line 1: Entity 'idontexist' not defined
<undefinedentity>&idontexist;</abc>
^
line 1: Opening and ending tag mismatch: undefinedentity line 1 and abc
<undefinedentity>&idontexist;</abc>
^
SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
xmlparse
---------------------------
<invalidns xmlns='&lt;'/>
(1 row)
SELECT xmlparse(document '<relativens xmlns=''relative''/>');
xmlparse
--------------------------------
<relativens xmlns='relative'/>
(1 row)
SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
ERROR: invalid XML document
DETAIL: line 1: Entity 'idontexist' not defined
<twoerrors>&idontexist;</unbalanced>
^
line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
<twoerrors>&idontexist;</unbalanced>
^
SELECT xmlparse(document '<nosuchprefix:tag/>');
xmlparse
---------------------
<nosuchprefix:tag/>
(1 row)
SELECT xmlpi(name foo); SELECT xmlpi(name foo);
xmlpi xmlpi
--------- ---------
@ -379,7 +466,7 @@ SELECT '<>' IS NOT DOCUMENT;
ERROR: invalid XML content ERROR: invalid XML content
LINE 1: SELECT '<>' IS NOT DOCUMENT; LINE 1: SELECT '<>' IS NOT DOCUMENT;
^ ^
DETAIL: Entity: line 1: parser error : StartTag: invalid element name DETAIL: line 1: StartTag: invalid element name
<> <>
^ ^
SELECT xmlagg(data) FROM xmltest; SELECT xmlagg(data) FROM xmltest;
@ -425,7 +512,7 @@ EXECUTE foo ('bad');
ERROR: invalid XML document ERROR: invalid XML document
LINE 1: EXECUTE foo ('bad'); LINE 1: EXECUTE foo ('bad');
^ ^
DETAIL: Entity: line 1: parser error : Start tag expected, '<' not found DETAIL: line 1: Start tag expected, '<' not found
bad bad
^ ^
SET XML OPTION CONTENT; SET XML OPTION CONTENT;
@ -679,6 +766,36 @@ SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</p
t t
(1 row) (1 row)
SELECT xml_is_well_formed('<invalidentity>&</abc>');
xml_is_well_formed
--------------------
f
(1 row)
SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
xml_is_well_formed
--------------------
f
(1 row)
SELECT xml_is_well_formed('<invalidns xmlns=''&lt;''/>');
xml_is_well_formed
--------------------
t
(1 row)
SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
xml_is_well_formed
--------------------
t
(1 row)
SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
xml_is_well_formed
--------------------
f
(1 row)
SET xmloption TO CONTENT; SET xmloption TO CONTENT;
SELECT xml_is_well_formed('abc'); SELECT xml_is_well_formed('abc');
xml_is_well_formed xml_is_well_formed
@ -686,3 +803,36 @@ SELECT xml_is_well_formed('abc');
t t
(1 row) (1 row)
-- Since xpath() deals with namespaces, it's a bit stricter about
-- what's well-formed and what's not. If we don't obey these rules
-- (i.e. ignore namespace-related errors from libxml), xpath()
-- fails in subtle ways. The following would for example produce
-- the xml value
-- <invalidns xmlns='<'/>
-- which is invalid because '<' may not appear un-escaped in
-- attribute values.
-- Since different libxml versions emit slightly different
-- error messages, we suppress the DETAIL in this test.
\set VERBOSITY terse
SELECT xpath('/*', '<invalidns xmlns=''&lt;''/>');
ERROR: could not parse XML document
\set VERBOSITY default
-- Again, the XML isn't well-formed for namespace purposes
SELECT xpath('/*', '<nosuchprefix:tag/>');
ERROR: could not parse XML document
DETAIL: line 1: Namespace prefix nosuchprefix on tag is not defined
<nosuchprefix:tag/>
^
CONTEXT: SQL function "xpath" statement 1
-- XPath deprecates relative namespaces, but they're not supposed to
-- throw an error, only a warning.
SELECT xpath('/*', '<relativens xmlns=''relative''/>');
WARNING: line 1: xmlns: URI relative is not absolute
<relativens xmlns='relative'/>
^
CONTEXT: SQL function "xpath" statement 1
xpath
--------------------------------------
{"<relativens xmlns=\"relative\"/>"}
(1 row)

View File

@ -172,6 +172,30 @@ SELECT xmlparse(content '<abc>x</abc>');
ERROR: unsupported XML feature ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support. DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml. HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(content '<invalidentity>&</invalidentity>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(content '<invalidns xmlns=''&lt;''/>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(content '<relativens xmlns=''relative''/>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(content '<nosuchprefix:tag/>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(document 'abc'); SELECT xmlparse(document 'abc');
ERROR: unsupported XML feature ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support. DETAIL: This functionality requires the server to be built with libxml support.
@ -180,6 +204,30 @@ SELECT xmlparse(document '<abc>x</abc>');
ERROR: unsupported XML feature ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support. DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml. HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(document '<invalidentity>&</abc>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(document '<relativens xmlns=''relative''/>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlparse(document '<nosuchprefix:tag/>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xmlpi(name foo); SELECT xmlpi(name foo);
ERROR: unsupported XML feature ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support. DETAIL: This functionality requires the server to be built with libxml support.
@ -627,8 +675,57 @@ SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</p
ERROR: unsupported XML feature ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support. DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml. HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xml_is_well_formed('<invalidentity>&</abc>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xml_is_well_formed('<invalidns xmlns=''&lt;''/>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
SET xmloption TO CONTENT; SET xmloption TO CONTENT;
SELECT xml_is_well_formed('abc'); SELECT xml_is_well_formed('abc');
ERROR: unsupported XML feature ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support. DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml. HINT: You need to rebuild PostgreSQL using --with-libxml.
-- Since xpath() deals with namespaces, it's a bit stricter about
-- what's well-formed and what's not. If we don't obey these rules
-- (i.e. ignore namespace-related errors from libxml), xpath()
-- fails in subtle ways. The following would for example produce
-- the xml value
-- <invalidns xmlns='<'/>
-- which is invalid because '<' may not appear un-escaped in
-- attribute values.
-- Since different libxml versions emit slightly different
-- error messages, we suppress the DETAIL in this test.
\set VERBOSITY terse
SELECT xpath('/*', '<invalidns xmlns=''&lt;''/>');
ERROR: unsupported XML feature at character 20
\set VERBOSITY default
-- Again, the XML isn't well-formed for namespace purposes
SELECT xpath('/*', '<nosuchprefix:tag/>');
ERROR: unsupported XML feature
LINE 1: SELECT xpath('/*', '<nosuchprefix:tag/>');
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.
-- XPath deprecates relative namespaces, but they're not supposed to
-- throw an error, only a warning.
SELECT xpath('/*', '<relativens xmlns=''relative''/>');
ERROR: unsupported XML feature
LINE 1: SELECT xpath('/*', '<relativens xmlns=''relative''/>');
^
DETAIL: This functionality requires the server to be built with libxml support.
HINT: You need to rebuild PostgreSQL using --with-libxml.

View File

@ -62,9 +62,21 @@ SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as fun
SELECT xmlparse(content 'abc'); SELECT xmlparse(content 'abc');
SELECT xmlparse(content '<abc>x</abc>'); SELECT xmlparse(content '<abc>x</abc>');
SELECT xmlparse(content '<invalidentity>&</invalidentity>');
SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
SELECT xmlparse(content '<invalidns xmlns=''&lt;''/>');
SELECT xmlparse(content '<relativens xmlns=''relative''/>');
SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
SELECT xmlparse(content '<nosuchprefix:tag/>');
SELECT xmlparse(document 'abc'); SELECT xmlparse(document 'abc');
SELECT xmlparse(document '<abc>x</abc>'); SELECT xmlparse(document '<abc>x</abc>');
SELECT xmlparse(document '<invalidentity>&</abc>');
SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
SELECT xmlparse(document '<relativens xmlns=''relative''/>');
SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
SELECT xmlparse(document '<nosuchprefix:tag/>');
SELECT xmlpi(name foo); SELECT xmlpi(name foo);
@ -208,6 +220,32 @@ SELECT xml_is_well_formed('<foo><bar>baz</foo>');
SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>'); SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>'); SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>'); SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
SELECT xml_is_well_formed('<invalidentity>&</abc>');
SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
SELECT xml_is_well_formed('<invalidns xmlns=''&lt;''/>');
SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
SET xmloption TO CONTENT; SET xmloption TO CONTENT;
SELECT xml_is_well_formed('abc'); SELECT xml_is_well_formed('abc');
-- Since xpath() deals with namespaces, it's a bit stricter about
-- what's well-formed and what's not. If we don't obey these rules
-- (i.e. ignore namespace-related errors from libxml), xpath()
-- fails in subtle ways. The following would for example produce
-- the xml value
-- <invalidns xmlns='<'/>
-- which is invalid because '<' may not appear un-escaped in
-- attribute values.
-- Since different libxml versions emit slightly different
-- error messages, we suppress the DETAIL in this test.
\set VERBOSITY terse
SELECT xpath('/*', '<invalidns xmlns=''&lt;''/>');
\set VERBOSITY default
-- Again, the XML isn't well-formed for namespace purposes
SELECT xpath('/*', '<nosuchprefix:tag/>');
-- XPath deprecates relative namespaces, but they're not supposed to
-- throw an error, only a warning.
SELECT xpath('/*', '<relativens xmlns=''relative''/>');