diff --git a/src/jv.h b/src/jv.h index eb313f8a..2cc63c79 100644 --- a/src/jv.h +++ b/src/jv.h @@ -234,6 +234,7 @@ enum { jv jv_parse(const char* string); jv jv_parse_sized(const char* string, int length); +jv jv_parse_custom_flags(const char* string, int flags); typedef void (*jv_nomem_handler_f)(void *); void jv_nomem_handler(jv_nomem_handler_f, void *); diff --git a/src/jv_parse.c b/src/jv_parse.c index 3be93608..0c427eae 100644 --- a/src/jv_parse.c +++ b/src/jv_parse.c @@ -901,3 +901,46 @@ jv jv_parse_sized(const char* string, int length) { jv jv_parse(const char* string) { return jv_parse_sized(string, strlen(string)); } + +jv jv_parse_sized_custom_flags(const char* string, int length, int flags) { + struct jv_parser parser; + parser_init(&parser, flags); + jv_parser_set_buf(&parser, string, length, 0); + jv value = jv_parser_next(&parser); + if (jv_is_valid(value)) { + jv next = jv_parser_next(&parser); + if (jv_is_valid(next)) { + // multiple JSON values, we only wanted one + jv_free(value); + jv_free(next); + value = jv_invalid_with_msg(jv_string("Unexpected extra JSON values")); + } else if (jv_invalid_has_msg(jv_copy(next))) { + // parser error after the first JSON value + jv_free(value); + value = next; + } else { + // a single valid JSON value + jv_free(next); + } + } else if (jv_invalid_has_msg(jv_copy(value))) { + // parse error, we'll return it + } else { + // no value at all + jv_free(value); + value = jv_invalid_with_msg(jv_string("Expected JSON value")); + } + parser_free(&parser); + + if (!jv_is_valid(value) && jv_invalid_has_msg(jv_copy(value))) { + jv msg = jv_invalid_get_msg(value); + value = jv_invalid_with_msg(jv_string_fmt("%s (while parsing '%s')", + jv_string_value(msg), + string)); + jv_free(msg); + } + return value; +} + +jv jv_parse_custom_flags(const char* string, int flags) { + return jv_parse_sized_custom_flags(string, strlen(string), flags); +} diff --git a/tests/jq_fuzz_parse_extended.c b/tests/jq_fuzz_parse_extended.c new file mode 100644 index 00000000..af6ceccc --- /dev/null +++ b/tests/jq_fuzz_parse_extended.c @@ -0,0 +1,29 @@ +#include +#include +#include + +#include "jv.h" + +int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + if (size < 4) { + return 0; + } + + int fuzz_flags = *(int*)data; + data += 4; + size -= 4; + + // Creat null-terminated string + char *null_terminated = (char *)malloc(size + 1); + memcpy(null_terminated, (char *)data, size); + null_terminated[size] = '\0'; + + // Fuzzer entrypoint + jv res = jv_parse_custom_flags(null_terminated, fuzz_flags); + jv_free(res); + + // Free the null-terminated string + free(null_terminated); + + return 0; +} diff --git a/tests/jq_fuzz_parse_stream.c b/tests/jq_fuzz_parse_stream.c new file mode 100644 index 00000000..5b51c599 --- /dev/null +++ b/tests/jq_fuzz_parse_stream.c @@ -0,0 +1,21 @@ +#include +#include +#include + +#include "jv.h" + +int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + // Creat null-terminated string + char *null_terminated = (char *)malloc(size + 1); + memcpy(null_terminated, (char *)data, size); + null_terminated[size] = '\0'; + + // Fuzzer entrypoint + jv res = jv_parse_custom_flags(null_terminated, JV_PARSE_STREAMING); + jv_free(res); + + // Free the null-terminated string + free(null_terminated); + + return 0; +}