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.
This commit is contained in:
parent
7d17e683fc
commit
e6ecc93a17
@ -1466,10 +1466,10 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
|
|||||||
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
|
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
|
||||||
hstore_to_json_loose
|
hstore_to_json_loose
|
||||||
------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "a key": 1}
|
{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "h": "2016-01-01", "a key": 1}
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
||||||
@ -1484,10 +1484,10 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
|
|||||||
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
|
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
|
||||||
hstore_to_jsonb_loose
|
hstore_to_jsonb_loose
|
||||||
---------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------------
|
||||||
{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "a key": 1}
|
{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "h": "2016-01-01", "a key": 1}
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
create table test_json_agg (f1 text, f2 hstore);
|
create table test_json_agg (f1 text, f2 hstore);
|
||||||
|
@ -334,11 +334,11 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
|
|||||||
-- json and jsonb
|
-- json and jsonb
|
||||||
select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
||||||
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
|
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
|
||||||
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
|
||||||
|
|
||||||
select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
||||||
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
|
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
|
||||||
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
|
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
|
||||||
|
|
||||||
create table test_json_agg (f1 text, f2 hstore);
|
create table test_json_agg (f1 text, f2 hstore);
|
||||||
insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'),
|
insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'),
|
||||||
|
@ -76,7 +76,8 @@ typedef struct JsonAggState
|
|||||||
|
|
||||||
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, 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 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);
|
||||||
@ -182,13 +183,20 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
|
|||||||
(c) == '_' || \
|
(c) == '_' || \
|
||||||
IS_HIGHBIT_SET(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)
|
IsValidJsonNumber(const char *str, int len)
|
||||||
{
|
{
|
||||||
bool numeric_error;
|
bool numeric_error;
|
||||||
|
int total_len;
|
||||||
JsonLexContext dummy_lex;
|
JsonLexContext dummy_lex;
|
||||||
|
|
||||||
|
if (len <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* json_lex_number expects a leading '-' to have been eaten already.
|
* 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;
|
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;
|
break;
|
||||||
case '-':
|
case '-':
|
||||||
/* Negative number. */
|
/* Negative number. */
|
||||||
json_lex_number(lex, s + 1, NULL);
|
json_lex_number(lex, s + 1, NULL, NULL);
|
||||||
lex->token_type = JSON_TOKEN_NUMBER;
|
lex->token_type = JSON_TOKEN_NUMBER;
|
||||||
break;
|
break;
|
||||||
case '0':
|
case '0':
|
||||||
@ -683,7 +691,7 @@ json_lex(JsonLexContext *lex)
|
|||||||
case '8':
|
case '8':
|
||||||
case '9':
|
case '9':
|
||||||
/* Positive number. */
|
/* Positive number. */
|
||||||
json_lex_number(lex, s, NULL);
|
json_lex_number(lex, s, NULL, NULL);
|
||||||
lex->token_type = JSON_TOKEN_NUMBER;
|
lex->token_type = JSON_TOKEN_NUMBER;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -983,7 +991,7 @@ json_lex_string(JsonLexContext *lex)
|
|||||||
lex->token_terminator = s + 1;
|
lex->token_terminator = s + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
/*
|
||||||
* The next token in the input stream is known to be a number; lex it.
|
* The next token in the input stream is known to be a number; lex it.
|
||||||
*
|
*
|
||||||
* In JSON, a number consists of four parts:
|
* In JSON, a number consists of four parts:
|
||||||
@ -1004,29 +1012,30 @@ json_lex_string(JsonLexContext *lex)
|
|||||||
* followed by at least one digit.)
|
* followed by at least one digit.)
|
||||||
*
|
*
|
||||||
* The 's' argument to this function points to the ostensible beginning
|
* 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.
|
* 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
|
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;
|
bool error = false;
|
||||||
char *p;
|
int len = s - lex->input;
|
||||||
int len;
|
|
||||||
|
|
||||||
len = s - lex->input;
|
|
||||||
/* Part (1): leading sign indicator. */
|
/* Part (1): leading sign indicator. */
|
||||||
/* Caller already did this for us; so do nothing. */
|
/* Caller already did this for us; so do nothing. */
|
||||||
|
|
||||||
/* Part (2): parse main digit string. */
|
/* Part (2): parse main digit string. */
|
||||||
if (*s == '0')
|
if (len < lex->input_length && *s == '0')
|
||||||
{
|
{
|
||||||
s++;
|
s++;
|
||||||
len++;
|
len++;
|
||||||
}
|
}
|
||||||
else if (*s >= '1' && *s <= '9')
|
else if (len < lex->input_length && *s >= '1' && *s <= '9')
|
||||||
{
|
{
|
||||||
do
|
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
|
* here should be considered part of the token for error-reporting
|
||||||
* purposes.
|
* 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;
|
error = true;
|
||||||
|
|
||||||
|
if (total_len != NULL)
|
||||||
|
*total_len = len;
|
||||||
|
|
||||||
if (num_err != NULL)
|
if (num_err != NULL)
|
||||||
{
|
{
|
||||||
/* let the caller handle the error */
|
/* let the caller handle any error */
|
||||||
*num_err = error;
|
*num_err = error;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
/* return token endpoint */
|
||||||
lex->prev_token_terminator = lex->token_terminator;
|
lex->prev_token_terminator = lex->token_terminator;
|
||||||
lex->token_terminator = p;
|
lex->token_terminator = s;
|
||||||
|
/* handle error if any */
|
||||||
if (error)
|
if (error)
|
||||||
report_invalid_token(lex);
|
report_invalid_token(lex);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user