# HG changeset patch # User Mike Becker # Date 1765403621 -3600 # Node ID f60f23b362e9d7f1a72c9158b8d7bbad5bab6c42 # Parent 2ebbcb38986d27e4447fbc1ba9780c7822a364c2 fix cxJsonFromString() ignoring unexpected trailing data + fix wrong handling of trailing whitespaces in general resolves #777 diff -r 2ebbcb38986d -r f60f23b362e9 src/json.c --- 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) { diff -r 2ebbcb38986d -r f60f23b362e9 tests/test_json.c --- 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);