Commit 29dcf7de authored by Andrew Dunstan's avatar Andrew Dunstan

Properly detect invalid JSON numbers when generating JSON.

Instead of looking for characters that aren't valid in JSON numbers, we
simply pass the output string through the JSON number parser, and if it
fails the string is quoted. This means among other things that money and
domains over money will be quoted correctly and generate valid JSON.

Fixes bug #8676 reported by Anderson Cristian da Silva.

Backpatched to 9.2 where JSON generation was introduced.
parent a133bf70
...@@ -50,7 +50,7 @@ typedef enum /* contexts of JSON parser */ ...@@ -50,7 +50,7 @@ typedef enum /* contexts of JSON parser */
static inline void json_lex(JsonLexContext *lex); static inline void json_lex(JsonLexContext *lex);
static inline void json_lex_string(JsonLexContext *lex); static inline void json_lex_string(JsonLexContext *lex);
static inline void json_lex_number(JsonLexContext *lex, char *s); static inline void json_lex_number(JsonLexContext *lex, char *s, bool *num_err);
static inline void parse_scalar(JsonLexContext *lex, JsonSemAction *sem); static inline void parse_scalar(JsonLexContext *lex, JsonSemAction *sem);
static void parse_object_field(JsonLexContext *lex, JsonSemAction *sem); static void parse_object_field(JsonLexContext *lex, JsonSemAction *sem);
static void parse_object(JsonLexContext *lex, JsonSemAction *sem); static void parse_object(JsonLexContext *lex, JsonSemAction *sem);
...@@ -147,8 +147,6 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token) ...@@ -147,8 +147,6 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
#define TYPCATEGORY_JSON 'j' #define TYPCATEGORY_JSON 'j'
/* fake category for types that have a cast to json */ /* fake category for types that have a cast to json */
#define TYPCATEGORY_JSON_CAST 'c' #define TYPCATEGORY_JSON_CAST 'c'
/* letters appearing in numeric output that aren't valid in a JSON number */
#define NON_NUMERIC_LETTER "NnAaIiFfTtYy"
/* chars to consider as part of an alphanumeric token */ /* chars to consider as part of an alphanumeric token */
#define JSON_ALPHANUMERIC_CHAR(c) \ #define JSON_ALPHANUMERIC_CHAR(c) \
(((c) >= 'a' && (c) <= 'z') || \ (((c) >= 'a' && (c) <= 'z') || \
...@@ -567,7 +565,7 @@ json_lex(JsonLexContext *lex) ...@@ -567,7 +565,7 @@ json_lex(JsonLexContext *lex)
break; break;
case '-': case '-':
/* Negative number. */ /* Negative number. */
json_lex_number(lex, s + 1); json_lex_number(lex, s + 1, NULL);
lex->token_type = JSON_TOKEN_NUMBER; lex->token_type = JSON_TOKEN_NUMBER;
break; break;
case '0': case '0':
...@@ -581,7 +579,7 @@ json_lex(JsonLexContext *lex) ...@@ -581,7 +579,7 @@ json_lex(JsonLexContext *lex)
case '8': case '8':
case '9': case '9':
/* Positive number. */ /* Positive number. */
json_lex_number(lex, s); json_lex_number(lex, s, NULL);
lex->token_type = JSON_TOKEN_NUMBER; lex->token_type = JSON_TOKEN_NUMBER;
break; break;
default: default:
...@@ -903,7 +901,7 @@ json_lex_string(JsonLexContext *lex) ...@@ -903,7 +901,7 @@ json_lex_string(JsonLexContext *lex)
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
static inline void static inline void
json_lex_number(JsonLexContext *lex, char *s) json_lex_number(JsonLexContext *lex, char *s, bool *num_err)
{ {
bool error = false; bool error = false;
char *p; char *p;
...@@ -976,10 +974,19 @@ json_lex_number(JsonLexContext *lex, char *s) ...@@ -976,10 +974,19 @@ json_lex_number(JsonLexContext *lex, char *s)
*/ */
for (p = s; len < lex->input_length && JSON_ALPHANUMERIC_CHAR(*p); p++, len++) for (p = s; len < lex->input_length && JSON_ALPHANUMERIC_CHAR(*p); p++, len++)
error = true; error = true;
lex->prev_token_terminator = lex->token_terminator;
lex->token_terminator = p; if (num_err != NULL)
if (error) {
report_invalid_token(lex); /* let the caller handle the error */
*num_err = error;
}
else
{
lex->prev_token_terminator = lex->token_terminator;
lex->token_terminator = p;
if (error)
report_invalid_token(lex);
}
} }
/* /*
...@@ -1214,6 +1221,8 @@ datum_to_json(Datum val, bool is_null, StringInfo result, ...@@ -1214,6 +1221,8 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{ {
char *outputstr; char *outputstr;
text *jsontext; text *jsontext;
bool numeric_error;
JsonLexContext dummy_lex;
if (is_null) if (is_null)
{ {
...@@ -1237,14 +1246,13 @@ datum_to_json(Datum val, bool is_null, StringInfo result, ...@@ -1237,14 +1246,13 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
break; break;
case TYPCATEGORY_NUMERIC: case TYPCATEGORY_NUMERIC:
outputstr = OidOutputFunctionCall(typoutputfunc, val); outputstr = OidOutputFunctionCall(typoutputfunc, val);
/* /*
* Don't call escape_json here if it's a valid JSON number. * Don't call escape_json here if it's a valid JSON number.
* Numeric output should usually be a valid JSON number and JSON
* numbers shouldn't be quoted. Quote cases like "Nan" and
* "Infinity", however.
*/ */
if (strpbrk(outputstr, NON_NUMERIC_LETTER) == NULL) dummy_lex.input = *outputstr == '-' ? outputstr + 1 : outputstr;
dummy_lex.input_length = strlen(dummy_lex.input);
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
if (! numeric_error)
appendStringInfoString(result, outputstr); appendStringInfoString(result, outputstr);
else else
escape_json(result, outputstr); escape_json(result, outputstr);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment