Wed, 10 Dec 2025 22:53:41 +0100
fix cxJsonFromString() ignoring unexpected trailing data + fix wrong handling of trailing whitespaces in general
resolves #777
| src/json.c | file | annotate | diff | comparison | revisions | |
| tests/test_json.c | file | annotate | diff | comparison | revisions |
--- a/src/json.c Wed Dec 10 22:34:48 2025 +0100 +++ b/src/json.c Wed Dec 10 22:53:41 2025 +0100 @@ -228,7 +228,9 @@ } } - if (ttype != CX_JSON_NO_TOKEN) { + if (ttype == CX_JSON_NO_TOKEN) { + return CX_JSON_NO_DATA; + } else { // uncompleted token size_t uncompleted_len = json->buffer.size - token_part_start; if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { @@ -255,9 +257,8 @@ } // advance the buffer position - we saved the stuff in the uncompleted token json->buffer.pos += uncompleted_len; + return CX_JSON_INCOMPLETE_DATA; } - - return CX_JSON_INCOMPLETE_DATA; } // converts a Unicode codepoint to utf8 @@ -804,8 +805,23 @@ // LCOV_EXCL_STOP } CxJsonStatus status = cxJsonNext(&parser, value); + // check if we consume the total string + CxJsonValue *chk_value = NULL; + CxJsonStatus chk_status = CX_JSON_NO_DATA; + if (status == CX_JSON_NO_ERROR) { + chk_status = cxJsonNext(&parser, &chk_value); + } cxJsonDestroy(&parser); - return status; + if (chk_status == CX_JSON_NO_DATA) { + return status; + } else { + cxJsonValueFree(*value); + // if chk_value is nothing, the free is harmless + cxJsonValueFree(chk_value); + *value = &cx_json_value_nothing; + return CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN; + } + } void cxJsonValueFree(CxJsonValue *value) {
--- a/tests/test_json.c Wed Dec 10 22:34:48 2025 +0100 +++ b/tests/test_json.c Wed Dec 10 22:53:41 2025 +0100 @@ -215,23 +215,34 @@ CX_TEST(test_json_from_string_multiple_values) { CxJsonStatus status; - CxJsonValue *obj = NULL; + CxJsonValue *obj; CX_TEST_DO { + obj = NULL; status = cxJsonFromString(NULL, "{ \"obj1\": \"hello\" }\n\"value2\"\n", &obj); - - // TODO: what is the expected behavior here? Is this an error or do we ignore the second value? + CX_TEST_ASSERT(obj != NULL); + CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); + CX_TEST_ASSERT(status == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + + obj = NULL; + status = cxJsonFromString(NULL, "\"value\" \n ] syntax error [", &obj); + CX_TEST_ASSERT(obj != NULL); + CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); + CX_TEST_ASSERT(status == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } +} + +CX_TEST(test_json_from_string_untrimmed) { + CxJsonStatus status; + CxJsonValue *obj; + CX_TEST_DO { + obj = NULL; + status = cxJsonFromString(NULL, "\n\t{ \"obj1\": \"hello\" } \n", &obj); CX_TEST_ASSERT(status == CX_JSON_NO_ERROR); CX_TEST_ASSERT(cxJsonIsObject(obj)); - // CX_TEST_ASSERT(status == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); - - cxJsonValueFree(obj); - - // TODO: this really should be an error in theory - status = cxJsonFromString(NULL, "\"value\" \n ] syntax error [", &obj); - CX_TEST_ASSERT(status == CX_JSON_NO_ERROR); - CX_TEST_ASSERT(cxJsonIsString(obj)); - // CX_TEST_ASSERT(status == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); - + CxJsonValue *obj1 = cxJsonObjGet(obj, "obj1"); + CX_TEST_ASSERT(cxJsonIsString(obj1)); + CX_TEST_ASSERT(cx_strcmp(cxJsonAsCxString(obj1), "hello") == 0); + cxJsonValueFree(obj); } } @@ -930,7 +941,7 @@ cxJsonValueFree(v); // read remaining '\n' result = cxJsonNext(&json, &v); - CX_TEST_ASSERT(result == CX_JSON_INCOMPLETE_DATA); + CX_TEST_ASSERT(result == CX_JSON_NO_DATA); // read string cxJsonFill(&json, "\"hello world\"\n"); result = cxJsonNext(&json, &v); @@ -1565,6 +1576,7 @@ cx_test_register(suite, test_json_from_string); cx_test_register(suite, test_json_from_string_errors); cx_test_register(suite, test_json_from_string_multiple_values); + cx_test_register(suite, test_json_from_string_untrimmed); cx_test_register(suite, test_json_escaped_strings); cx_test_register(suite, test_json_escaped_unicode_strings); cx_test_register(suite, test_json_escaped_unicode_malformed);