Skip to content

Commit b1ac6fe

Browse files
liquidatyclaude
andcommitted
json_esc1: do not truncate length-delimited strings at an embedded NUL
The escape scan stopped on `(c = *s)`, treating a NUL byte as a string terminator even when an explicit length was supplied -- silently dropping everything from the first embedded NUL onward. Remove the NUL guard from the loop condition: slen and max_output_size bound the loop, NUL-terminated callers pass slen == strlen so they still stop at the NUL exactly, and an embedded NUL is now emitted as its six-character unicode hex escape via the existing control-character (JSON_ESC_CHAR) path. tests: add strn-embedded-nul and keyn-embedded-nul, which write a length-delimited value/key containing a NUL and assert it is escaped (both fail against the pre-fix truncating scan). Suite: 52/52. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1fd2b75 commit b1ac6fe

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

tests/test.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,22 @@ static void run_string_tests(void) {
210210
jsonwriter_end(h);
211211
done("strn-len", h, &s, "[\"hel\"]");
212212

213+
/* an embedded NUL is data, not a terminator: strn must escape it (\u0000)
214+
* and keep writing the rest, not truncate the length-delimited string */
215+
h = begin(&s, 1);
216+
jsonwriter_start_array(h);
217+
jsonwriter_cstrn(h, "a\0b", 3);
218+
jsonwriter_end(h);
219+
done("strn-embedded-nul", h, &s, "[\"a\\u0000b\"]");
220+
221+
/* same for a length-delimited object key */
222+
h = begin(&s, 1);
223+
jsonwriter_start_object(h);
224+
jsonwriter_object_keyn(h, "k\0y", 3);
225+
jsonwriter_cstr(h, "v");
226+
jsonwriter_end(h);
227+
done("keyn-embedded-nul", h, &s, "{\"k\\u0000y\":\"v\"}");
228+
213229
/* quote + backslash escaping */
214230
h = begin(&s, 1);
215231
jsonwriter_start_array(h);

utils.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ static unsigned int json_esc1(const unsigned char *s, unsigned int slen,
3535
return 0;
3636
}
3737

38-
while(slen && (c = *s) && (size_t)(s - orig_s) < max_output_size) {
38+
/* Length-delimited: an embedded NUL is data (escaped to a \u00 hex escape
39+
* by the JSON_ESC_CHAR path below), not a terminator -- do not stop the scan
40+
* on it. slen and max_output_size bound the loop; NUL-terminated callers pass
41+
* slen == strlen, so they still stop at the NUL exactly. */
42+
while(slen && (size_t)(s - orig_s) < max_output_size) {
43+
c = *s;
3944
c_len = UTF8_charLenC(*s);
4045

4146
if(c_len > 0 && ((unsigned char)c_len > slen || (size_t)((s - orig_s) + c_len) > max_output_size))

0 commit comments

Comments
 (0)