diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 91af030095..bb9364843e 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -66,16 +66,19 @@ #include "funcapi.h" #include "lib/stringinfo.h" #include "libpq/pqformat.h" +#include "nodes/miscnodes.h" #include "miscadmin.h" #include "utils/builtins.h" #include "utils/json.h" #include "utils/jsonpath.h" -static Datum jsonPathFromCstring(char *in, int len); +static Datum jsonPathFromCstring(char *in, int len, struct Node *escontext); static char *jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len); -static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, +static bool flattenJsonPathParseItem(StringInfo buf, int *result, + struct Node *escontext, + JsonPathParseItem *item, int nestingLevel, bool insideArraySubscript); static void alignStringInfoInt(StringInfo buf); static int32 reserveSpaceForItemPointer(StringInfo buf); @@ -95,7 +98,7 @@ jsonpath_in(PG_FUNCTION_ARGS) char *in = PG_GETARG_CSTRING(0); int len = strlen(in); - return jsonPathFromCstring(in, len); + return jsonPathFromCstring(in, len, fcinfo->context); } /* @@ -119,7 +122,7 @@ jsonpath_recv(PG_FUNCTION_ARGS) else elog(ERROR, "unsupported jsonpath version number: %d", version); - return jsonPathFromCstring(str, nbytes); + return jsonPathFromCstring(str, nbytes, NULL); } /* @@ -165,24 +168,29 @@ jsonpath_send(PG_FUNCTION_ARGS) * representation of jsonpath. */ static Datum -jsonPathFromCstring(char *in, int len) +jsonPathFromCstring(char *in, int len, struct Node *escontext) { - JsonPathParseResult *jsonpath = parsejsonpath(in, len); + JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext); JsonPath *res; StringInfoData buf; + if (SOFT_ERROR_OCCURRED(escontext)) + return (Datum) 0; + + if (!jsonpath) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath", + in))); + initStringInfo(&buf); enlargeStringInfo(&buf, 4 * len /* estimation */ ); appendStringInfoSpaces(&buf, JSONPATH_HDRSZ); - if (!jsonpath) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath", - in))); - - flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false); + if (!flattenJsonPathParseItem(&buf, NULL, escontext, + jsonpath->expr, 0, false)) + return (Datum) 0; res = (JsonPath *) buf.data; SET_VARSIZE(res, buf.len); @@ -225,9 +233,10 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len) * Recursive function converting given jsonpath parse item and all its * children into a binary representation. */ -static int -flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, - int nestingLevel, bool insideArraySubscript) +static bool +flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext, + JsonPathParseItem *item, int nestingLevel, + bool insideArraySubscript) { /* position from beginning of jsonpath data */ int32 pos = buf->len - JSONPATH_HDRSZ; @@ -295,16 +304,22 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, int32 left = reserveSpaceForItemPointer(buf); int32 right = reserveSpaceForItemPointer(buf); - chld = !item->value.args.left ? pos : - flattenJsonPathParseItem(buf, item->value.args.left, - nestingLevel + argNestingLevel, - insideArraySubscript); + if (!item->value.args.left) + chld = pos; + else if (! flattenJsonPathParseItem(buf, &chld, escontext, + item->value.args.left, + nestingLevel + argNestingLevel, + insideArraySubscript)) + return false; *(int32 *) (buf->data + left) = chld - pos; - chld = !item->value.args.right ? pos : - flattenJsonPathParseItem(buf, item->value.args.right, - nestingLevel + argNestingLevel, - insideArraySubscript); + if (!item->value.args.right) + chld = pos; + else if (! flattenJsonPathParseItem(buf, &chld, escontext, + item->value.args.right, + nestingLevel + argNestingLevel, + insideArraySubscript)) + return false; *(int32 *) (buf->data + right) = chld - pos; } break; @@ -323,9 +338,11 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, item->value.like_regex.patternlen); appendStringInfoChar(buf, '\0'); - chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr, - nestingLevel, - insideArraySubscript); + if (! flattenJsonPathParseItem(buf, &chld, escontext, + item->value.like_regex.expr, + nestingLevel, + insideArraySubscript)) + return false; *(int32 *) (buf->data + offs) = chld - pos; } break; @@ -341,10 +358,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, { int32 arg = reserveSpaceForItemPointer(buf); - chld = !item->value.arg ? pos : - flattenJsonPathParseItem(buf, item->value.arg, - nestingLevel + argNestingLevel, - insideArraySubscript); + if (!item->value.arg) + chld = pos; + else if (! flattenJsonPathParseItem(buf, &chld, escontext, + item->value.arg, + nestingLevel + argNestingLevel, + insideArraySubscript)) + return false; *(int32 *) (buf->data + arg) = chld - pos; } break; @@ -357,13 +377,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, break; case jpiCurrent: if (nestingLevel <= 0) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("@ is not allowed in root expressions"))); break; case jpiLast: if (!insideArraySubscript) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("LAST is allowed only in array subscripts"))); break; @@ -383,15 +403,22 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, { int32 *ppos; int32 topos; - int32 frompos = - flattenJsonPathParseItem(buf, - item->value.array.elems[i].from, - nestingLevel, true) - pos; + int32 frompos; + + if (! flattenJsonPathParseItem(buf, &frompos, escontext, + item->value.array.elems[i].from, + nestingLevel, true)) + return false; + frompos -= pos; if (item->value.array.elems[i].to) - topos = flattenJsonPathParseItem(buf, - item->value.array.elems[i].to, - nestingLevel, true) - pos; + { + if (! flattenJsonPathParseItem(buf, &topos, escontext, + item->value.array.elems[i].to, + nestingLevel, true)) + return false; + topos -= pos; + } else topos = 0; @@ -424,12 +451,17 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, if (item->next) { - chld = flattenJsonPathParseItem(buf, item->next, nestingLevel, - insideArraySubscript) - pos; + if (! flattenJsonPathParseItem(buf, &chld, escontext, + item->next, nestingLevel, + insideArraySubscript)) + return false; + chld -= pos; *(int32 *) (buf->data + next) = chld; } - return pos; + if (result) + *result = pos; + return true; } /* diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index e758616eb8..4335e1c282 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1721,7 +1721,8 @@ executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg, cxt->regex = cstring_to_text_with_len(jsp->content.like_regex.pattern, jsp->content.like_regex.patternlen); - cxt->cflags = jspConvertRegexFlags(jsp->content.like_regex.flags); + (void) jspConvertRegexFlags(jsp->content.like_regex.flags, + &(cxt->cflags), NULL); } if (RE_compile_and_execute(cxt->regex, str->val.string.val, diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 2a56629cc3..a460fa1bf7 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -38,9 +38,11 @@ static JsonPathParseItem *makeItemUnary(JsonPathItemType type, static JsonPathParseItem *makeItemList(List *list); static JsonPathParseItem *makeIndexArray(List *list); static JsonPathParseItem *makeAny(int first, int last); -static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, - JsonPathString *pattern, - JsonPathString *flags); +static bool makeItemLikeRegex(JsonPathParseItem *expr, + JsonPathString *pattern, + JsonPathString *flags, + JsonPathParseItem ** result, + struct Node *escontext); /* * Bison doesn't allocate anything that needs to live across parser calls, @@ -57,6 +59,9 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, %expect 0 %name-prefix="jsonpath_yy" %parse-param {JsonPathParseResult **result} +%parse-param {struct Node *escontext} +%lex-param {JsonPathParseResult **result} +%lex-param {struct Node *escontext} %union { @@ -163,9 +168,20 @@ predicate: { $$ = makeItemUnary(jpiIsUnknown, $2); } | expr STARTS_P WITH_P starts_with_initial { $$ = makeItemBinary(jpiStartsWith, $1, $4); } - | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); } + | expr LIKE_REGEX_P STRING_P + { + JsonPathParseItem *jppitem; + if (! makeItemLikeRegex($1, &$3, NULL, &jppitem, escontext)) + YYABORT; + $$ = jppitem; + } | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P - { $$ = makeItemLikeRegex($1, &$3, &$5); } + { + JsonPathParseItem *jppitem; + if (! makeItemLikeRegex($1, &$3, &$5, &jppitem, escontext)) + YYABORT; + $$ = jppitem; + } ; starts_with_initial: @@ -472,9 +488,10 @@ makeAny(int first, int last) return v; } -static JsonPathParseItem * +static bool makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern, - JsonPathString *flags) + JsonPathString *flags, JsonPathParseItem ** result, + struct Node *escontext) { JsonPathParseItem *v = makeItemType(jpiLikeRegex); int i; @@ -506,7 +523,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern, v->value.like_regex.flags |= JSP_REGEX_QUOTE; break; default: - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid input syntax for type %s", "jsonpath"), errdetail("Unrecognized flag character \"%.*s\" in LIKE_REGEX predicate.", @@ -515,22 +532,48 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern, } } - /* Convert flags to what RE_compile_and_cache needs */ - cflags = jspConvertRegexFlags(v->value.like_regex.flags); + /* Convert flags to what pg_regcomp needs */ + if ( !jspConvertRegexFlags(v->value.like_regex.flags, &cflags, escontext)) + return false; /* check regex validity */ - (void) RE_compile_and_cache(cstring_to_text_with_len(pattern->val, - pattern->len), - cflags, DEFAULT_COLLATION_OID); + { + regex_t re_tmp; + pg_wchar *wpattern; + int wpattern_len; + int re_result; - return v; + wpattern = (pg_wchar *) palloc((pattern->len + 1) * sizeof(pg_wchar)); + wpattern_len = pg_mb2wchar_with_len(pattern->val, + wpattern, + pattern->len); + + if ((re_result = pg_regcomp(&re_tmp, wpattern, wpattern_len, cflags, + DEFAULT_COLLATION_OID)) != REG_OKAY) + { + char errMsg[100]; + + /* See regexp.c for explanation */ + CHECK_FOR_INTERRUPTS(); + pg_regerror(re_result, &re_tmp, errMsg, sizeof(errMsg)); + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("invalid regular expression: %s", errMsg))); + } + + pg_regfree(&re_tmp); + } + + *result = v; + + return true; } /* * Convert from XQuery regex flags to those recognized by our regex library. */ -int -jspConvertRegexFlags(uint32 xflags) +bool +jspConvertRegexFlags(uint32 xflags, int *result, struct Node *escontext) { /* By default, XQuery is very nearly the same as Spencer's AREs */ int cflags = REG_ADVANCED; @@ -561,18 +604,12 @@ jspConvertRegexFlags(uint32 xflags) * XQuery-style ignore-whitespace mode. */ if (xflags & JSP_REGEX_WSPACE) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("XQuery \"x\" flag (expanded regular expressions) is not implemented"))); } - /* - * We'll never need sub-match details at execution. While - * RE_compile_and_execute would set this flag anyway, force it on here to - * ensure that the regex cache entries created by makeItemLikeRegex are - * useful. - */ - cflags |= REG_NOSUB; + *result = cflags; - return cflags; + return true; } diff --git a/src/backend/utils/adt/jsonpath_internal.h b/src/backend/utils/adt/jsonpath_internal.h index edfc6191a0..b27716855e 100644 --- a/src/backend/utils/adt/jsonpath_internal.h +++ b/src/backend/utils/adt/jsonpath_internal.h @@ -25,8 +25,14 @@ typedef struct JsonPathString #include "utils/jsonpath.h" #include "jsonpath_gram.h" -extern int jsonpath_yylex(YYSTYPE *yylval_param); -extern int jsonpath_yyparse(JsonPathParseResult **result); -extern void jsonpath_yyerror(JsonPathParseResult **result, const char *message); +#define YY_DECL extern int jsonpath_yylex(YYSTYPE *yylval_param, \ + JsonPathParseResult **result, \ + struct Node *escontext) +YY_DECL; +extern int jsonpath_yyparse(JsonPathParseResult **result, + struct Node *escontext); +extern void jsonpath_yyerror(JsonPathParseResult **result, + struct Node *escontext, + const char *message); #endif /* JSONPATH_INTERNAL_H */ diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 948f379e76..59652c76dc 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -25,6 +25,7 @@ #include "jsonpath_gram.h" #include "mb/pg_wchar.h" +#include "nodes/miscnodes.h" #include "nodes/pg_list.h" } @@ -39,8 +40,8 @@ static int scanbuflen; static void addstring(bool init, char *s, int l); static void addchar(bool init, char c); static enum yytokentype checkKeyword(void); -static void parseUnicode(char *s, int l); -static void parseHexChar(char *s); +static bool parseUnicode(char *s, int l, struct Node *escontext); +static bool parseHexChar(char *s, struct Node *escontext); /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ #undef fprintf @@ -147,25 +148,48 @@ hex_fail \\x{hex_dig}{0,1} \\v { addchar(false, '\v'); } -{unicode}+ { parseUnicode(yytext, yyleng); } +{unicode}+ { + if (!parseUnicode(yytext, yyleng, escontext)) + yyterminate(); + } -{hex_char} { parseHexChar(yytext); } +{hex_char} { + if (!parseHexChar(yytext, escontext)) + yyterminate(); + } -{unicode}*{unicodefail} { jsonpath_yyerror(NULL, "invalid unicode sequence"); } +{unicode}*{unicodefail} { + jsonpath_yyerror(NULL, escontext, + "invalid unicode sequence"); + yyterminate(); + } -{hex_fail} { jsonpath_yyerror(NULL, "invalid hex character sequence"); } +{hex_fail} { + jsonpath_yyerror(NULL, escontext, + "invalid hex character sequence"); + yyterminate(); + } {unicode}+\\ { /* throw back the \\, and treat as unicode */ yyless(yyleng - 1); - parseUnicode(yytext, yyleng); + if (!parseUnicode(yytext, yyleng, escontext)) + yyterminate(); } \\. { addchar(false, yytext[1]); } -\\ { jsonpath_yyerror(NULL, "unexpected end after backslash"); } +\\ { + jsonpath_yyerror(NULL, escontext, + "unexpected end after backslash"); + yyterminate(); + } -<> { jsonpath_yyerror(NULL, "unexpected end of quoted string"); } +<> { + jsonpath_yyerror(NULL, escontext, + "unexpected end of quoted string"); + yyterminate(); + } \" { yylval->str = scanstring; @@ -187,8 +211,12 @@ hex_fail \\x{hex_dig}{0,1} \* { } -<> { jsonpath_yyerror(NULL, "unexpected end of comment"); } - +<> { + jsonpath_yyerror( + NULL, escontext, + "unexpected end of comment"); + yyterminate(); + } \&\& { return AND_P; } \|\| { return OR_P; } @@ -253,11 +281,30 @@ hex_fail \\x{hex_dig}{0,1} return INT_P; } -{realfail} { jsonpath_yyerror(NULL, "invalid numeric literal"); } -{integer_junk} { jsonpath_yyerror(NULL, "trailing junk after numeric literal"); } -{decimal_junk} { jsonpath_yyerror(NULL, "trailing junk after numeric literal"); } -{real_junk} { jsonpath_yyerror(NULL, "trailing junk after numeric literal"); } - +{realfail} { + jsonpath_yyerror( + NULL, escontext, + "invalid numeric literal"); + yyterminate(); + } +{integer_junk} { + jsonpath_yyerror( + NULL, escontext, + "trailing junk after numeric literal"); + yyterminate(); + } +{decimal_junk} { + jsonpath_yyerror( + NULL, escontext, + "trailing junk after numeric literal"); + yyterminate(); + } +{real_junk} { + jsonpath_yyerror( + NULL, escontext, + "trailing junk after numeric literal"); + yyterminate(); + } \" { addchar(true, '\0'); BEGIN xq; @@ -281,18 +328,23 @@ hex_fail \\x{hex_dig}{0,1} /* LCOV_EXCL_STOP */ void -jsonpath_yyerror(JsonPathParseResult **result, const char *message) +jsonpath_yyerror(JsonPathParseResult **result, struct Node *escontext, + const char *message) { + /* don't overwrite escontext if it's already been set */ + if (SOFT_ERROR_OCCURRED(escontext)) + return; + if (*yytext == YY_END_OF_BUFFER_CHAR) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_SYNTAX_ERROR), /* translator: %s is typically "syntax error" */ errmsg("%s at end of jsonpath input", _(message)))); } else { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_SYNTAX_ERROR), /* translator: first %s is typically "syntax error" */ errmsg("%s at or near \"%s\" of jsonpath input", @@ -463,14 +515,14 @@ addchar(bool init, char c) /* Interface to jsonpath parser */ JsonPathParseResult * -parsejsonpath(const char *str, int len) +parsejsonpath(const char *str, int len, struct Node *escontext) { JsonPathParseResult *parseresult; jsonpath_scanner_init(str, len); - if (jsonpath_yyparse((void *) &parseresult) != 0) - jsonpath_yyerror(NULL, "bogus input"); /* shouldn't happen */ + if (jsonpath_yyparse((void *) &parseresult, escontext) != 0) + jsonpath_yyerror(NULL, escontext, "bogus input"); /* shouldn't happen */ jsonpath_scanner_finish(); @@ -478,27 +530,36 @@ parsejsonpath(const char *str, int len) } /* Turn hex character into integer */ -static int -hexval(char c) +static bool +hexval(char c, int *result, struct Node *escontext) { if (c >= '0' && c <= '9') - return c - '0'; + { + *result = c - '0'; + return true; + } if (c >= 'a' && c <= 'f') - return c - 'a' + 0xA; + { + *result = c - 'a' + 0xA; + return true; + } if (c >= 'A' && c <= 'F') - return c - 'A' + 0xA; - jsonpath_yyerror(NULL, "invalid hexadecimal digit"); - return 0; /* not reached */ + { + *result = c - 'A' + 0xA; + return true; + } + jsonpath_yyerror(NULL, escontext, "invalid hexadecimal digit"); + return false; } /* Add given unicode character to scanstring */ -static void -addUnicodeChar(int ch) +static bool +addUnicodeChar(int ch, struct Node *escontext) { if (ch == 0) { /* We can't allow this, since our TEXT type doesn't */ - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), errmsg("unsupported Unicode escape sequence"), errdetail("\\u0000 cannot be converted to text."))); @@ -507,30 +568,42 @@ addUnicodeChar(int ch) { char cbuf[MAX_UNICODE_EQUIVALENT_STRING + 1]; - pg_unicode_to_server(ch, (unsigned char *) cbuf); + /* + * If we're trapping the error status, call the noerror form of the + * conversion function. Otherwise call the normal form which provides + * more detailed errors. + */ + + if (! escontext || ! IsA(escontext, ErrorSaveContext)) + pg_unicode_to_server(ch, (unsigned char *) cbuf); + else if (!pg_unicode_to_server_noerror(ch, (unsigned char *) cbuf)) + ereturn(escontext, false, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("could not convert unicode to server encoding"))); addstring(false, cbuf, strlen(cbuf)); } + return true; } /* Add unicode character, processing any surrogate pairs */ -static void -addUnicode(int ch, int *hi_surrogate) +static bool +addUnicode(int ch, int *hi_surrogate, struct Node *escontext) { if (is_utf16_surrogate_first(ch)) { if (*hi_surrogate != -1) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "jsonpath"), errdetail("Unicode high surrogate must not follow " "a high surrogate."))); *hi_surrogate = ch; - return; + return true; } else if (is_utf16_surrogate_second(ch)) { if (*hi_surrogate == -1) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "jsonpath"), errdetail("Unicode low surrogate must follow a high " @@ -540,22 +613,22 @@ addUnicode(int ch, int *hi_surrogate) } else if (*hi_surrogate != -1) { - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "jsonpath"), errdetail("Unicode low surrogate must follow a high " "surrogate."))); } - addUnicodeChar(ch); + return addUnicodeChar(ch, escontext); } /* * parseUnicode was adopted from json_lex_string() in * src/backend/utils/adt/json.c */ -static void -parseUnicode(char *s, int l) +static bool +parseUnicode(char *s, int l, struct Node *escontext) { int i = 2; int hi_surrogate = -1; @@ -563,41 +636,57 @@ parseUnicode(char *s, int l) for (i = 2; i < l; i += 2) /* skip '\u' */ { int ch = 0; - int j; + int j, si; if (s[i] == '{') /* parse '\u{XX...}' */ { while (s[++i] != '}' && i < l) - ch = (ch << 4) | hexval(s[i]); + { + if (!hexval(s[i], &si, escontext)) + return false; + ch = (ch << 4) | si; + } i++; /* skip '}' */ } else /* parse '\uXXXX' */ { for (j = 0; j < 4 && i < l; j++) - ch = (ch << 4) | hexval(s[i++]); + { + if (!hexval(s[i++], &si, escontext)) + return false; + ch = (ch << 4) | si; + } } - addUnicode(ch, &hi_surrogate); + if (! addUnicode(ch, &hi_surrogate, escontext)) + return false; } if (hi_surrogate != -1) { - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "jsonpath"), errdetail("Unicode low surrogate must follow a high " "surrogate."))); } + + return true; } /* Parse sequence of hex-encoded characters */ -static void -parseHexChar(char *s) +static bool +parseHexChar(char *s, struct Node *escontext) { - int ch = (hexval(s[2]) << 4) | - hexval(s[3]); + int s2, s3, ch; + if (!hexval(s[2], &s2, escontext)) + return false; + if (!hexval(s[3], &s3, escontext)) + return false; - addUnicodeChar(ch); + ch = (s2 << 4) | s3; + + return addUnicodeChar(ch, escontext); } /* diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 13f60cdc09..b5035ceb19 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -254,8 +254,11 @@ typedef struct JsonPathParseResult bool lax; } JsonPathParseResult; -extern JsonPathParseResult *parsejsonpath(const char *str, int len); +extern JsonPathParseResult *parsejsonpath(const char *str, int len, + struct Node *escontext); + +extern bool jspConvertRegexFlags(uint32 xflags, int *result, + struct Node *escontext); -extern int jspConvertRegexFlags(uint32 xflags); #endif diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index fdaac58367..ca0cdf1ab2 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -1032,3 +1032,21 @@ select '1?(2>3)'::jsonpath; (1)?(2 > 3) (1 row) +-- test non-error-throwing API +SELECT str as jsonpath, + pg_input_is_valid(str,'jsonpath') as ok, + pg_input_error_message(str,'jsonpath') as errmsg +FROM unnest(ARRAY['$ ? (@ like_regex "pattern" flag "smixq")'::text, + '$ ? (@ like_regex "pattern" flag "a")', + '@ + 1', + '00', + '1a']) str; + jsonpath | ok | errmsg +-------------------------------------------+----+----------------------------------------------------------------------- + $ ? (@ like_regex "pattern" flag "smixq") | t | + $ ? (@ like_regex "pattern" flag "a") | f | invalid input syntax for type jsonpath + @ + 1 | f | @ is not allowed in root expressions + 00 | f | trailing junk after numeric literal at or near "00" of jsonpath input + 1a | f | trailing junk after numeric literal at or near "1a" of jsonpath input +(5 rows) + diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index d491714614..99d21d2af7 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -187,3 +187,14 @@ select '1..e3'::jsonpath; select '(1.).e'::jsonpath; select '(1.).e3'::jsonpath; select '1?(2>3)'::jsonpath; + +-- test non-error-throwing API + +SELECT str as jsonpath, + pg_input_is_valid(str,'jsonpath') as ok, + pg_input_error_message(str,'jsonpath') as errmsg +FROM unnest(ARRAY['$ ? (@ like_regex "pattern" flag "smixq")'::text, + '$ ? (@ like_regex "pattern" flag "a")', + '@ + 1', + '00', + '1a']) str;