From e9803341d5ce2c79548cc4791551115e38503223 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 5 Apr 2017 19:30:06 +0200 Subject: [PATCH 1/3] simplify and rename cJSON_strcasecmp Two NULL strings should not be considered equal for the purpose of cJSON. --- cJSON.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/cJSON.c b/cJSON.c index edd9310..c73ff03 100644 --- a/cJSON.c +++ b/cJSON.c @@ -70,23 +70,17 @@ CJSON_PUBLIC(const char*) cJSON_Version(void) return version; } -/* case insensitive strcmp */ -static int cJSON_strcasecmp(const unsigned char *string1, const unsigned char *string2) +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) { - if (string1 == NULL) + if ((string1 == NULL) || (string2 == NULL)) { - if (string2 == NULL) - { - /* both NULL */ - return 0; - } - return 1; } - if (string2 == NULL) + if (string1 == string2) { - return 1; + return 0; } for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) @@ -97,7 +91,7 @@ static int cJSON_strcasecmp(const unsigned char *string1, const unsigned char *s } } - return tolower(string1[0]) - tolower(string2[0]); + return tolower(*string1) - tolower(*string2); } typedef struct internal_hooks @@ -1686,7 +1680,7 @@ CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int item) CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON *object, const char *string) { cJSON *c = object ? object->child : NULL; - while (c && cJSON_strcasecmp((unsigned char*)c->string, (const unsigned char*)string)) + while (c && !case_insensitive_strcmp((unsigned char*)c->string, (const unsigned char*)string)) { c = c->next; } @@ -1860,7 +1854,7 @@ CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *stri { size_t i = 0; cJSON *c = object->child; - while (c && cJSON_strcasecmp((unsigned char*)c->string, (const unsigned char*)string)) + while (c && !case_insensitive_strcmp((unsigned char*)c->string, (const unsigned char*)string)) { i++; c = c->next; @@ -1948,7 +1942,7 @@ CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, { size_t i = 0; cJSON *c = object->child; - while(c && cJSON_strcasecmp((unsigned char*)c->string, (const unsigned char*)string)) + while(c && !case_insensitive_strcmp((unsigned char*)c->string, (const unsigned char*)string)) { i++; c = c->next; From 2a25abbf2a1735cbf481356fa72a91b28fa83e7a Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 5 Apr 2017 19:37:00 +0200 Subject: [PATCH 2/3] Deduplicate code for cJSON_GetObjectItem --- cJSON.c | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/cJSON.c b/cJSON.c index c73ff03..471cdf3 100644 --- a/cJSON.c +++ b/cJSON.c @@ -1677,34 +1677,44 @@ CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int item) return c; } -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON *object, const char *string) -{ - cJSON *c = object ? object->child : NULL; - while (c && !case_insensitive_strcmp((unsigned char*)c->string, (const unsigned char*)string)) - { - c = c->next; - } - return c; -} - -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) { cJSON *current_element = NULL; - if ((object == NULL) || (string == NULL)) + if ((object == NULL) || (name == NULL)) { return NULL; } current_element = object->child; - while ((current_element != NULL) && (strcmp(string, current_element->string) != 0)) + if (case_sensitive) { - current_element = current_element->next; + while ((current_element != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } } return current_element; } +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON *object, const char *string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) { return cJSON_GetObjectItem(object, string) ? 1 : 0; @@ -1854,7 +1864,7 @@ CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *stri { size_t i = 0; cJSON *c = object->child; - while (c && !case_insensitive_strcmp((unsigned char*)c->string, (const unsigned char*)string)) + while (c && (case_insensitive_strcmp((unsigned char*)c->string, (const unsigned char*)string) != 0)) { i++; c = c->next; @@ -1942,7 +1952,7 @@ CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, { size_t i = 0; cJSON *c = object->child; - while(c && !case_insensitive_strcmp((unsigned char*)c->string, (const unsigned char*)string)) + while(c && (case_insensitive_strcmp((unsigned char*)c->string, (const unsigned char*)string) != 0)) { i++; c = c->next; From 6ac896d8d28b4a0d88ab13010d0c3de8210fbd13 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Thu, 6 Apr 2017 13:02:38 +0200 Subject: [PATCH 3/3] cJSON_Compare: New function to compare json --- cJSON.c | 101 ++++++++++++++++++++++ cJSON.h | 4 + tests/CMakeLists.txt | 1 + tests/compare_tests.c | 192 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 298 insertions(+) create mode 100644 tests/compare_tests.c diff --git a/cJSON.c b/cJSON.c index 471cdf3..bc343ba 100644 --- a/cJSON.c +++ b/cJSON.c @@ -2484,3 +2484,104 @@ CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) return (item->type & 0xFF) == cJSON_Raw; } + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (a->valuedouble == b->valuedouble) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + for (a_element = a->child, b_element = b->child; + (a_element != NULL) && (b_element != NULL); + a_element = a_element->next, b_element = b_element->next) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + cJSON *b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} diff --git a/cJSON.h b/cJSON.h index 7199520..c29ba17 100644 --- a/cJSON.h +++ b/cJSON.h @@ -212,6 +212,10 @@ CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will need to be released. With recurse!=0, it will duplicate any children connected to the item. The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + /* 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. If not, then cJSON_GetErrorPtr() does the job. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0cf17d3..bb7c6fa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,6 +46,7 @@ if(ENABLE_CJSON_TEST) print_value misc_tests parse_with_opts + compare_tests ) add_library(test-common common.c) diff --git a/tests/compare_tests.c b/tests/compare_tests.c new file mode 100644 index 0000000..654e14e --- /dev/null +++ b/tests/compare_tests.c @@ -0,0 +1,192 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include "unity/examples/unity_config.h" +#include "unity/src/unity.h" +#include "common.h" + +static cJSON_bool compare_from_string(const char * const a, const char * const b, const cJSON_bool case_sensitive) +{ + cJSON *a_json = NULL; + cJSON *b_json = NULL; + cJSON_bool result = false; + + a_json = cJSON_Parse(a); + TEST_ASSERT_NOT_NULL_MESSAGE(a_json, "Failed to parse a."); + b_json = cJSON_Parse(b); + TEST_ASSERT_NOT_NULL_MESSAGE(b_json, "Failed to parse b."); + + result = cJSON_Compare(a_json, b_json, case_sensitive); + + cJSON_Delete(a_json); + cJSON_Delete(b_json); + + return result; +} + +static void cjson_compare_should_compare_null_pointer_as_not_equal(void) +{ + TEST_ASSERT_FALSE(cJSON_Compare(NULL, NULL, true)); + TEST_ASSERT_FALSE(cJSON_Compare(NULL, NULL, false)); +} + +static void cjson_compare_should_compare_invalid_as_not_equal(void) +{ + cJSON invalid[1]; + memset(invalid, '\0', sizeof(invalid)); + + TEST_ASSERT_FALSE(cJSON_Compare(invalid, invalid, false)); + TEST_ASSERT_FALSE(cJSON_Compare(invalid, invalid, true)); +} + +static void cjson_compare_should_compare_numbers(void) +{ + TEST_ASSERT_TRUE(compare_from_string("1", "1", true)); + TEST_ASSERT_TRUE(compare_from_string("1", "1", false)); + TEST_ASSERT_TRUE(compare_from_string("0.0001", "0.0001", true)); + TEST_ASSERT_TRUE(compare_from_string("0.0001", "0.0001", false)); + + TEST_ASSERT_FALSE(compare_from_string("1", "2", true)); + TEST_ASSERT_FALSE(compare_from_string("1", "2", false)); +} + +static void cjson_compare_should_compare_booleans(void) +{ + /* true */ + TEST_ASSERT_TRUE(compare_from_string("true", "true", true)); + TEST_ASSERT_TRUE(compare_from_string("true", "true", false)); + + /* false */ + TEST_ASSERT_TRUE(compare_from_string("false", "false", true)); + TEST_ASSERT_TRUE(compare_from_string("false", "false", false)); + + /* mixed */ + TEST_ASSERT_FALSE(compare_from_string("true", "false", true)); + TEST_ASSERT_FALSE(compare_from_string("true", "false", false)); + TEST_ASSERT_FALSE(compare_from_string("false", "true", true)); + TEST_ASSERT_FALSE(compare_from_string("false", "true", false)); +} + +static void cjson_compare_should_compare_null(void) +{ + TEST_ASSERT_TRUE(compare_from_string("null", "null", true)); + TEST_ASSERT_TRUE(compare_from_string("null", "null", false)); + + TEST_ASSERT_FALSE(compare_from_string("null", "true", true)); + TEST_ASSERT_FALSE(compare_from_string("null", "true", false)); +} + +static void cjson_compare_should_not_accept_invalid_types(void) +{ + cJSON invalid[1]; + memset(invalid, '\0', sizeof(invalid)); + + invalid->type = cJSON_Number | cJSON_String; + + TEST_ASSERT_FALSE(cJSON_Compare(invalid, invalid, true)); + TEST_ASSERT_FALSE(cJSON_Compare(invalid, invalid, false)); +} + +static void cjson_compare_should_compare_strings(void) +{ + TEST_ASSERT_TRUE(compare_from_string("\"abcdefg\"", "\"abcdefg\"", true)); + TEST_ASSERT_TRUE(compare_from_string("\"abcdefg\"", "\"abcdefg\"", false)); + + TEST_ASSERT_FALSE(compare_from_string("\"ABCDEFG\"", "\"abcdefg\"", true)); + TEST_ASSERT_FALSE(compare_from_string("\"ABCDEFG\"", "\"abcdefg\"", false)); +} + +static void cjson_compare_should_compare_raw(void) +{ + cJSON *raw1 = NULL; + cJSON *raw2 = NULL; + + raw1 = cJSON_Parse("\"[true, false]\""); + TEST_ASSERT_NOT_NULL(raw1); + raw2 = cJSON_Parse("\"[true, false]\""); + TEST_ASSERT_NOT_NULL(raw2); + + raw1->type = cJSON_Raw; + raw2->type = cJSON_Raw; + + TEST_ASSERT_TRUE(cJSON_Compare(raw1, raw2, true)); + TEST_ASSERT_TRUE(cJSON_Compare(raw1, raw2, false)); + + cJSON_Delete(raw1); + cJSON_Delete(raw2); +} + +static void cjson_compare_should_compare_arrays(void) +{ + TEST_ASSERT_TRUE(compare_from_string("[]", "[]", true)); + TEST_ASSERT_TRUE(compare_from_string("[]", "[]", false)); + + TEST_ASSERT_TRUE(compare_from_string("[false,true,null,42,\"string\",[],{}]", "[false, true, null, 42, \"string\", [], {}]", true)); + TEST_ASSERT_TRUE(compare_from_string("[false,true,null,42,\"string\",[],{}]", "[false, true, null, 42, \"string\", [], {}]", false)); + + TEST_ASSERT_TRUE(compare_from_string("[[[1], 2]]", "[[[1], 2]]", true)); + TEST_ASSERT_TRUE(compare_from_string("[[[1], 2]]", "[[[1], 2]]", false)); + + TEST_ASSERT_FALSE(compare_from_string("[true,null,42,\"string\",[],{}]", "[false, true, null, 42, \"string\", [], {}]", true)); + TEST_ASSERT_FALSE(compare_from_string("[true,null,42,\"string\",[],{}]", "[false, true, null, 42, \"string\", [], {}]", false)); +} + +static void cjson_compare_should_compare_objects(void) +{ + TEST_ASSERT_TRUE(compare_from_string("{}", "{}", true)); + TEST_ASSERT_TRUE(compare_from_string("{}", "{}", false)); + + TEST_ASSERT_TRUE(compare_from_string( + "{\"false\": false, \"true\": true, \"null\": null, \"number\": 42, \"string\": \"string\", \"array\": [], \"object\": {}}", + "{\"true\": true, \"false\": false, \"null\": null, \"number\": 42, \"string\": \"string\", \"array\": [], \"object\": {}}", + true)); + TEST_ASSERT_FALSE(compare_from_string( + "{\"False\": false, \"true\": true, \"null\": null, \"number\": 42, \"string\": \"string\", \"array\": [], \"object\": {}}", + "{\"true\": true, \"false\": false, \"null\": null, \"number\": 42, \"string\": \"string\", \"array\": [], \"object\": {}}", + true)); + TEST_ASSERT_TRUE(compare_from_string( + "{\"False\": false, \"true\": true, \"null\": null, \"number\": 42, \"string\": \"string\", \"array\": [], \"object\": {}}", + "{\"true\": true, \"false\": false, \"null\": null, \"number\": 42, \"string\": \"string\", \"array\": [], \"object\": {}}", + false)); + TEST_ASSERT_FALSE(compare_from_string( + "{\"Flse\": false, \"true\": true, \"null\": null, \"number\": 42, \"string\": \"string\", \"array\": [], \"object\": {}}", + "{\"true\": true, \"false\": false, \"null\": null, \"number\": 42, \"string\": \"string\", \"array\": [], \"object\": {}}", + false)); +} + +int main(void) +{ + UNITY_BEGIN(); + + RUN_TEST(cjson_compare_should_compare_null_pointer_as_not_equal); + RUN_TEST(cjson_compare_should_compare_invalid_as_not_equal); + RUN_TEST(cjson_compare_should_compare_numbers); + RUN_TEST(cjson_compare_should_compare_booleans); + RUN_TEST(cjson_compare_should_compare_null); + RUN_TEST(cjson_compare_should_not_accept_invalid_types); + RUN_TEST(cjson_compare_should_compare_strings); + RUN_TEST(cjson_compare_should_compare_raw); + RUN_TEST(cjson_compare_should_compare_arrays); + RUN_TEST(cjson_compare_should_compare_objects); + + return UNITY_END(); +}