From 983bb2b4d67124688d2116924f0b58c1b80acf88 Mon Sep 17 00:00:00 2001 From: caglarivriz Date: Thu, 2 Apr 2020 11:59:19 +0300 Subject: [PATCH] Added cJSON_ParseWithLength (#358) Co-authored-by: Caglar Ivriz --- README.md | 8 ++++++ cJSON.c | 33 +++++++++++++++++++++--- cJSON.h | 2 ++ tests/parse_examples.c | 57 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 55aba58..ba54472 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,12 @@ Given some JSON in a zero terminated string, you can parse it with `cJSON_Parse` cJSON *json = cJSON_Parse(string); ``` +Given some JSON in a string (whether zero terminated or not), you can parse it with `cJSON_ParseWithLength`. + +```c +cJSON *json = cJSON_ParseWithLength(string, buffer_length); +``` + It will parse the JSON and allocate a tree of `cJSON` items that represents it. Once it returns, you are fully responsible for deallocating it after use with `cJSON_Delete`. The allocator used by `cJSON_Parse` is `malloc` and `free` by default but can be changed (globally) with `cJSON_InitHooks`. @@ -277,6 +283,8 @@ By default, characters in the input string that follow the parsed JSON will not If you want more options, use `cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)`. `return_parse_end` returns a pointer to the end of the JSON in the input string or the position that an error occurs at (thereby replacing `cJSON_GetErrorPtr` in a thread safe way). `require_null_terminated`, if set to `1` will make it an error if the input string contains data after the JSON. +If you want more options giving buffer length, use `cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated)`. + ### Printing JSON Given a tree of `cJSON` items, you can print them as a string using `cJSON_Print`. diff --git a/cJSON.c b/cJSON.c index b0e744e..82d4deb 100644 --- a/cJSON.c +++ b/cJSON.c @@ -983,6 +983,11 @@ static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) return NULL; } + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) { buffer->offset++; @@ -1012,8 +1017,23 @@ static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) return buffer; } -/* Parse an object - create a new root, and populate. */ CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) { parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; cJSON *item = NULL; @@ -1022,13 +1042,13 @@ CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return global_error.json = NULL; global_error.position = 0; - if (value == NULL) + if (value == NULL || 0 == buffer_length) { goto fail; } buffer.content = (const unsigned char*)value; - buffer.length = strlen((const char*)value) + sizeof(""); + buffer.length = buffer_length; buffer.offset = 0; buffer.hooks = global_hooks; @@ -1098,7 +1118,12 @@ CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) return cJSON_ParseWithOpts(value, 0, 0); } -#define cjson_min(a, b) ((a < b) ? a : b) +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) { diff --git a/cJSON.h b/cJSON.h index 2c53562..e98ab64 100644 --- a/cJSON.h +++ b/cJSON.h @@ -151,9 +151,11 @@ CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); /* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ /* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ /* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); /* Render a cJSON entity to text for transfer/storage. */ CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); diff --git a/tests/parse_examples.c b/tests/parse_examples.c index 3aab77f..95a0959 100644 --- a/tests/parse_examples.c +++ b/tests/parse_examples.c @@ -195,6 +195,61 @@ static void test12_should_not_be_parsed(void) } } +static void test13_should_be_parsed_without_null_termination(void) +{ + cJSON *tree = NULL; + const char test_13[] = "{" \ + "\"Image\":{" \ + "\"Width\":800," \ + "\"Height\":600," \ + "\"Title\":\"Viewfrom15thFloor\"," \ + "\"Thumbnail\":{" \ + "\"Url\":\"http:/*www.example.com/image/481989943\"," \ + "\"Height\":125," \ + "\"Width\":\"100\"" \ + "}," \ + "\"IDs\":[116,943,234,38793]" \ + "}" \ + "}"; + + char test_13_wo_null[sizeof(test_13) - 1]; + memcpy(test_13_wo_null, test_13, sizeof(test_13) - 1); + + tree = cJSON_ParseWithLength(test_13_wo_null, sizeof(test_13) - 1); + TEST_ASSERT_NOT_NULL_MESSAGE(tree, "Failed to parse valid json."); + + if (tree != NULL) + { + cJSON_Delete(tree); + } +} + +static void test14_should_not_be_parsed(void) +{ + cJSON *tree = NULL; + const char test_14[] = "{" \ + "\"Image\":{" \ + "\"Width\":800," \ + "\"Height\":600," \ + "\"Title\":\"Viewfrom15thFloor\"," \ + "\"Thumbnail\":{" \ + "\"Url\":\"http:/*www.example.com/image/481989943\"," \ + "\"Height\":125," \ + "\"Width\":\"100\"" \ + "}," \ + "\"IDs\":[116,943,234,38793]" \ + "}" \ + "}"; + + tree = cJSON_ParseWithLength(test_14, sizeof(test_14) - 2); + TEST_ASSERT_NULL_MESSAGE(tree, "Should not continue after buffer_length is reached."); + + if (tree != NULL) + { + cJSON_Delete(tree); + } +} + int CJSON_CDECL main(void) { UNITY_BEGIN(); @@ -210,5 +265,7 @@ int CJSON_CDECL main(void) RUN_TEST(file_test10_should_be_parsed_and_printed); RUN_TEST(file_test11_should_be_parsed_and_printed); RUN_TEST(test12_should_not_be_parsed); + RUN_TEST(test13_should_be_parsed_without_null_termination); + RUN_TEST(test14_should_not_be_parsed); return UNITY_END(); }