Skip to content
Draft
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
37 changes: 22 additions & 15 deletions cJSON.c
Original file line number Diff line number Diff line change
Expand Up @@ -796,24 +796,31 @@ static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_bu
unsigned char *output_pointer = NULL;
unsigned char *output = NULL;

/* not a string */
/* Not a string */
if (buffer_at_offset(input_buffer)[0] != '\"')
{
goto fail;
}

{
/* calculate approximate size of the output (overestimate) */
/* Calculate approximate size of the output (overestimate) and validate control characters */
size_t allocation_length = 0;
size_t skipped_bytes = 0;
while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"'))
{
/* is escape sequence */
/* Check for unescaped control characters (U+0000 to U+001F) */
if (*input_end < 0x20) /* Control characters must be escaped */
{
input_buffer->offset = (size_t)(input_end - input_buffer->content);
goto fail; /* Error: unescaped control character */
}

/* Is escape sequence */
if (input_end[0] == '\\')
{
if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length)
{
/* prevent buffer overflow when last input character is a backslash */
/* Prevent buffer overflow when last input character is a backslash */
goto fail;
}
skipped_bytes++;
Expand All @@ -823,27 +830,27 @@ static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_bu
}
if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"'))
{
goto fail; /* string ended unexpectedly */
goto fail; /* String ended unexpectedly */
}

/* This is at most how much we need for the output */
allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes;
allocation_length = (size_t)(input_end - buffer_at_offset(input_buffer)) - skipped_bytes;
output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof(""));
if (output == NULL)
{
goto fail; /* allocation failure */
goto fail; /* Allocation failure */
}
}

output_pointer = output;
/* loop through the string literal */
/* Loop through the string literal */
while (input_pointer < input_end)
{
if (*input_pointer != '\\')
{
*output_pointer++ = *input_pointer++;
}
/* escape sequence */
/* Escape sequence */
else
{
unsigned char sequence_length = 2;
Expand Down Expand Up @@ -880,25 +887,25 @@ static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_bu
sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer);
if (sequence_length == 0)
{
/* failed to convert UTF16-literal to UTF-8 */
/* Failed to convert UTF16-literal to UTF-8 */
goto fail;
}
break;

default:
goto fail;
goto fail; /* Invalid escape sequence */
}
input_pointer += sequence_length;
}
}

/* zero terminate the output */
/* Zero terminate the output */
*output_pointer = '\0';

item->type = cJSON_String;
item->valuestring = (char*)output;

input_buffer->offset = (size_t) (input_end - input_buffer->content);
input_buffer->offset = (size_t)(input_end - input_buffer->content);
input_buffer->offset++;

return true;
Expand All @@ -907,17 +914,17 @@ static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_bu
if (output != NULL)
{
input_buffer->hooks.deallocate(output);
output = NULL;
}

if (input_pointer != NULL)
if (input_pointer != NULL && (size_t)(input_pointer - input_buffer->content) < input_buffer->length)
{
input_buffer->offset = (size_t)(input_pointer - input_buffer->content);
}

return false;
}


/* Render the cstring provided to an escaped version that can be printed. */
static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer)
{
Expand Down
27 changes: 18 additions & 9 deletions tests/parse_string.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ static cJSON item[1];
static void assert_is_string(cJSON *string_item)
{
TEST_ASSERT_NOT_NULL_MESSAGE(string_item, "Item is NULL.");

assert_not_in_list(string_item);
assert_has_no_child(string_item);
assert_has_type(string_item, cJSON_String);
Expand Down Expand Up @@ -68,19 +67,15 @@ static void assert_not_parse_string(const char * const string)
assert_is_invalid(item);
}



static void parse_string_should_parse_strings(void)
{
assert_parse_string("\"\"", "");
assert_parse_string(
"\" !\\\"#$%&'()*+,-./\\/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_'abcdefghijklmnopqrstuvwxyz{|}~\"",
" !\"#$%&'()*+,-.//0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_'abcdefghijklmnopqrstuvwxyz{|}~");
assert_parse_string(
"\"\\\"\\\\\\/\\b\\f\\n\\r\\t\\u20AC\\u732b\"",
"\"\\/\b\f\n\r\t€猫");
reset(item);
assert_parse_string("\"\b\f\n\r\t\"", "\b\f\n\r\t");
"\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"",
"\"\\/\b\f\n\r\t");
reset(item);
}

Expand Down Expand Up @@ -119,9 +114,22 @@ static void parse_string_should_parse_bug_94(void)
reset(item);
}

static void parse_string_should_not_parse_unescaped_control_chars(void)
{
/* Test unescaped control characters (U+0000 to U+001F) */
assert_not_parse_string("\"\x0a\""); /* Newline */
reset(item);
assert_not_parse_string("\"\x00\""); /* Null character */
reset(item);
assert_not_parse_string("\"1\x0a2\""); /* Newline in middle, matches #766 test case */
reset(item);
assert_not_parse_string("\"\x1f\""); /* Unit separator (highest control char) */
reset(item);
}

int CJSON_CDECL main(void)
{
/* initialize cJSON item and error pointer */
/* Initialize cJSON item and error pointer */
memset(item, 0, sizeof(cJSON));

UNITY_BEGIN();
Expand All @@ -131,5 +139,6 @@ int CJSON_CDECL main(void)
RUN_TEST(parse_string_should_not_parse_invalid_backslash);
RUN_TEST(parse_string_should_parse_bug_94);
RUN_TEST(parse_string_should_not_overflow_with_closing_backslash);
RUN_TEST(parse_string_should_not_parse_unescaped_control_chars); /* New test */
return UNITY_END();
}
}