From e222189360ff5a6201d42582a0fe15abc9d4cc11 Mon Sep 17 00:00:00 2001 From: Denis Gregor Date: Sun, 16 Aug 2026 20:26:15 +0300 Subject: [PATCH] Check the whole RFC 8259 number grammar in strict mode Strict mode rejected leading zeros but nothing else about the shape of a number, so several malformed numbers parsed and were then written back out verbatim, producing JSON that other parsers reject: $ echo '[2.e3]' | ./json_parse -s - [ 2.e3 ] RFC 8259 requires a mandatory integer part and at least one digit in both the fraction and the exponent: number = [ minus ] int [ frac ] [ exp ] int = zero / ( digit1-9 *DIGIT ) frac = decimal-point 1*DIGIT exp = e [ minus / plus ] 1*DIGIT so "1.", "-2.", "2.e3", "2.e+3", "0.e1", "-.123", "1e" and "1e+" are all invalid. Walk the accumulated text against that grammar instead. The leading zero rule from the previous check is part of the same walk rather than a separate test, and its cases keep their coverage. Only JSON_TOKENER_STRICT is affected; the default tokener stays as lenient as it was. Measured against JSONTestSuite (318 files): strict mode went from 44 to 37 files accepted that the suite says must be rejected, default mode stayed at 71, and nothing that must be accepted regressed in either mode. --- json_tokener.c | 62 +++++++++++++++++++++++++++++++++++---- tests/test_parse.c | 18 ++++++++++++ tests/test_parse.expected | 15 +++++++++- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/json_tokener.c b/json_tokener.c index b723f13..cfa01ab 100644 --- a/json_tokener.c +++ b/json_tokener.c @@ -1045,15 +1045,67 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * } if (tok->flags & JSON_TOKENER_STRICT) { - /* RFC 8259 forbids leading zeros in the integer part: - * a '0' may only be followed by '.', 'e'/'E' or the end - * of the number, so "01", "00" and "-0123" are invalid - * while "0", "-0" and "0.5" remain valid. + /* Check the accumulated text against the RFC 8259 grammar: + * + * number = [ minus ] int [ frac ] [ exp ] + * int = zero / ( digit1-9 *DIGIT ) + * frac = decimal-point 1*DIGIT + * exp = e [ minus / plus ] 1*DIGIT + * + * so the integer part is mandatory and may not carry a + * leading zero, and both the fraction and the exponent + * need at least one digit of their own. That rejects + * "01", ".5", "-.123", "1.", "2.e3" and "1e", while + * "0", "-0", "0.5" and "2e3" stay valid. */ const char *num = tok->pb->buf; if (*num == '-') num++; - if (num[0] == '0' && num[1] >= '0' && num[1] <= '9') + if (*num == '0') + { + num++; + if (*num >= '0' && *num <= '9') + { + tok->err = json_tokener_error_parse_number; + goto out; + } + } + else if (*num >= '1' && *num <= '9') + { + while (*num >= '0' && *num <= '9') + num++; + } + else + { + /* no integer part at all, e.g. ".5" or "-.123" */ + tok->err = json_tokener_error_parse_number; + goto out; + } + if (*num == '.') + { + num++; + if (!(*num >= '0' && *num <= '9')) + { + tok->err = json_tokener_error_parse_number; + goto out; + } + while (*num >= '0' && *num <= '9') + num++; + } + if (*num == 'e' || *num == 'E') + { + num++; + if (*num == '+' || *num == '-') + num++; + if (!(*num >= '0' && *num <= '9')) + { + tok->err = json_tokener_error_parse_number; + goto out; + } + while (*num >= '0' && *num <= '9') + num++; + } + if (*num != '\0') { tok->err = json_tokener_error_parse_number; goto out; diff --git a/tests/test_parse.c b/tests/test_parse.c index 897863a..220ce5e 100644 --- a/tests/test_parse.c +++ b/tests/test_parse.c @@ -375,6 +375,24 @@ struct incremental_step /* ... but a lone zero, "-0" and a zero before the fraction stay valid. */ {"[-0]", -1, -1, json_tokener_success, 1, JSON_TOKENER_STRICT}, {"[0.5]", -1, -1, json_tokener_success, 1, JSON_TOKENER_STRICT}, + /* RFC 8259 wants a digit either side of the decimal point and after the + * exponent marker, so a bare "1." or "2.e3" is not a number ... */ + {"[1.]", -1, 3, json_tokener_error_parse_number, 1, JSON_TOKENER_STRICT}, + {"[-2.]", -1, 4, json_tokener_error_parse_number, 1, JSON_TOKENER_STRICT}, + {"[2.e3]", -1, 5, json_tokener_error_parse_number, 1, JSON_TOKENER_STRICT}, + {"[2.e+3]", -1, 6, json_tokener_error_parse_number, 1, JSON_TOKENER_STRICT}, + {"[0.e1]", -1, 5, json_tokener_error_parse_number, 1, JSON_TOKENER_STRICT}, + {"[1e]", -1, 3, json_tokener_error_parse_number, 1, JSON_TOKENER_STRICT}, + {"[1e+]", -1, 4, json_tokener_error_parse_number, 1, JSON_TOKENER_STRICT}, + /* ... and the integer part is mandatory, so "-.123" is not a number either + * (a bare ".5" never reaches the number state at all). */ + {"[-.123]", -1, 6, json_tokener_error_parse_number, 1, JSON_TOKENER_STRICT}, + /* The well-formed spellings of the same values stay valid. */ + {"[1.0]", -1, -1, json_tokener_success, 1, JSON_TOKENER_STRICT}, + {"[2e3]", -1, -1, json_tokener_success, 1, JSON_TOKENER_STRICT}, + {"[2E-3]", -1, -1, json_tokener_success, 1, JSON_TOKENER_STRICT}, + {"[0.123]", -1, -1, json_tokener_success, 1, JSON_TOKENER_STRICT}, + {"[-0.123]", -1, -1, json_tokener_success, 1, JSON_TOKENER_STRICT}, {"0e+0", 5, 4, json_tokener_success, 1, 0}, {"[0e+0]", -1, -1, json_tokener_success, 1, 0}, diff --git a/tests/test_parse.expected b/tests/test_parse.expected index acf0594..b409a36 100644 --- a/tests/test_parse.expected +++ b/tests/test_parse.expected @@ -163,6 +163,19 @@ json_tokener_parse_ex(tok, [-0123] , 7) ... OK: got correct error: number json_tokener_parse_ex(tok, [01.5] , 6) ... OK: got correct error: number expected json_tokener_parse_ex(tok, [-0] , 4) ... OK: got object of type [array]: [ 0 ] json_tokener_parse_ex(tok, [0.5] , 5) ... OK: got object of type [array]: [ 0.5 ] +json_tokener_parse_ex(tok, [1.] , 4) ... OK: got correct error: number expected +json_tokener_parse_ex(tok, [-2.] , 5) ... OK: got correct error: number expected +json_tokener_parse_ex(tok, [2.e3] , 6) ... OK: got correct error: number expected +json_tokener_parse_ex(tok, [2.e+3] , 7) ... OK: got correct error: number expected +json_tokener_parse_ex(tok, [0.e1] , 6) ... OK: got correct error: number expected +json_tokener_parse_ex(tok, [1e] , 4) ... OK: got correct error: number expected +json_tokener_parse_ex(tok, [1e+] , 5) ... OK: got correct error: number expected +json_tokener_parse_ex(tok, [-.123] , 7) ... OK: got correct error: number expected +json_tokener_parse_ex(tok, [1.0] , 5) ... OK: got object of type [array]: [ 1.0 ] +json_tokener_parse_ex(tok, [2e3] , 5) ... OK: got object of type [array]: [ 2e3 ] +json_tokener_parse_ex(tok, [2E-3] , 6) ... OK: got object of type [array]: [ 2E-3 ] +json_tokener_parse_ex(tok, [0.123] , 7) ... OK: got object of type [array]: [ 0.123 ] +json_tokener_parse_ex(tok, [-0.123] , 8) ... OK: got object of type [array]: [ -0.123 ] json_tokener_parse_ex(tok, 0e+0 , 5) ... OK: got object of type [double]: 0e+0 json_tokener_parse_ex(tok, [0e+0] , 6) ... OK: got object of type [array]: [ 0e+0 ] json_tokener_parse_ex(tok, 0e , 2) ... OK: got correct error: continue @@ -375,5 +388,5 @@ json_tokener_parse_ex(tok, {"":1} , 7) ... OK: got correct error: invalid json_tokener_parse_ex(tok, {"":1} , 7) ... OK: got correct error: invalid string sequence json_tokener_parse_ex(tok, {"":1} , 7) ... OK: got correct error: invalid string sequence json_tokener_parse_ex(tok, {"":1} , 7) ... OK: got correct error: invalid string sequence -End Incremental Tests OK=279 ERROR=0 +End Incremental Tests OK=292 ERROR=0 ==================================