Skip to content

Commit e6ecc93

Browse files
committed
Fix IsValidJsonNumber() to notice trailing non-alphanumeric garbage.
Commit e09996f 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.
1 parent 7d17e68 commit e6ecc93

File tree

3 files changed

+43
-29
lines changed

3 files changed

+43
-29
lines changed

contrib/hstore/expected/hstore.out

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,10 +1466,10 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
14661466
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
14671467
(1 row)
14681468

1469-
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
1470-
hstore_to_json_loose
1471-
------------------------------------------------------------------------------------------
1472-
{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "a key": 1}
1469+
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"');
1470+
hstore_to_json_loose
1471+
-------------------------------------------------------------------------------------------------------------
1472+
{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "h": "2016-01-01", "a key": 1}
14731473
(1 row)
14741474

14751475
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=
14841484
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
14851485
(1 row)
14861486

1487-
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
1488-
hstore_to_jsonb_loose
1489-
---------------------------------------------------------------------------------------
1490-
{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "a key": 1}
1487+
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"');
1488+
hstore_to_jsonb_loose
1489+
----------------------------------------------------------------------------------------------------------
1490+
{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "h": "2016-01-01", "a key": 1}
14911491
(1 row)
14921492

14931493
create table test_json_agg (f1 text, f2 hstore);

contrib/hstore/sql/hstore.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,11 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
334334
-- json and jsonb
335335
select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
336336
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
337-
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
337+
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"');
338338

339339
select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
340340
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
341-
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
341+
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"');
342342

343343
create table test_json_agg (f1 text, f2 hstore);
344344
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'),

src/backend/utils/adt/json.c

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ typedef struct JsonAggState
7676

7777
static inline void json_lex(JsonLexContext *lex);
7878
static inline void json_lex_string(JsonLexContext *lex);
79-
static inline void json_lex_number(JsonLexContext *lex, char *s, bool *num_err);
79+
static inline void json_lex_number(JsonLexContext *lex, char *s,
80+
bool *num_err, int *total_len);
8081
static inline void parse_scalar(JsonLexContext *lex, JsonSemAction *sem);
8182
static void parse_object_field(JsonLexContext *lex, JsonSemAction *sem);
8283
static void parse_object(JsonLexContext *lex, JsonSemAction *sem);
@@ -182,13 +183,20 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
182183
(c) == '_' || \
183184
IS_HIGHBIT_SET(c))
184185

185-
/* utility function to check if a string is a valid JSON number */
186-
extern bool
186+
/*
187+
* Utility function to check if a string is a valid JSON number.
188+
*
189+
* str is of length len, and need not be null-terminated.
190+
*/
191+
bool
187192
IsValidJsonNumber(const char *str, int len)
188193
{
189194
bool numeric_error;
195+
int total_len;
190196
JsonLexContext dummy_lex;
191197

198+
if (len <= 0)
199+
return false;
192200

193201
/*
194202
* json_lex_number expects a leading '-' to have been eaten already.
@@ -207,9 +215,9 @@ IsValidJsonNumber(const char *str, int len)
207215
dummy_lex.input_length = len;
208216
}
209217

210-
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
218+
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error, &total_len);
211219

212-
return !numeric_error;
220+
return (!numeric_error) && (total_len == dummy_lex.input_length);
213221
}
214222

215223
/*
@@ -669,7 +677,7 @@ json_lex(JsonLexContext *lex)
669677
break;
670678
case '-':
671679
/* Negative number. */
672-
json_lex_number(lex, s + 1, NULL);
680+
json_lex_number(lex, s + 1, NULL, NULL);
673681
lex->token_type = JSON_TOKEN_NUMBER;
674682
break;
675683
case '0':
@@ -683,7 +691,7 @@ json_lex(JsonLexContext *lex)
683691
case '8':
684692
case '9':
685693
/* Positive number. */
686-
json_lex_number(lex, s, NULL);
694+
json_lex_number(lex, s, NULL, NULL);
687695
lex->token_type = JSON_TOKEN_NUMBER;
688696
break;
689697
default:
@@ -983,7 +991,7 @@ json_lex_string(JsonLexContext *lex)
983991
lex->token_terminator = s + 1;
984992
}
985993

986-
/*-------------------------------------------------------------------------
994+
/*
987995
* The next token in the input stream is known to be a number; lex it.
988996
*
989997
* In JSON, a number consists of four parts:
@@ -1004,29 +1012,30 @@ json_lex_string(JsonLexContext *lex)
10041012
* followed by at least one digit.)
10051013
*
10061014
* The 's' argument to this function points to the ostensible beginning
1007-
* of part 2 - i.e. the character after any optional minus sign, and the
1015+
* of part 2 - i.e. the character after any optional minus sign, or the
10081016
* first character of the string if there is none.
10091017
*
1010-
*-------------------------------------------------------------------------
1018+
* If num_err is not NULL, we return an error flag to *num_err rather than
1019+
* raising an error for a badly-formed number. Also, if total_len is not NULL
1020+
* the distance from lex->input to the token end+1 is returned to *total_len.
10111021
*/
10121022
static inline void
1013-
json_lex_number(JsonLexContext *lex, char *s, bool *num_err)
1023+
json_lex_number(JsonLexContext *lex, char *s,
1024+
bool *num_err, int *total_len)
10141025
{
10151026
bool error = false;
1016-
char *p;
1017-
int len;
1027+
int len = s - lex->input;
10181028

1019-
len = s - lex->input;
10201029
/* Part (1): leading sign indicator. */
10211030
/* Caller already did this for us; so do nothing. */
10221031

10231032
/* Part (2): parse main digit string. */
1024-
if (*s == '0')
1033+
if (len < lex->input_length && *s == '0')
10251034
{
10261035
s++;
10271036
len++;
10281037
}
1029-
else if (*s >= '1' && *s <= '9')
1038+
else if (len < lex->input_length && *s >= '1' && *s <= '9')
10301039
{
10311040
do
10321041
{
@@ -1081,18 +1090,23 @@ json_lex_number(JsonLexContext *lex, char *s, bool *num_err)
10811090
* here should be considered part of the token for error-reporting
10821091
* purposes.
10831092
*/
1084-
for (p = s; len < lex->input_length && JSON_ALPHANUMERIC_CHAR(*p); p++, len++)
1093+
for (; len < lex->input_length && JSON_ALPHANUMERIC_CHAR(*s); s++, len++)
10851094
error = true;
10861095

1096+
if (total_len != NULL)
1097+
*total_len = len;
1098+
10871099
if (num_err != NULL)
10881100
{
1089-
/* let the caller handle the error */
1101+
/* let the caller handle any error */
10901102
*num_err = error;
10911103
}
10921104
else
10931105
{
1106+
/* return token endpoint */
10941107
lex->prev_token_terminator = lex->token_terminator;
1095-
lex->token_terminator = p;
1108+
lex->token_terminator = s;
1109+
/* handle error if any */
10961110
if (error)
10971111
report_invalid_token(lex);
10981112
}

0 commit comments

Comments
 (0)