]> granicus.if.org Git - postgresql/commitdiff
Fix IsValidJsonNumber() to notice trailing non-alphanumeric garbage.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 3 Feb 2016 06:39:08 +0000 (01:39 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 3 Feb 2016 06:39:48 +0000 (01:39 -0500)
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.

contrib/hstore/expected/hstore.out
contrib/hstore/sql/hstore.sql
src/backend/utils/adt/json.c

index 9749e45c14392acafb89201dba55a8f3c0663b73..6773a2b72f3d80432f90398d48b390561097c8d9 100644 (file)
@@ -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"}
 (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');
-                                   hstore_to_json_loose                                   
-------------------------------------------------------------------------------------------
- {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "a key": 1}
+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                                             
+-------------------------------------------------------------------------------------------------------------
+ {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "h": "2016-01-01", "a key": 1}
 (1 row)
 
 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"}
 (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');
-                                 hstore_to_jsonb_loose                                 
----------------------------------------------------------------------------------------
- {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "a key": 1}
+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                                           
+----------------------------------------------------------------------------------------------------------
+ {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "h": "2016-01-01", "a key": 1}
 (1 row)
 
 create table test_json_agg (f1 text, f2 hstore);
index 5a9e9ee5ae8b2c18b49f8a31929ed873c282af63..48514789e647c41fa5365e831627035fc9cd1565 100644 (file)
@@ -334,11 +334,11 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
 -- 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 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 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);
 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'),
index c23dcb485bfeeece9e9fc576f1dffbb7177eeb4a..844db523241c236b277e091a567f5a5e5b827e86 100644 (file)
@@ -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);
        }