Skip to content

Commit 1f2b195

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 0b55fef commit 1f2b195

File tree

3 files changed

+38
-24
lines changed

3 files changed

+38
-24
lines changed

contrib/hstore/expected/hstore.out

Lines changed: 4 additions & 4 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
create table test_json_agg (f1 text, f2 hstore);

contrib/hstore/sql/hstore.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
334334
-- json
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
create table test_json_agg (f1 text, f2 hstore);
340340
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
@@ -63,7 +63,8 @@ typedef enum /* type categories for datum_to_json */
6363

6464
static inline void json_lex(JsonLexContext *lex);
6565
static inline void json_lex_string(JsonLexContext *lex);
66-
static inline void json_lex_number(JsonLexContext *lex, char *s, bool *num_err);
66+
static inline void json_lex_number(JsonLexContext *lex, char *s,
67+
bool *num_err, int *total_len);
6768
static inline void parse_scalar(JsonLexContext *lex, JsonSemAction *sem);
6869
static void parse_object_field(JsonLexContext *lex, JsonSemAction *sem);
6970
static void parse_object(JsonLexContext *lex, JsonSemAction *sem);
@@ -166,13 +167,20 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
166167
(c) == '_' || \
167168
IS_HIGHBIT_SET(c))
168169

169-
/* utility function to check if a string is a valid JSON number */
170-
extern bool
170+
/*
171+
* Utility function to check if a string is a valid JSON number.
172+
*
173+
* str is of length len, and need not be null-terminated.
174+
*/
175+
bool
171176
IsValidJsonNumber(const char *str, int len)
172177
{
173178
bool numeric_error;
179+
int total_len;
174180
JsonLexContext dummy_lex;
175181

182+
if (len <= 0)
183+
return false;
176184

177185
/*
178186
* json_lex_number expects a leading '-' to have been eaten already.
@@ -191,9 +199,9 @@ IsValidJsonNumber(const char *str, int len)
191199
dummy_lex.input_length = len;
192200
}
193201

194-
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
202+
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error, &total_len);
195203

196-
return !numeric_error;
204+
return (!numeric_error) && (total_len == dummy_lex.input_length);
197205
}
198206

199207
/*
@@ -610,7 +618,7 @@ json_lex(JsonLexContext *lex)
610618
break;
611619
case '-':
612620
/* Negative number. */
613-
json_lex_number(lex, s + 1, NULL);
621+
json_lex_number(lex, s + 1, NULL, NULL);
614622
lex->token_type = JSON_TOKEN_NUMBER;
615623
break;
616624
case '0':
@@ -624,7 +632,7 @@ json_lex(JsonLexContext *lex)
624632
case '8':
625633
case '9':
626634
/* Positive number. */
627-
json_lex_number(lex, s, NULL);
635+
json_lex_number(lex, s, NULL, NULL);
628636
lex->token_type = JSON_TOKEN_NUMBER;
629637
break;
630638
default:
@@ -919,7 +927,7 @@ json_lex_string(JsonLexContext *lex)
919927
lex->token_terminator = s + 1;
920928
}
921929

922-
/*-------------------------------------------------------------------------
930+
/*
923931
* The next token in the input stream is known to be a number; lex it.
924932
*
925933
* In JSON, a number consists of four parts:
@@ -940,29 +948,30 @@ json_lex_string(JsonLexContext *lex)
940948
* followed by at least one digit.)
941949
*
942950
* The 's' argument to this function points to the ostensible beginning
943-
* of part 2 - i.e. the character after any optional minus sign, and the
951+
* of part 2 - i.e. the character after any optional minus sign, or the
944952
* first character of the string if there is none.
945953
*
946-
*-------------------------------------------------------------------------
954+
* If num_err is not NULL, we return an error flag to *num_err rather than
955+
* raising an error for a badly-formed number. Also, if total_len is not NULL
956+
* the distance from lex->input to the token end+1 is returned to *total_len.
947957
*/
948958
static inline void
949-
json_lex_number(JsonLexContext *lex, char *s, bool *num_err)
959+
json_lex_number(JsonLexContext *lex, char *s,
960+
bool *num_err, int *total_len)
950961
{
951962
bool error = false;
952-
char *p;
953-
int len;
963+
int len = s - lex->input;
954964

955-
len = s - lex->input;
956965
/* Part (1): leading sign indicator. */
957966
/* Caller already did this for us; so do nothing. */
958967

959968
/* Part (2): parse main digit string. */
960-
if (*s == '0')
969+
if (len < lex->input_length && *s == '0')
961970
{
962971
s++;
963972
len++;
964973
}
965-
else if (*s >= '1' && *s <= '9')
974+
else if (len < lex->input_length && *s >= '1' && *s <= '9')
966975
{
967976
do
968977
{
@@ -1017,18 +1026,23 @@ json_lex_number(JsonLexContext *lex, char *s, bool *num_err)
10171026
* here should be considered part of the token for error-reporting
10181027
* purposes.
10191028
*/
1020-
for (p = s; len < lex->input_length && JSON_ALPHANUMERIC_CHAR(*p); p++, len++)
1029+
for (; len < lex->input_length && JSON_ALPHANUMERIC_CHAR(*s); s++, len++)
10211030
error = true;
10221031

1032+
if (total_len != NULL)
1033+
*total_len = len;
1034+
10231035
if (num_err != NULL)
10241036
{
1025-
/* let the caller handle the error */
1037+
/* let the caller handle any error */
10261038
*num_err = error;
10271039
}
10281040
else
10291041
{
1042+
/* return token endpoint */
10301043
lex->prev_token_terminator = lex->token_terminator;
1031-
lex->token_terminator = p;
1044+
lex->token_terminator = s;
1045+
/* handle error if any */
10321046
if (error)
10331047
report_invalid_token(lex);
10341048
}

0 commit comments

Comments
 (0)