From 725ae2b83f9d43a12ddf65f1ff1fd219b999b65a Mon Sep 17 00:00:00 2001 From: Thomas Joly Date: Mon, 18 Sep 2017 19:21:58 +0200 Subject: [PATCH] add more strict checking --- Makefile | 7 ++- jsmn.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++- jsmn.h | 3 ++ test/tests.c | 27 +++++------ 4 files changed, 155 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index f89701fd..5d5f819e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # You can put your build options here -include config.mk -all: libjsmn.a +all: libjsmn.a libjsmn.a: jsmn.o $(AR) rc $@ $^ @@ -36,6 +36,9 @@ clean: rm -f *.a *.so rm -f simple_example rm -f jsondump + rm -f test/test_default + rm -f test/test_links + rm -f test/test_strict + rm -f test/test_strict_links .PHONY: all clean test - diff --git a/jsmn.c b/jsmn.c index bcd6392a..aaba6a10 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,5 +1,88 @@ #include "jsmn.h" +#ifdef JSMN_STRICT + +typedef enum { + JSMN_TOK_UNDEFINED = 0, + JSMN_TOK_STRING = 1, + JSMN_TOK_VALUE, + JSMN_TOK_OPENING_BRACE, + JSMN_TOK_CLOSING_BRACE, + JSMN_TOK_OPENING_BRACKET, + JSMN_TOK_CLOSING_BRACKET, + JSMN_TOK_COLON, + JSMN_TOK_COMA, + JSMN_TOK_END, +} jsmn_tok_type_t; + +/** + * Next token type depends on context. + */ +static inline jsmn_tok_type_t jsmn_string_next_tok(jsmntok_t *token_parent, + jsmn_tok_type_t toktype) +{ + if (token_parent->type == JSMN_ARRAY && toktype == JSMN_TOK_COMA) { + return JSMN_TOK_VALUE; + } else if (toktype == JSMN_TOK_OPENING_BRACE || toktype == JSMN_TOK_COMA) { + return JSMN_TOK_STRING; + } + return JSMN_TOK_VALUE; +} + +static const jsmn_tok_type_t coma_expected[] = { + JSMN_TOK_UNDEFINED, /* tokens == NULL */ + JSMN_TOK_OPENING_BRACE, + JSMN_TOK_CLOSING_BRACE, + JSMN_TOK_OPENING_BRACKET, + JSMN_TOK_CLOSING_BRACKET, + JSMN_TOK_VALUE, + JSMN_TOK_END +}; + +static const jsmn_tok_type_t double_quote_expected[] = { + JSMN_TOK_OPENING_BRACE, + JSMN_TOK_OPENING_BRACKET, + JSMN_TOK_COLON, + JSMN_TOK_COMA, + JSMN_TOK_END +}; + +static const jsmn_tok_type_t closing_brace_expected[] = { + JSMN_TOK_UNDEFINED, /* tokens == NULL */ + JSMN_TOK_VALUE, + JSMN_TOK_OPENING_BRACE, + JSMN_TOK_CLOSING_BRACKET, + JSMN_TOK_CLOSING_BRACE, + JSMN_TOK_END +}; + +static const jsmn_tok_type_t closing_bracket_expected[] = { + JSMN_TOK_UNDEFINED, /* tokens == NULL */ + JSMN_TOK_VALUE, + JSMN_TOK_OPENING_BRACKET, + JSMN_TOK_CLOSING_BRACKET, + JSMN_TOK_CLOSING_BRACE, + JSMN_TOK_END +}; + +/** + * Return 0 if 'toktype' is found in the 'expected' token list. + * Otherwise return -1; + */ +static inline int jsmn_tok_expected(jsmn_tok_type_t curr_toktype, + const jsmn_tok_type_t *expected) +{ + int i = 0; + for (i = 0; expected[i] != JSMN_TOK_END; ++i) { + if (curr_toktype == expected[i]) { + return 0; + } + } + return -1; +} + +#endif + /** * Allocates a fresh unused token from the token pull. */ @@ -18,6 +101,7 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, return tok; } + /** * Fills token type and boundaries. */ @@ -162,6 +246,10 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, c = js[parser->pos]; switch (c) { case '{': case '[': +#ifdef JSMN_STRICT + parser->toktype = (c == '{') ? + JSMN_TOK_OPENING_BRACE : JSMN_TOK_OPENING_BRACKET; +#endif count++; if (tokens == NULL) { break; @@ -180,6 +268,21 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, parser->toksuper = parser->toknext - 1; break; case '}': case ']': +#ifdef JSMN_STRICT + if (tokens != NULL) { + if (c == '}') { + if (jsmn_tok_expected + (parser->toktype, closing_brace_expected)) { + return JSMN_ERROR_INVAL; + } + } else if (jsmn_tok_expected + (parser->toktype, closing_bracket_expected )) { + return JSMN_ERROR_INVAL; + } + } + parser->toktype = (c == '}') ? + JSMN_TOK_CLOSING_BRACE : JSMN_TOK_CLOSING_BRACKET; +#endif if (tokens == NULL) break; type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); @@ -229,18 +332,41 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, #endif break; case '\"': +#ifdef JSMN_STRICT + if (jsmn_tok_expected(parser->toktype, double_quote_expected)) { + return JSMN_ERROR_INVAL; + } +#endif r = jsmn_parse_string(parser, js, len, tokens, num_tokens); if (r < 0) return r; count++; if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; +#ifdef JSMN_STRICT + if (tokens != NULL) { + parser->toktype = jsmn_string_next_tok + (&tokens[parser->toksuper], parser->toktype); + } +#endif break; case '\t' : case '\r' : case '\n' : case ' ': break; case ':': parser->toksuper = parser->toknext - 1; +#ifdef JSMN_STRICT + if (parser->toktype != JSMN_TOK_STRING) { + return JSMN_ERROR_INVAL; + } + parser->toktype = JSMN_TOK_COLON; +#endif break; case ',': +#ifdef JSMN_STRICT + if (tokens != NULL && + jsmn_tok_expected(parser->toktype, coma_expected)) { + return JSMN_ERROR_INVAL; + } +#endif if (tokens != NULL && parser->toksuper != -1 && tokens[parser->toksuper].type != JSMN_ARRAY && tokens[parser->toksuper].type != JSMN_OBJECT) { @@ -256,7 +382,11 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, } } #endif + } +#ifdef JSMN_STRICT + parser->toktype = JSMN_TOK_COMA; +#endif break; #ifdef JSMN_STRICT /* In strict mode primitives are: numbers and booleans */ @@ -271,6 +401,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, return JSMN_ERROR_INVAL; } } + parser->toktype = JSMN_TOK_VALUE; #else /* In non-strict mode every unquoted value is a primitive */ default: @@ -310,5 +441,7 @@ void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; parser->toksuper = -1; +#ifdef JSMN_STRICT + parser->toktype = 0; +#endif } - diff --git a/jsmn.h b/jsmn.h index 5a5200ee..e8bfa092 100644 --- a/jsmn.h +++ b/jsmn.h @@ -55,6 +55,9 @@ typedef struct { unsigned int pos; /* offset in the JSON string */ unsigned int toknext; /* next token to allocate */ int toksuper; /* superior token node, e.g parent object or array */ +#ifdef JSMN_STRICT + int toktype; +#endif } jsmn_parser; /** diff --git a/test/tests.c b/test/tests.c index 31761cd3..06d46ab4 100644 --- a/test/tests.c +++ b/test/tests.c @@ -54,27 +54,26 @@ int test_object(void) { check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); - /* FIXME */ - /*check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2));*/ - /*check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4));*/ - /*check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4));*/ - /*check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4));*/ - /*check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4));*/ - /*check(parse("{,}", JSMN_ERROR_INVAL, 4));*/ + check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2)); + check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4)); + check(parse("{,}", JSMN_ERROR_INVAL, 4)); #endif return 0; } int test_array(void) { - /* FIXME */ - /*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/ - /*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/ +#ifdef JSMN_STRICT + check(parse("[10}", JSMN_ERROR_INVAL, 3)); + check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)); + check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3)); +#endif check(parse("[10]", 2, 2, JSMN_ARRAY, -1, -1, 1, JSMN_PRIMITIVE, "10")); check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3)); - /* FIXME */ - /*check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3));*/ return 0; } @@ -357,8 +356,8 @@ int test_nonstrict(void) { //nested {s don't cause a parse error. js = "\"key {1\": 1234"; check(parse(js, 2, 2, - JSMN_STRING, "key {1", 1, - JSMN_PRIMITIVE, "1234")); + JSMN_STRING, "key {1", 1, + JSMN_PRIMITIVE, "1234")); #endif