diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2016-02-03 01:39:08 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2016-02-03 01:39:48 -0500 |
commit | e6ecc93a1747624c4d33fa48d8a2d77319f3400f (patch) | |
tree | 66482d9ca797ee054d80f61d78eb7a40782a7a5c /src/backend/utils/adt/json.c | |
parent | 7d17e683fcc28a1b371c7dd02935728cd2cbf9bf (diff) | |
download | postgresql-e6ecc93a1747624c4d33fa48d8a2d77319f3400f.tar.gz postgresql-e6ecc93a1747624c4d33fa48d8a2d77319f3400f.zip |
Fix IsValidJsonNumber() to notice trailing non-alphanumeric garbage.
Commit e09996ff8dee3f70 was one brick shy of a load: it didn't insist
that the detected JSON number be the whole of the supplied string.
This allowed inputs such as "2016-01-01" to be misdetected as valid JSON
numbers. Per bug #13906 from Dmitry Ryabov.
In passing, be more wary of zero-length input (I'm not sure this can
happen given current callers, but better safe than sorry), and do some
minor cosmetic cleanup.
Diffstat (limited to 'src/backend/utils/adt/json.c')
-rw-r--r-- | src/backend/utils/adt/json.c | 52 |
1 files changed, 33 insertions, 19 deletions
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index c23dcb485bf..844db523241 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -76,7 +76,8 @@ typedef struct JsonAggState static inline void json_lex(JsonLexContext *lex); static inline void json_lex_string(JsonLexContext *lex); -static inline void json_lex_number(JsonLexContext *lex, char *s, bool *num_err); +static inline void json_lex_number(JsonLexContext *lex, char *s, + bool *num_err, int *total_len); static inline void parse_scalar(JsonLexContext *lex, JsonSemAction *sem); static void parse_object_field(JsonLexContext *lex, JsonSemAction *sem); static void parse_object(JsonLexContext *lex, JsonSemAction *sem); @@ -182,13 +183,20 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token) (c) == '_' || \ IS_HIGHBIT_SET(c)) -/* utility function to check if a string is a valid JSON number */ -extern bool +/* + * Utility function to check if a string is a valid JSON number. + * + * str is of length len, and need not be null-terminated. + */ +bool IsValidJsonNumber(const char *str, int len) { bool numeric_error; + int total_len; JsonLexContext dummy_lex; + if (len <= 0) + return false; /* * json_lex_number expects a leading '-' to have been eaten already. @@ -207,9 +215,9 @@ IsValidJsonNumber(const char *str, int len) dummy_lex.input_length = len; } - json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error); + json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error, &total_len); - return !numeric_error; + return (!numeric_error) && (total_len == dummy_lex.input_length); } /* @@ -669,7 +677,7 @@ json_lex(JsonLexContext *lex) break; case '-': /* Negative number. */ - json_lex_number(lex, s + 1, NULL); + json_lex_number(lex, s + 1, NULL, NULL); lex->token_type = JSON_TOKEN_NUMBER; break; case '0': @@ -683,7 +691,7 @@ json_lex(JsonLexContext *lex) case '8': case '9': /* Positive number. */ - json_lex_number(lex, s, NULL); + json_lex_number(lex, s, NULL, NULL); lex->token_type = JSON_TOKEN_NUMBER; break; default: @@ -983,7 +991,7 @@ json_lex_string(JsonLexContext *lex) lex->token_terminator = s + 1; } -/*------------------------------------------------------------------------- +/* * The next token in the input stream is known to be a number; lex it. * * In JSON, a number consists of four parts: @@ -1004,29 +1012,30 @@ json_lex_string(JsonLexContext *lex) * followed by at least one digit.) * * The 's' argument to this function points to the ostensible beginning - * of part 2 - i.e. the character after any optional minus sign, and the + * of part 2 - i.e. the character after any optional minus sign, or the * first character of the string if there is none. * - *------------------------------------------------------------------------- + * If num_err is not NULL, we return an error flag to *num_err rather than + * raising an error for a badly-formed number. Also, if total_len is not NULL + * the distance from lex->input to the token end+1 is returned to *total_len. */ static inline void -json_lex_number(JsonLexContext *lex, char *s, bool *num_err) +json_lex_number(JsonLexContext *lex, char *s, + bool *num_err, int *total_len) { bool error = false; - char *p; - int len; + int len = s - lex->input; - len = s - lex->input; /* Part (1): leading sign indicator. */ /* Caller already did this for us; so do nothing. */ /* Part (2): parse main digit string. */ - if (*s == '0') + if (len < lex->input_length && *s == '0') { s++; len++; } - else if (*s >= '1' && *s <= '9') + else if (len < lex->input_length && *s >= '1' && *s <= '9') { do { @@ -1081,18 +1090,23 @@ json_lex_number(JsonLexContext *lex, char *s, bool *num_err) * here should be considered part of the token for error-reporting * purposes. */ - for (p = s; len < lex->input_length && JSON_ALPHANUMERIC_CHAR(*p); p++, len++) + for (; len < lex->input_length && JSON_ALPHANUMERIC_CHAR(*s); s++, len++) error = true; + if (total_len != NULL) + *total_len = len; + if (num_err != NULL) { - /* let the caller handle the error */ + /* let the caller handle any error */ *num_err = error; } else { + /* return token endpoint */ lex->prev_token_terminator = lex->token_terminator; - lex->token_terminator = p; + lex->token_terminator = s; + /* handle error if any */ if (error) report_invalid_token(lex); } |