Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ If you want more options, use `cJSON_ParseWithOpts(const char *value, const char

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)`.

**Note on embedded NUL bytes:** `cJSON_ParseWithLength` (and `cJSON_ParseWithLengthOpts`) can parse JSON whose strings contain embedded NUL bytes, for example a string created from the input `"a\u0000b"`. However, cJSON stores every string as a NUL-terminated C string (`valuestring`) **without** keeping an explicit length. As a result, the embedded NUL is dropped when the value is later serialized: `cJSON_Print` / `cJSON_PrintUnformatted` rely on `strlen`, so they truncate the string at the first NUL. In other words, parse → print is **not** guaranteed to round-trip for strings containing embedded NUL bytes. If you need to preserve such data, decode the `\u0000` yourself or store the binary content encoded (e.g. base64) in the JSON.

### Printing JSON

Given a tree of `cJSON` items, you can print them as a string using `cJSON_Print`.
Expand Down
8 changes: 7 additions & 1 deletion cJSON.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,19 @@ 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);
/* Parse a block of JSON that may not be null terminated. Note: cJSON stores strings as
* NUL-terminated C strings without an explicit length, so a string that contains an
* embedded NUL byte (e.g. parsed from "\u0000") will be truncated when printed. */
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. */
/* Render a cJSON entity to text for transfer/storage.
* NOTE: strings are stored as NUL-terminated C strings, so any string containing an
* embedded NUL byte (e.g. one parsed from "\u0000" via cJSON_ParseWithLength) is
* truncated at that NUL when printed, because the printer uses strlen. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
Expand Down
23 changes: 23 additions & 0 deletions tests/parse_with_opts.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,28 @@ static void parse_with_opts_should_parse_utf8_bom(void)
cJSON_Delete(without_bom);
}

/* Documents the known parse -> print limitation for strings that contain an
* embedded NUL byte (issue D from the static review): cJSON stores strings as
* NUL-terminated C strings without an explicit length, so cJSON_Print truncates
* at the first NUL instead of round-tripping the value. */
static void parse_with_opts_should_truncate_embedded_null_on_print(void)
{
const char input[] = "\"a\\u0000b\""; /* JSON text: "a\u0000b" */
cJSON *item = cJSON_ParseWithLength(input, sizeof(input) - 1);
char *printed = NULL;

TEST_ASSERT_NOT_NULL_MESSAGE(item, "Failed to parse string with embedded NUL.");
TEST_ASSERT_TRUE(cJSON_IsString(item));

printed = cJSON_PrintUnformatted(item);
TEST_ASSERT_NOT_NULL(printed);
/* strlen-based printing stops at the embedded NUL: only "a" survives */
TEST_ASSERT_EQUAL_STRING("\"a\"", printed);

free(printed);
cJSON_Delete(item);
}

int CJSON_CDECL main(void)
{
UNITY_BEGIN();
Expand All @@ -107,6 +129,7 @@ int CJSON_CDECL main(void)
RUN_TEST(parse_with_opts_should_require_null_if_requested);
RUN_TEST(parse_with_opts_should_return_parse_end);
RUN_TEST(parse_with_opts_should_parse_utf8_bom);
RUN_TEST(parse_with_opts_should_truncate_embedded_null_on_print);

return UNITY_END();
}