diff --git a/CMakeLists.txt b/CMakeLists.txt index c84e14e3..7997b210 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -231,7 +231,7 @@ if (BUILD_TESTS AND NOT SKIP_SRC) DEPENDS array-test alivedetection-test boreas_error-test boreas_io-test cli-test cpeutils-test cvss-test ping-test sniffer-test util-test networking-test passwordbasedauthentication-test xmlutils-test version-test osp-test - nvti-test hosts-test) + nvti-test hosts-test jsonpull-test) endif (BUILD_TESTS AND NOT SKIP_SRC) diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 95c94235..6b656306 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -42,6 +42,9 @@ pkg_check_modules (GPGME REQUIRED gpgme>=1.7.0) # for serverutils we need libgcrypt pkg_check_modules (GCRYPT REQUIRED libgcrypt) +# for json parsing we need cJSON +pkg_check_modules (CJSON REQUIRED libcjson>=1.7.14) + # for mqtt find_library(LIBPAHO paho-mqtt3c) message (STATUS "Looking for paho-mqtt3c ... ${LIBPAHO}") @@ -111,11 +114,11 @@ include_directories (${GLIB_INCLUDE_DIRS} ${GPGME_INCLUDE_DIRS} ${GCRYPT_INCLUDE set (FILES cpeutils.c passwordbasedauthentication.c compressutils.c fileutils.c gpgmeutils.c kb.c ldaputils.c nvticache.c mqtt.c radiusutils.c serverutils.c sshutils.c uuidutils.c - xmlutils.c) + xmlutils.c jsonpull.c) set (HEADERS cpeutils.h passwordbasedauthentication.h authutils.h compressutils.h fileutils.h gpgmeutils.h kb.h ldaputils.h nvticache.h mqtt.h radiusutils.h serverutils.h sshutils.h - uuidutils.h xmlutils.h) + uuidutils.h xmlutils.h jsonpull.h) if (BUILD_STATIC) add_library (gvm_util_static STATIC ${FILES}) @@ -137,13 +140,29 @@ if (BUILD_SHARED) ${RADIUS_LDFLAGS} ${LIBSSH_LDFLAGS} ${GNUTLS_LDFLAGS} ${GCRYPT_LDFLAGS} ${LDAP_LDFLAGS} ${REDIS_LDFLAGS} ${LIBXML2_LDFLAGS} ${UUID_LDFLAGS} - ${LINKER_HARDENING_FLAGS} ${CRYPT_LDFLAGS}) + ${LINKER_HARDENING_FLAGS} ${CRYPT_LDFLAGS} + ${CJSON_LDFLAGS}) endif (BUILD_SHARED) ## Tests if (BUILD_TESTS) + add_executable (jsonpull-test + EXCLUDE_FROM_ALL + jsonpull_tests.c) + + add_test (jsonpull-test jsonpull-test) + + target_include_directories (jsonpull-test PRIVATE ${CGREEN_INCLUDE_DIRS}) + + target_link_libraries (jsonpull-test ${CGREEN_LIBRARIES} + ${GLIB_LDFLAGS} ${CJSON_LDFLAGS}) + + add_custom_target (tests-jsonpull + DEPENDS jsonpull-test) + + add_executable (passwordbasedauthentication-test EXCLUDE_FROM_ALL passwordbasedauthentication_tests.c) diff --git a/util/jsonpull.c b/util/jsonpull.c new file mode 100644 index 00000000..03dddf42 --- /dev/null +++ b/util/jsonpull.c @@ -0,0 +1,978 @@ +/* SPDX-FileCopyrightText: 2024 Greenbone AG + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "jsonpull.h" + +#include + +#define GVM_JSON_CHAR_EOF -1 ///< End of file +#define GVM_JSON_CHAR_ERROR -2 ///< Error reading file +#define GVM_JSON_CHAR_UNDEFINED -3 ///< Undefined state + +/** + * @brief Escapes a string according to the JSON or JSONPath standard + * + * @param[in] string The string to escape + * @param[in] single_quote Whether to escape single quotes + * + * @return The escaped string + */ +gchar * +gvm_json_string_escape (const char *string, gboolean single_quote) +{ + gchar *point; + if (string == NULL) + return NULL; + + GString *escaped = g_string_sized_new (strlen (string)); + for (point = (char *) string; *point != 0; point++) + { + unsigned char character = *point; + + if ((character > 31) && (character != '\\') + && (single_quote ? (character != '\'') : (character != '\"'))) + { + g_string_append_c (escaped, character); + } + else + { + g_string_append_c (escaped, '\\'); + switch (*point) + { + case '\\': + case '\'': + case '\"': + g_string_append_c (escaped, *point); + break; + case '\b': + g_string_append_c (escaped, 'b'); + break; + case '\f': + g_string_append_c (escaped, 'f'); + break; + case '\n': + g_string_append_c (escaped, 'n'); + break; + case '\r': + g_string_append_c (escaped, 'r'); + break; + case '\t': + g_string_append_c (escaped, 't'); + break; + default: + g_string_append_printf (escaped, "u%04x", character); + } + } + } + return g_string_free (escaped, FALSE); +} + +/** + * @brief Creates a new JSON path element. + * + * @param[in] parent_type Type of the parent (array, object, none/root) + * @param[in] depth The depth in the document tree + * + * @return The newly allocated path element + */ +gvm_json_path_elem_t * +gvm_json_pull_path_elem_new (gvm_json_pull_container_type_t parent_type, + int depth) +{ + gvm_json_path_elem_t *new_elem = g_malloc0 (sizeof (gvm_json_path_elem_t)); + new_elem->parent_type = parent_type; + new_elem->depth = depth; + return new_elem; +} + +/** + * @brief Frees a JSON path element. + * + * @param[in] elem The element to free + */ +void +gvm_json_pull_path_elem_free (gvm_json_path_elem_t *elem) +{ + g_free (elem->key); + g_free (elem); +} + +/** + * @brief Initializes a JSON pull event data structure. + * + * @param[in] event The event structure to initialize + */ +void +gvm_json_pull_event_init (gvm_json_pull_event_t *event) +{ + memset (event, 0, sizeof (gvm_json_pull_event_t)); +} + +/** + * @brief Resets a JSON pull event data structure for reuse. + * + * @param[in] event The event structure to reset + */ +void +gvm_json_pull_event_reset (gvm_json_pull_event_t *event) +{ + cJSON_free (event->value); + if (event->error_message) + g_free (event->error_message); + memset (event, 0, sizeof (gvm_json_pull_event_t)); +} + +/** + * @brief Frees all data of JSON pull event data structure. + * + * @param[in] event The event structure to clean up + */ +void +gvm_json_pull_event_cleanup (gvm_json_pull_event_t *event) +{ + cJSON_free (event->value); + if (event->error_message) + g_free (event->error_message); + memset (event, 0, sizeof (gvm_json_pull_event_t)); +} + +/** + * @brief Initializes a JSON pull parser. + * + * @param[in] parser The parser data structure to initialize + * @param[in] input_stream The JSON input stream + * @param[in] parse_buffer_limit Maximum buffer size for parsing values + * @param[in] read_buffer_size Buffer size for reading from the stream + */ +void +gvm_json_pull_parser_init_full (gvm_json_pull_parser_t *parser, + FILE *input_stream, size_t parse_buffer_limit, + size_t read_buffer_size) +{ + assert (parser); + assert (input_stream); + memset (parser, 0, sizeof (gvm_json_pull_parser_t)); + + if (parse_buffer_limit <= 0) + parse_buffer_limit = GVM_JSON_PULL_PARSE_BUFFER_LIMIT; + + if (read_buffer_size <= 0) + read_buffer_size = GVM_JSON_PULL_READ_BUFFER_SIZE; + + parser->input_stream = input_stream; + parser->path = g_queue_new (); + parser->expect = GVM_JSON_PULL_EXPECT_VALUE; + parser->parse_buffer_limit = parse_buffer_limit; + parser->parse_buffer = g_string_new (""); + parser->read_buffer_size = read_buffer_size; + parser->read_buffer = g_malloc0 (read_buffer_size); + parser->last_read_char = GVM_JSON_CHAR_UNDEFINED; +} + +/** + * @brief Initializes a JSON pull parser with default buffer sizes. + * + * @param[in] parser The parser data structure to initialize + * @param[in] input_stream The JSON input stream + */ +void +gvm_json_pull_parser_init (gvm_json_pull_parser_t *parser, FILE *input_stream) +{ + gvm_json_pull_parser_init_full (parser, input_stream, 0, 0); +} + +/** + * @brief Frees the data of a JSON pull parser. + * + * @param[in] parser The parser data structure to free the data of + */ +void +gvm_json_pull_parser_cleanup (gvm_json_pull_parser_t *parser) +{ + assert (parser); + g_queue_free_full (parser->path, + (GDestroyNotify) gvm_json_pull_path_elem_free); + g_string_free (parser->parse_buffer, TRUE); + g_free (parser->read_buffer); + memset (parser, 0, sizeof (gvm_json_pull_parser_t)); +} + +/** + * @brief Generates message for an error that occurred reading the JSON stream. + * + * @return The newly allocated error message + */ +static gchar * +gvm_json_read_stream_error_str () +{ + return g_strdup_printf ("error reading JSON stream: %s", strerror (errno)); +} + +/** + * @brief Checks if the parse buffer limit of a JSON pull parser is reached. + * + * @param[in] value_type The value type to include in the error message + * @param[in] parser The parser to check the parse buffer of + * @param[in] event Event data for error status and message if needed + * + * @return 0 if buffer size is okay, 1 if limit was reached + */ +static int +gvm_json_pull_check_parse_buffer_size (const char *value_type, + gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event) +{ + if (parser->parse_buffer->len >= parser->parse_buffer_limit) + { + event->error_message = + g_strdup_printf ("%s exceeds size limit of %zu bytes", value_type, + parser->parse_buffer_limit); + event->type = GVM_JSON_PULL_EVENT_ERROR; + return 1; + } + return 0; +} + +/** + * @brief Reads the next character in a pull parser input stream. + * + * @param[in] parser The parser to read the next character from + * + * @return The character code, GVM_JSON_CHAR_ERROR or GVM_JSON_CHAR_EOF. + */ +static int +gvm_json_pull_parser_next_char (gvm_json_pull_parser_t *parser) +{ + parser->read_pos++; + if (parser->read_pos < parser->last_read_size) + { + parser->last_read_char = + (unsigned char) parser->read_buffer[parser->read_pos]; + return parser->last_read_char; + } + else + { + parser->read_pos = 0; + parser->last_read_size = fread ( + parser->read_buffer, 1, parser->read_buffer_size, parser->input_stream); + if (ferror (parser->input_stream)) + parser->last_read_char = GVM_JSON_CHAR_ERROR; + else if (parser->last_read_size <= 0) + parser->last_read_char = GVM_JSON_CHAR_EOF; + else + parser->last_read_char = + (unsigned char) parser->read_buffer[parser->read_pos]; + return parser->last_read_char; + } +} + +/** + * @brief Tries to parse the buffer content of a JSON pull parser. + * + * @param[in] parser The parser to use the parse buffer of + * @param[in] event Event set error of if necessary + * @param[in] value_name Name of the value for error message if needed + * @param[in] validate_func Function for validating the parsed value + * @param[out] cjson_value Return of the parsed cJSON object on success + * + * @return 0 success, 1 error + */ +static int +gvm_json_pull_parse_buffered (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event, + const char *value_name, + cJSON_bool (*validate_func) (const cJSON *const), + cJSON **cjson_value) +{ + cJSON *parsed_value = cJSON_Parse (parser->parse_buffer->str); + *cjson_value = NULL; + if (validate_func (parsed_value) == 0) + { + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup_printf ("error parsing %s", value_name); + cJSON_free (parsed_value); + return 1; + } + *cjson_value = parsed_value; + return 0; +} + +/** + * @brief Handles error or EOF after reading a character in JSON pull parser. + * + * @param[in] parser Parser to get the last read character from + * @param[in] event Event data to set EOF or error status in + * @param[in] allow_eof Whether to allow EOF, generate error on EOF if FALSE + */ +static void +gvm_json_pull_handle_read_end (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event, gboolean allow_eof) +{ + if (parser->last_read_char == GVM_JSON_CHAR_ERROR) + { + event->error_message = gvm_json_read_stream_error_str (); + event->type = GVM_JSON_PULL_EVENT_ERROR; + } + else if (allow_eof) + event->type = GVM_JSON_PULL_EVENT_EOF; + else + { + event->error_message = g_strdup ("unexpected EOF"); + event->type = GVM_JSON_PULL_EVENT_ERROR; + } +} + +/** + * @brief Skips whitespaces in the input stream of a JSON pull parser + * + * The parser will be at the first non-whitespace character on success. + * + * @param[in] parser Parser to skip the whitespaces in + * @param[in] event Event data to set EOF or error status in + * @param[in] allow_eof Whether to allow EOF, generate error on EOF if FALSE + * + * @return 1 if EOF was reached or an error occurred, 0 otherwise + */ +static int +gvm_json_pull_skip_space (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event, gboolean allow_eof) +{ + while (g_ascii_isspace (parser->last_read_char)) + gvm_json_pull_parser_next_char (parser); + if (parser->last_read_char < 0) + { + gvm_json_pull_handle_read_end (parser, event, allow_eof); + return 1; + } + return 0; +} + +/** + * @brief Parses a string in a JSON pull parser. + * + * The parser is expected to be at the opening quote mark and will be at the + * character after the closing quote mark on success. + * + * @param[in] parser Parser to handle the string value in + * @param[in] event Event data to set EOF or error status in + * @param[out] cjson_value The cJSON value for the string on success + * + * @return 1 if an error occurred (including EOF), 0 otherwise + */ +static int +gvm_json_pull_parse_string (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event, cJSON **cjson_value) +{ + gboolean escape_next_char = FALSE; + g_string_truncate (parser->parse_buffer, 0); + g_string_append_c (parser->parse_buffer, '"'); + while (gvm_json_pull_parser_next_char (parser) >= 0) + { + if (gvm_json_pull_check_parse_buffer_size ("string", parser, event)) + return 1; + g_string_append_c (parser->parse_buffer, parser->last_read_char); + if (escape_next_char) + escape_next_char = FALSE; + else if (parser->last_read_char == '\\') + escape_next_char = TRUE; + else if (parser->last_read_char == '"') + break; + } + + if (parser->last_read_char < 0) + { + gvm_json_pull_handle_read_end (parser, event, FALSE); + return 1; + } + + gvm_json_pull_parser_next_char (parser); + + return gvm_json_pull_parse_buffered (parser, event, "string", cJSON_IsString, + cjson_value); +} + +/** + * @brief Parses a number in a JSON pull parser. + * + * The parser is expected to be at the first character of the number and will + * be at the first non-number character on success. + * + * @param[in] parser Parser to handle the number value in + * @param[in] event Event data to set EOF or error status in + * @param[out] cjson_value The cJSON value for the number on success. + * + * @return 1 if an error occurred, 0 otherwise + */ +static int +gvm_json_pull_parse_number (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event, cJSON **cjson_value) +{ + g_string_truncate (parser->parse_buffer, 0); + g_string_append_c (parser->parse_buffer, parser->last_read_char); + while (gvm_json_pull_parser_next_char (parser) >= 0) + { + if (gvm_json_pull_check_parse_buffer_size ("number", parser, event)) + return 1; + if (g_ascii_isdigit (parser->last_read_char) + || parser->last_read_char == '.' || parser->last_read_char == 'e' + || parser->last_read_char == '-' || parser->last_read_char == '+') + g_string_append_c (parser->parse_buffer, parser->last_read_char); + else + break; + } + + if (parser->last_read_char == GVM_JSON_CHAR_ERROR) + { + event->error_message = gvm_json_read_stream_error_str (); + event->type = GVM_JSON_PULL_EVENT_ERROR; + return 1; + } + + return gvm_json_pull_parse_buffered (parser, event, "number", cJSON_IsNumber, + cjson_value); +} + +/** + * @brief Parses a keyword value in a JSON pull parser. + * + * The parser is expected to be at the first character of the keyword and will + * be at the first character after the keyword on success. + * + * @param[in] parser Parser to handle the keyword value in + * @param[in] event Event data to set EOF or error status in + * @param[in] keyword The expected keyword, e.g. "null", "true", "false". + * + * @return 1 if an error occurred, 0 otherwise + */ +static int +gvm_json_pull_parse_keyword (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event, const char *keyword) +{ + for (size_t i = 0; i < strlen (keyword); i++) + { + if (parser->last_read_char < 0) + { + gvm_json_pull_handle_read_end (parser, event, FALSE); + return 1; + } + else if (parser->last_read_char != keyword[i]) + { + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = + g_strdup_printf ("misspelled keyword '%s'", keyword); + return 1; + } + gvm_json_pull_parser_next_char (parser); + } + return 0; +} + +/** + * @brief Updates the expectation for a JSON pull parser according to the path. + * + * @param[in] parser The parser to update. + */ +static void +parse_value_next_expect (gvm_json_pull_parser_t *parser) +{ + if (parser->path->length) + parser->expect = GVM_JSON_PULL_EXPECT_COMMA; + else + parser->expect = GVM_JSON_PULL_EXPECT_EOF; +} + +/** + * @brief Handles the case that an object key is expected in a JSON pull parser. + * + * This will continue the parsing until the value is expected, the end of the + * current object was reached or an error occurred. + * + * @param[in] parser Parser to process + * @param[in] event Event data to set error or end of object status in + * + * @return 1 if an error occurred, 0 otherwise + */ +static int +gvm_json_pull_parse_key (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event) +{ + if (gvm_json_pull_skip_space (parser, event, FALSE)) + return 1; + + cJSON *key_cjson = NULL; + gchar *key_str; + gvm_json_path_elem_t *path_elem; + + switch (parser->last_read_char) + { + case '"': + if (gvm_json_pull_parse_string (parser, event, &key_cjson)) + return 1; + key_str = g_strdup (key_cjson->valuestring); + cJSON_free (key_cjson); + + // Expect colon: + if (gvm_json_pull_skip_space (parser, event, FALSE)) + { + g_free (key_str); + return 1; + } + if (parser->last_read_char != ':') + { + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup_printf ("expected colon"); + g_free (key_str); + return 1; + } + gvm_json_pull_parser_next_char (parser); + + path_elem = g_queue_peek_tail (parser->path); + g_free (path_elem->key); + path_elem->key = key_str; + parser->expect = GVM_JSON_PULL_EXPECT_VALUE; + + break; + case '}': + event->type = GVM_JSON_PULL_EVENT_OBJECT_END; + event->value = NULL; + gvm_json_pull_path_elem_free (g_queue_pop_tail (parser->path)); + parse_value_next_expect (parser); + gvm_json_pull_parser_next_char (parser); + break; + case ']': + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup ("unexpected closing square bracket"); + return 1; + default: + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup ("unexpected character"); + return 1; + } + + return 0; +} + +/** + * @brief Handles the case that a comma is expected in a JSON pull parser. + * + * This will continue the parsing until a comma or the end of the + * current array/object was reached or an error occurred. + * + * @param[in] parser Parser to process + * @param[in] event Event data to set error or end of object status in + * + * @return 1 if an error occurred, 0 otherwise + */ +static int +gvm_json_pull_parse_comma (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event) +{ + if (gvm_json_pull_skip_space (parser, event, FALSE)) + return 1; + + gvm_json_path_elem_t *path_elem = NULL; + switch (parser->last_read_char) + { + case ',': + path_elem = g_queue_peek_tail (parser->path); + path_elem->index++; + if (path_elem->parent_type == GVM_JSON_PULL_CONTAINER_OBJECT) + parser->expect = GVM_JSON_PULL_EXPECT_KEY; + else + parser->expect = GVM_JSON_PULL_EXPECT_VALUE; + gvm_json_pull_parser_next_char (parser); + break; + case ']': + path_elem = g_queue_peek_tail (parser->path); + if (path_elem == NULL + || path_elem->parent_type != GVM_JSON_PULL_CONTAINER_ARRAY) + { + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup ("unexpected closing square bracket"); + return 1; + } + event->type = GVM_JSON_PULL_EVENT_ARRAY_END; + event->value = NULL; + gvm_json_pull_path_elem_free (g_queue_pop_tail (parser->path)); + parse_value_next_expect (parser); + gvm_json_pull_parser_next_char (parser); + break; + case '}': + path_elem = g_queue_peek_tail (parser->path); + if (path_elem == NULL + || path_elem->parent_type != GVM_JSON_PULL_CONTAINER_OBJECT) + { + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup ("unexpected closing curly brace"); + return 1; + } + event->type = GVM_JSON_PULL_EVENT_OBJECT_END; + event->value = NULL; + gvm_json_pull_path_elem_free (g_queue_pop_tail (parser->path)); + parse_value_next_expect (parser); + gvm_json_pull_parser_next_char (parser); + break; + default: + event->error_message = g_strdup ("expected comma or end of container"); + event->type = GVM_JSON_PULL_EVENT_ERROR; + return 1; + } + return 0; +} + +/** + * @brief Handles the case that a value is expected in a JSON pull parser. + * + * This will continue the parsing until a value or the end of the + * current array/object was parsed or an error occurred. + * + * @param[in] parser Parser to process + * @param[in] event Event data to set error or end of object status in + * + * @return 1 if an error occurred, 0 otherwise + */ +static int +gvm_json_pull_parse_value (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event) +{ + if (gvm_json_pull_skip_space (parser, event, FALSE)) + return 1; + + cJSON *cjson_value = NULL; + gvm_json_path_elem_t *path_elem = NULL; + + switch (parser->last_read_char) + { + case '"': + if (gvm_json_pull_parse_string (parser, event, &cjson_value)) + return 1; + event->type = GVM_JSON_PULL_EVENT_STRING; + event->value = cjson_value; + parse_value_next_expect (parser); + break; + case 'n': + if (gvm_json_pull_parse_keyword (parser, event, "null")) + return 1; + event->type = GVM_JSON_PULL_EVENT_NULL; + event->value = cJSON_CreateNull (); + parse_value_next_expect (parser); + break; + case 'f': + if (gvm_json_pull_parse_keyword (parser, event, "false")) + return 1; + event->type = GVM_JSON_PULL_EVENT_BOOLEAN; + event->value = cJSON_CreateFalse (); + parse_value_next_expect (parser); + break; + case 't': + if (gvm_json_pull_parse_keyword (parser, event, "true")) + return 1; + event->type = GVM_JSON_PULL_EVENT_BOOLEAN; + event->value = cJSON_CreateTrue (); + parse_value_next_expect (parser); + break; + case '[': + event->type = GVM_JSON_PULL_EVENT_ARRAY_START; + event->value = NULL; + parser->path_add = gvm_json_pull_path_elem_new ( + GVM_JSON_PULL_CONTAINER_ARRAY, parser->path->length); + parser->expect = GVM_JSON_PULL_EXPECT_VALUE; + gvm_json_pull_parser_next_char (parser); + break; + case ']': + path_elem = g_queue_peek_tail (parser->path); + if (path_elem == NULL + || path_elem->parent_type != GVM_JSON_PULL_CONTAINER_ARRAY) + { + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup ("unexpected closing square bracket"); + return 1; + } + event->type = GVM_JSON_PULL_EVENT_ARRAY_END; + event->value = NULL; + gvm_json_pull_path_elem_free (g_queue_pop_tail (parser->path)); + parse_value_next_expect (parser); + gvm_json_pull_parser_next_char (parser); + break; + case '{': + event->type = GVM_JSON_PULL_EVENT_OBJECT_START; + event->value = NULL; + parser->path_add = gvm_json_pull_path_elem_new ( + GVM_JSON_PULL_CONTAINER_OBJECT, parser->path->length); + parser->expect = GVM_JSON_PULL_EXPECT_KEY; + gvm_json_pull_parser_next_char (parser); + break; + case '}': + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup ("unexpected closing curly brace"); + return 1; + break; + default: + if (g_ascii_isdigit (parser->last_read_char) + || parser->last_read_char == '-') + { + if (gvm_json_pull_parse_number (parser, event, &cjson_value)) + return 1; + event->type = GVM_JSON_PULL_EVENT_NUMBER; + event->value = cjson_value; + parse_value_next_expect (parser); + } + else + { + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup ("unexpected character"); + return 1; + } + } + return 0; +} + +/** + * @brief Get the next event from a JSON pull parser. + * + * Note: This invalidates previous event data like the cJSON value. + * + * @param[in] parser The JSON pull parser to process until the next event + * @param[in] event Structure to store event data in. + */ +void +gvm_json_pull_parser_next (gvm_json_pull_parser_t *parser, + gvm_json_pull_event_t *event) +{ + assert (parser); + assert (event); + + gvm_json_pull_event_reset (event); + if (parser->last_read_char == GVM_JSON_CHAR_UNDEFINED) + { + // Handle first read of the stream + if (gvm_json_pull_parser_next_char (parser) < 0) + { + gvm_json_pull_handle_read_end (parser, event, TRUE); + return; + } + } + event->path = parser->path; + + // Delayed addition to path after a container start element + if (parser->path_add) + { + g_queue_push_tail (parser->path, parser->path_add); + parser->path_add = NULL; + } + + // Check for expected end of file + if (parser->expect == GVM_JSON_PULL_EXPECT_EOF) + { + gvm_json_pull_skip_space (parser, event, TRUE); + + if (parser->last_read_char == GVM_JSON_CHAR_ERROR) + { + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = gvm_json_read_stream_error_str (); + } + else if (parser->last_read_char != GVM_JSON_CHAR_EOF) + { + event->type = GVM_JSON_PULL_EVENT_ERROR; + event->error_message = g_strdup_printf ( + "unexpected character at end of file (%d)", parser->last_read_char); + return; + } + return; + } + + if (parser->expect == GVM_JSON_PULL_EXPECT_COMMA) + { + if (gvm_json_pull_parse_comma (parser, event)) + return; + } + + if (parser->expect == GVM_JSON_PULL_EXPECT_KEY) + { + if (gvm_json_pull_parse_key (parser, event)) + return; + } + + if (parser->expect == GVM_JSON_PULL_EXPECT_VALUE) + { + gvm_json_pull_parse_value (parser, event); + } +} + +/** + * @brief Expands the current array or object of a JSON pull parser. + * + * This should be called after an array or object start event. + * + * @param[in] parser Parser to get the current container element from + * @param[out] error_message Error message output + * + * @return The expanded container as a cJSON object if successful, else NULL + */ +cJSON * +gvm_json_pull_expand_container (gvm_json_pull_parser_t *parser, + gchar **error_message) +{ + gvm_json_path_elem_t *path_tail = NULL; + + int start_depth; + gboolean in_string, escape_next_char, in_expanded_container; + cJSON *expanded; + + g_string_truncate (parser->parse_buffer, 0); + + if (error_message) + *error_message = NULL; + + // require "path_add" to only allow expansion at start of container + if (parser->path_add) + { + path_tail = parser->path_add; + g_queue_push_tail (parser->path, path_tail); + parser->path_add = NULL; + } + + if (path_tail && path_tail->parent_type == GVM_JSON_PULL_CONTAINER_ARRAY) + g_string_append_c (parser->parse_buffer, '['); + else if (path_tail + && path_tail->parent_type == GVM_JSON_PULL_CONTAINER_OBJECT) + g_string_append_c (parser->parse_buffer, '{'); + else + { + if (error_message) + *error_message = + g_strdup ("can only expand after array or object start"); + return NULL; + } + + start_depth = path_tail->depth; + in_string = escape_next_char = FALSE; + in_expanded_container = TRUE; + + while (parser->last_read_char >= 0 && in_expanded_container) + { + if (parser->parse_buffer->len >= parser->parse_buffer_limit) + { + if (error_message) + *error_message = + g_strdup_printf ("container exceeds size limit of %zu bytes", + parser->parse_buffer_limit); + return NULL; + } + + g_string_append_c (parser->parse_buffer, parser->last_read_char); + + if (escape_next_char) + { + escape_next_char = FALSE; + } + else if (in_string) + { + escape_next_char = (parser->last_read_char == '\\'); + in_string = (parser->last_read_char != '"'); + } + else + { + switch (parser->last_read_char) + { + case '"': + in_string = TRUE; + break; + case '[': + path_tail = gvm_json_pull_path_elem_new ( + GVM_JSON_PULL_CONTAINER_ARRAY, parser->path->length); + g_queue_push_tail (parser->path, path_tail); + break; + case '{': + path_tail = gvm_json_pull_path_elem_new ( + GVM_JSON_PULL_CONTAINER_OBJECT, parser->path->length); + g_queue_push_tail (parser->path, path_tail); + break; + case ']': + path_tail = g_queue_pop_tail (parser->path); + if (path_tail->parent_type != GVM_JSON_PULL_CONTAINER_ARRAY) + { + if (error_message) + *error_message = + g_strdup ("unexpected closing square bracket"); + return NULL; + } + if (path_tail->depth == start_depth) + in_expanded_container = FALSE; + break; + case '}': + path_tail = g_queue_pop_tail (parser->path); + if (path_tail->parent_type != GVM_JSON_PULL_CONTAINER_OBJECT) + { + if (error_message) + *error_message = + g_strdup ("unexpected closing curly brace"); + return NULL; + } + if (path_tail->depth == start_depth) + in_expanded_container = FALSE; + break; + } + } + gvm_json_pull_parser_next_char (parser); + } + + if (parser->last_read_char == GVM_JSON_CHAR_ERROR) + { + if (error_message) + *error_message = gvm_json_read_stream_error_str (); + return NULL; + } + else if (in_expanded_container && parser->last_read_char == GVM_JSON_CHAR_EOF) + { + if (error_message) + *error_message = g_strdup ("unexpected EOF"); + return NULL; + } + + expanded = cJSON_Parse (parser->parse_buffer->str); + g_string_truncate (parser->parse_buffer, 0); + parse_value_next_expect (parser); + + if (expanded == NULL && error_message) + *error_message = g_strdup ("could not parse expanded container"); + + return expanded; +} + +/** + * @brief Appends a string path element to a JSONPath string. + * + * @param[in] path_elem The path element to append + * @param[in] path_string The path string to append to + */ +static void +gvm_json_path_string_add_elem (gvm_json_path_elem_t *path_elem, + GString *path_string) +{ + if (path_elem->parent_type == GVM_JSON_PULL_CONTAINER_OBJECT) + { + gchar *escaped_key = gvm_json_string_escape (path_elem->key, TRUE); + g_string_append_printf (path_string, "['%s']", escaped_key); + g_free (escaped_key); + } + else + g_string_append_printf (path_string, "[%d]", path_elem->index); +} + +/** + * @brief Converts a path as used by a JSON pull parser to a JSONPath string. + * + * @param[in] path The path to convert + * + * @return Newly allocated string of the path in JSONPath bracket notation + */ +gchar * +gvm_json_path_to_string (GQueue *path) +{ + GString *path_string = g_string_new ("$"); + g_queue_foreach (path, (GFunc) gvm_json_path_string_add_elem, path_string); + return g_string_free (path_string, FALSE); +} diff --git a/util/jsonpull.h b/util/jsonpull.h new file mode 100644 index 00000000..0b057a49 --- /dev/null +++ b/util/jsonpull.h @@ -0,0 +1,137 @@ +/* SPDX-FileCopyrightText: 2024 Greenbone AG + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _GVM_JSONPULL_H +#define _GVM_JSONPULL_H + +#define _GNU_SOURCE + +#include +#include +#include + +/** + * @brief Type of container the parser is currently in + */ +typedef enum +{ + GVM_JSON_PULL_CONTAINER_NONE = 0, ///< No container / document root + GVM_JSON_PULL_CONTAINER_ARRAY, ///< Array + GVM_JSON_PULL_CONTAINER_OBJECT, ///< Object +} gvm_json_pull_container_type_t; + +/** + * @brief Path element types for the JSON pull parser. + */ +typedef struct gvm_json_path_elem +{ + gvm_json_pull_container_type_t parent_type; ///< parent container type + int index; ///< Index of the element within the parent + char *key; ///< Key if element is in an object + int depth; ///< Number of ancestor elements +} gvm_json_path_elem_t; + +/** + * @brief Event types for the JSON pull parser + */ +typedef enum +{ + GVM_JSON_PULL_EVENT_UNDEFINED = 0, + GVM_JSON_PULL_EVENT_ARRAY_START, + GVM_JSON_PULL_EVENT_ARRAY_END, + GVM_JSON_PULL_EVENT_OBJECT_START, + GVM_JSON_PULL_EVENT_OBJECT_END, + GVM_JSON_PULL_EVENT_STRING, + GVM_JSON_PULL_EVENT_NUMBER, + GVM_JSON_PULL_EVENT_BOOLEAN, + GVM_JSON_PULL_EVENT_NULL, + GVM_JSON_PULL_EVENT_EOF, + GVM_JSON_PULL_EVENT_ERROR, +} gvm_json_pull_event_type_t; + +/** + * @brief Event generated by the JSON pull parser. + */ +typedef struct +{ + gvm_json_pull_event_type_t type; ///< Type of event + GQueue *path; ///< Path to the event value + cJSON *value; ///< Value for non-container value events + gchar *error_message; ///< Error message, NULL on success +} gvm_json_pull_event_t; + +/** + * @brief Expected token state for the JSON pull parser + */ +typedef enum +{ + GVM_JSON_PULL_EXPECT_UNDEFINED = 0, ///< Undefined state + GVM_JSON_PULL_EXPECT_VALUE, ///< Expect start of a value + GVM_JSON_PULL_EXPECT_KEY, ///< Expect start of a key + GVM_JSON_PULL_EXPECT_COMMA, ///< Expect comma or container end brace + GVM_JSON_PULL_EXPECT_EOF ///< Expect end of file +} gvm_json_pull_expect_t; + +#define GVM_JSON_PULL_PARSE_BUFFER_LIMIT 10485760 + +#define GVM_JSON_PULL_READ_BUFFER_SIZE 4096 + +/** + * @brief A json pull parser + */ +typedef struct +{ + GQueue *path; ///< Path to the current value + gvm_json_path_elem_t *path_add; ///< Path elem to add in next step + gvm_json_pull_expect_t expect; ///< Current expected token + int keyword_pos; ///< Position in a keyword like "true" or "null" + FILE *input_stream; ///< Input stream + char *read_buffer; ///< Stream reading buffer + size_t read_buffer_size; ///< Size of the stream reading buffer + size_t last_read_size; ///< Size of last stream read + int last_read_char; ///< Character last read from stream + size_t read_pos; ///< Position in current read + GString *parse_buffer; ///< Buffer for parsing values and object keys + size_t parse_buffer_limit; ///< Maximum parse buffer size +} gvm_json_pull_parser_t; + +gchar * +gvm_json_string_escape (const char *, gboolean); + +gvm_json_path_elem_t * +gvm_json_pull_path_elem_new (gvm_json_pull_container_type_t, int); + +void +gvm_json_pull_path_elem_free (gvm_json_path_elem_t *); + +void +gvm_json_pull_event_init (gvm_json_pull_event_t *); + +void +gvm_json_pull_event_reset (gvm_json_pull_event_t *); + +void +gvm_json_pull_event_cleanup (gvm_json_pull_event_t *); + +void +gvm_json_pull_parser_init_full (gvm_json_pull_parser_t *, FILE *, size_t, + size_t); + +void +gvm_json_pull_parser_init (gvm_json_pull_parser_t *, FILE *); + +void +gvm_json_pull_parser_cleanup (gvm_json_pull_parser_t *); + +void +gvm_json_pull_parser_next (gvm_json_pull_parser_t *, gvm_json_pull_event_t *); + +cJSON * +gvm_json_pull_expand_container (gvm_json_pull_parser_t *, gchar **); + +gchar * +gvm_json_path_to_string (GQueue *path); + +#endif /* _GVM_JSONPULL_H */ diff --git a/util/jsonpull_tests.c b/util/jsonpull_tests.c new file mode 100644 index 00000000..444f5a66 --- /dev/null +++ b/util/jsonpull_tests.c @@ -0,0 +1,1225 @@ +/* SPDX-FileCopyrightText: 2019-2023 Greenbone AG + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "jsonpull.c" + +#include +#include +#include + +Describe (jsonpull); +BeforeEach (jsonpull) +{ +} +AfterEach (jsonpull) +{ +} + +/* + * Helper function to open a string as a read-only stream. + */ +static inline FILE * +fstropen_r (const char *str) +{ + return fmemopen ((void *) str, strlen (str), "r"); +} + +static ssize_t +read_with_error_on_eof (void *stream_cookie, char *buf, size_t size) +{ + FILE *stream = stream_cookie; + ssize_t ret = fread (buf, 1, size, stream); + if (ret <= 0) + { + errno = EIO; + return -1; + } + else + return ret; +} + +#define INIT_JSON_PARSER(json_string) \ + gvm_json_pull_event_t event; \ + gvm_json_pull_parser_t parser; \ + FILE *jsonstream; \ + jsonstream = fstropen_r (json_string); \ + gvm_json_pull_event_init (&event); \ + gvm_json_pull_parser_init_full (&parser, jsonstream, 100, 4); + +#define INIT_READ_ERROR_JSON_PARSER(json_string) \ + gvm_json_pull_event_t event; \ + gvm_json_pull_parser_t parser; \ + FILE *jsonstream = fstropen_r (json_string); \ + cookie_io_functions_t io_functions = {.read = read_with_error_on_eof, \ + .write = NULL, \ + .seek = NULL, \ + .close = NULL}; \ + FILE *errorstream = fopencookie (jsonstream, "r", io_functions); \ + gvm_json_pull_event_init (&event); \ + gvm_json_pull_parser_init_full (&parser, errorstream, 100, 4); + +#define CLEANUP_JSON_PARSER \ + gvm_json_pull_event_cleanup (&event); \ + gvm_json_pull_parser_cleanup (&parser); \ + fclose (jsonstream); + +#define CHECK_PATH_EQUALS(expected_path_str) \ + path_str = gvm_json_path_to_string (event.path); \ + assert_that (path_str, is_equal_to_string (expected_path_str)); \ + g_free (path_str); + +#define JSON_READ_ERROR "error reading JSON stream: Input/output error" + +Ensure (jsonpull, can_json_escape_strings) +{ + const char *unescaped_string = "\"'Abc\\\b\f\n\r\t\001Äöü'\""; + const char *escaped_string_dq = "\\\"'Abc\\\\\\b\\f\\n\\r\\t\\u0001Äöü'\\\""; + const char *escaped_string_sq = "\"\\'Abc\\\\\\b\\f\\n\\r\\t\\u0001Äöü\\'\""; + + gchar *escaped_string = NULL; + escaped_string = gvm_json_string_escape (NULL, FALSE); + assert_that (escaped_string, is_null); + + escaped_string = gvm_json_string_escape (unescaped_string, FALSE); + assert_that (escaped_string, is_equal_to_string (escaped_string_dq)); + g_free (escaped_string); + + escaped_string = gvm_json_string_escape (unescaped_string, TRUE); + assert_that (escaped_string, is_equal_to_string (escaped_string_sq)); + g_free (escaped_string); +} + +Ensure (jsonpull, can_init_parser_with_defaults) +{ + gvm_json_pull_parser_t parser; + FILE *strfile = fstropen_r ("[]"); + + gvm_json_pull_parser_init (&parser, strfile); + assert_that (parser.input_stream, is_equal_to (strfile)); + assert_that (parser.parse_buffer_limit, + is_equal_to (GVM_JSON_PULL_PARSE_BUFFER_LIMIT)); + assert_that (parser.read_buffer_size, + is_equal_to (GVM_JSON_PULL_READ_BUFFER_SIZE)); +} + +Ensure (jsonpull, can_parse_false) +{ + INIT_JSON_PARSER ("false"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_BOOLEAN)); + assert_that (cJSON_IsBool (event.value), is_true); + assert_that (cJSON_IsFalse (event.value), is_true); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_true) +{ + INIT_JSON_PARSER ("true"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_BOOLEAN)); + assert_that (cJSON_IsBool (event.value), is_true); + assert_that (cJSON_IsTrue (event.value), is_true); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_null) +{ + INIT_JSON_PARSER ("null"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NULL)); + assert_that (cJSON_IsNull (event.value), is_true); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_empty_strings) +{ + INIT_JSON_PARSER ("\"\""); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_STRING)); + assert_that (event.value->valuestring, is_equal_to_string ("")); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_strings_with_content) +{ + INIT_JSON_PARSER ("\n\"123\\tXYZ\\nÄöü\"\n"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_STRING)); + assert_that (event.value->valuestring, is_equal_to_string ("123\tXYZ\nÄöü")); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_integer_numbers) +{ + INIT_JSON_PARSER ("-0987"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + assert_that (event.value->valueint, is_equal_to (-987)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_floating_point_numbers) +{ + INIT_JSON_PARSER ("\t\n 1.2345e+4\n"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + assert_that (event.value->valuedouble, is_equal_to (1.2345e+4)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_empty_arrays) +{ + INIT_JSON_PARSER ("[ ]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_END)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_single_elem_arrays) +{ + gchar *path_str; + INIT_JSON_PARSER ("[ 123 ]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + assert_that (event.value->valueint, is_equal_to (123)); + CHECK_PATH_EQUALS ("$[0]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_END)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_multiple_elem_arrays) +{ + gchar *path_str; + INIT_JSON_PARSER ("[123, \"ABC\", null]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + assert_that (event.value->valueint, is_equal_to (123)); + CHECK_PATH_EQUALS ("$[0]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_STRING)); + assert_that (event.value->valuestring, is_equal_to_string ("ABC")); + CHECK_PATH_EQUALS ("$[1]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NULL)); + CHECK_PATH_EQUALS ("$[2]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_END)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_empty_objects) +{ + INIT_JSON_PARSER ("{ }"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_END)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_single_elem_objects) +{ + gchar *path_str; + INIT_JSON_PARSER ("{ \"keyA\": \"valueA\" }"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_STRING)); + assert_that (event.value->valuestring, is_equal_to_string ("valueA")); + CHECK_PATH_EQUALS ("$['keyA']"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_END)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_multiple_elem_objects) +{ + gchar *path_str; + INIT_JSON_PARSER ("{ \"keyA\": \"valueA\", \"keyB\":12345 }"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_STRING)); + assert_that (event.value->valuestring, is_equal_to_string ("valueA")); + CHECK_PATH_EQUALS ("$['keyA']"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + assert_that (event.value->valueint, is_equal_to (12345)); + CHECK_PATH_EQUALS ("$['keyB']"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_END)); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_parse_nested_containers) +{ + gchar *path_str; + INIT_JSON_PARSER ("[{\"A\":null, \"B\":{\"C\": [1,2]}, \"D\":\"3\"}, [4]]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + CHECK_PATH_EQUALS ("$"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + CHECK_PATH_EQUALS ("$[0]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NULL)); + CHECK_PATH_EQUALS ("$[0]['A']"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + CHECK_PATH_EQUALS ("$[0]['B']"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + CHECK_PATH_EQUALS ("$[0]['B']['C']"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + assert_that (event.value->valueint, is_equal_to (1)); + CHECK_PATH_EQUALS ("$[0]['B']['C'][0]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + assert_that (event.value->valueint, is_equal_to (2)); + CHECK_PATH_EQUALS ("$[0]['B']['C'][1]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_END)); + CHECK_PATH_EQUALS ("$[0]['B']['C']"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_END)); + CHECK_PATH_EQUALS ("$[0]['B']"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_STRING)); + assert_that (event.value->valuestring, is_equal_to_string ("3")); + CHECK_PATH_EQUALS ("$[0]['D']"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_END)); + CHECK_PATH_EQUALS ("$[0]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + CHECK_PATH_EQUALS ("$[1]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + assert_that (event.value->valueint, is_equal_to (4)); + CHECK_PATH_EQUALS ("$[1][0]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_END)); + CHECK_PATH_EQUALS ("$[1]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_END)); + CHECK_PATH_EQUALS ("$"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_expand_arrays) +{ + gchar *path_str, *error_message; + cJSON *expanded, *child; + INIT_JSON_PARSER ("[[], [1], [2, [3]], [\"A\", \"\\\"B]\"]]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + CHECK_PATH_EQUALS ("$"); + + // empty array + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + CHECK_PATH_EQUALS ("$[0]"); + expanded = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (error_message, is_equal_to_string (NULL)); + assert_that (expanded, is_not_null); + assert_that (cJSON_IsArray (expanded), is_true); + assert_that (expanded->child, is_null); + cJSON_free (expanded); + + // single-element array + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + CHECK_PATH_EQUALS ("$[1]"); + expanded = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (error_message, is_null); + assert_that (expanded, is_not_null); + assert_that (cJSON_IsArray (expanded), is_true); + child = expanded->child; + assert_that (child, is_not_null); + assert_that (cJSON_IsNumber (child), is_true); + assert_that (child->valueint, is_equal_to (1)); + child = child->next; + assert_that (child, is_null); + cJSON_free (expanded); + + // multi-element array + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + CHECK_PATH_EQUALS ("$[2]"); + expanded = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (error_message, is_null); + assert_that (expanded, is_not_null); + assert_that (cJSON_IsArray (expanded), is_true); + child = expanded->child; + assert_that (child, is_not_null); + assert_that (cJSON_IsNumber (child), is_true); + assert_that (child->valueint, is_equal_to (2)); + child = child->next; + assert_that (child, is_not_null); + assert_that (cJSON_IsArray (child), is_true); + assert_that (child->child->valueint, is_equal_to (3)); + cJSON_free (expanded); + + // string array + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + CHECK_PATH_EQUALS ("$[3]"); + expanded = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (error_message, is_null); + assert_that (expanded, is_not_null); + assert_that (cJSON_IsArray (expanded), is_true); + child = expanded->child; + assert_that (child, is_not_null); + assert_that (cJSON_IsString (child), is_true); + assert_that (child->valuestring, is_equal_to_string ("A")); + child = child->next; + assert_that (child, is_not_null); + assert_that (cJSON_IsString (child), is_true); + assert_that (child->valuestring, is_equal_to_string ("\"B]")); + cJSON_free (expanded); + + // array end and EOF + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_END)); + CHECK_PATH_EQUALS ("$"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, can_expand_objects) +{ + gchar *path_str, *error_message; + cJSON *expanded, *child; + INIT_JSON_PARSER ( + "{\"A\":{}, \"B\": {\"C\": \"\\\"D}\", \"E\":123, \"F\":{}}}"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + CHECK_PATH_EQUALS ("$"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + CHECK_PATH_EQUALS ("$['A']"); + expanded = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (error_message, is_null); + assert_that (cJSON_IsObject (expanded), is_true); + assert_that (expanded->child, is_null); + cJSON_free (expanded); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + CHECK_PATH_EQUALS ("$['B']"); + expanded = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (error_message, is_null); + assert_that (cJSON_IsObject (expanded), is_true); + child = expanded->child; + assert_that (child, is_not_null); + assert_that (cJSON_IsString (child), is_true); + assert_that (child->string, is_equal_to_string ("C")); + assert_that (child->valuestring, is_equal_to_string ("\"D}")); + child = child->next; + assert_that (child, is_not_null); + assert_that (cJSON_IsNumber (child), is_true); + assert_that (child->string, is_equal_to_string ("E")); + assert_that (child->valueint, is_equal_to (123)); + child = child->next; + assert_that (child, is_not_null); + assert_that (cJSON_IsObject (child), is_true); + assert_that (child->string, is_equal_to_string ("F")); + assert_that (child->child, is_null); + cJSON_free (expanded); + + // object end and EOF + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_END)); + CHECK_PATH_EQUALS ("$"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_EOF)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_read_error) +{ + INIT_READ_ERROR_JSON_PARSER ("123"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string (JSON_READ_ERROR)); + gvm_json_pull_parser_cleanup (&parser); + gvm_json_pull_event_cleanup (&event); + fclose (jsonstream); +} + +Ensure (jsonpull, fails_for_misspelled_true) +{ + INIT_JSON_PARSER ("trxyz"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("misspelled keyword 'true'")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_incomplete_true) +{ + INIT_JSON_PARSER ("tru"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_misspelled_false) +{ + INIT_JSON_PARSER ("falxyz"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("misspelled keyword 'false'")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_misspelled_null) +{ + INIT_JSON_PARSER ("nulx"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("misspelled keyword 'null'")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_string_read_error) +{ + INIT_READ_ERROR_JSON_PARSER ("\"ABCDEFG\""); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string (JSON_READ_ERROR)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_string_eof) +{ + INIT_JSON_PARSER ("\"no closing quote here"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_overlong_string) +{ + INIT_JSON_PARSER ("\"This should be too long for a small parse buffer\""); + parser.parse_buffer_limit = 10; + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("string exceeds size limit of 10 bytes")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_invalid_string) +{ + INIT_JSON_PARSER ("\"This has an invalid escape sequence: \\x\""); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("error parsing string")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_number_read_error) +{ + INIT_READ_ERROR_JSON_PARSER ("12345.123456789"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string (JSON_READ_ERROR)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_overlong_number) +{ + INIT_READ_ERROR_JSON_PARSER ("12345.123456789"); + parser.parse_buffer_limit = 10; + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("number exceeds size limit of 10 bytes")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_invalid_number) +{ + INIT_JSON_PARSER ("-+e"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("error parsing number")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_array_eof) +{ + INIT_JSON_PARSER ("["); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_array_eof_after_value) +{ + INIT_JSON_PARSER ("[123"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_array_eof_after_comma) +{ + INIT_JSON_PARSER ("[123,"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_array_read_error) +{ + INIT_READ_ERROR_JSON_PARSER ("[ "); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string (JSON_READ_ERROR)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_invalid_array_bracket) +{ + INIT_JSON_PARSER ("[}"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("unexpected closing curly brace")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_invalid_array_bracket_after_value) +{ + INIT_JSON_PARSER ("[123}"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("unexpected closing curly brace")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_invalid_array_other_char) +{ + INIT_JSON_PARSER ("[!"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("unexpected character")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_invalid_array_other_char_after_value) +{ + INIT_JSON_PARSER ("[123!"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("expected comma or end of container")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_key_eof) +{ + INIT_JSON_PARSER ("{"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_key_read_error) +{ + INIT_READ_ERROR_JSON_PARSER ("{ "); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string (JSON_READ_ERROR)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_key_invalid_string) +{ + INIT_JSON_PARSER ("{\"invalid escape:\\x\": 123}"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("error parsing string")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_invalid_object_key_bracket) +{ + INIT_JSON_PARSER ("{]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("unexpected closing square bracket")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_invalid_object_key_other_char) +{ + INIT_JSON_PARSER ("{!"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("unexpected character")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_colon_eof) +{ + INIT_JSON_PARSER ("{\"A\" "); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_colon_read_error) +{ + INIT_READ_ERROR_JSON_PARSER ("{\"A\" "); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string (JSON_READ_ERROR)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_colon_other_char) +{ + INIT_JSON_PARSER ("{\"A\"!"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("expected colon")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_value_eof) +{ + INIT_JSON_PARSER ("{\"A\": "); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_value_read_error) +{ + INIT_READ_ERROR_JSON_PARSER ("{\"A\": "); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string (JSON_READ_ERROR)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_value_curly_brace) +{ + INIT_JSON_PARSER ("{\"A\": }"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("unexpected closing curly brace")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_value_square_bracket) +{ + INIT_JSON_PARSER ("{\"A\": ]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("unexpected closing square bracket")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_eof_after_value) +{ + INIT_JSON_PARSER ("{\"A\": 123"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_square_bracket_after_value) +{ + INIT_JSON_PARSER ("{\"A\": 123 ]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("unexpected closing square bracket")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_object_eof_after_comma) +{ + INIT_JSON_PARSER ("{\"A\": 123, "); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string ("unexpected EOF")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_read_error_after_doc_end) +{ + INIT_READ_ERROR_JSON_PARSER ("123 "); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, is_equal_to_string (JSON_READ_ERROR)); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_content_after_doc_end) +{ + INIT_JSON_PARSER ("123 456"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ERROR)); + assert_that (event.error_message, + is_equal_to_string ("unexpected character at end of file (52)")); + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_expand_before_container) +{ + cJSON *cjson_value; + gchar *error_message; + INIT_JSON_PARSER ("[]"); + + cjson_value = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (cjson_value, is_null); + assert_that (error_message, is_equal_to_string ("can only expand after" + " array or object start")); + + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_expand_after_value) +{ + cJSON *cjson_value; + gchar *error_message; + INIT_JSON_PARSER ("[123, 456]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_NUMBER)); + + cjson_value = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (cjson_value, is_null); + assert_that (error_message, is_equal_to_string ("can only expand after" + " array or object start")); + + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_expand_invalid_content) +{ + cJSON *cjson_value; + gchar *error_message; + INIT_JSON_PARSER ("[invalid content]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + + cjson_value = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (cjson_value, is_null); + assert_that (error_message, + is_equal_to_string ("could not parse expanded container")); + + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_expand_overlong) +{ + cJSON *cjson_value; + gchar *error_message; + INIT_JSON_PARSER ("[1234567890.123456780]"); + parser.parse_buffer_limit = 10; + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + + cjson_value = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (cjson_value, is_null); + assert_that (error_message, + is_equal_to_string ("container exceeds size limit of 10 bytes")); + + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_expand_unexpected_curly_brace) +{ + cJSON *cjson_value; + gchar *error_message; + INIT_JSON_PARSER ("[ 123 }"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + + cjson_value = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (cjson_value, is_null); + assert_that (error_message, + is_equal_to_string ("unexpected closing curly brace")); + + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_expand_unexpected_square_bracket) +{ + cJSON *cjson_value; + gchar *error_message; + INIT_JSON_PARSER ("{ \"A\": 123 ]"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_OBJECT_START)); + + cjson_value = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (cjson_value, is_null); + assert_that (error_message, + is_equal_to_string ("unexpected closing square bracket")); + + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_expand_eof) +{ + cJSON *cjson_value; + gchar *error_message; + INIT_JSON_PARSER ("[ 123"); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + + cjson_value = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (cjson_value, is_null); + assert_that (error_message, is_equal_to_string ("unexpected EOF")); + + CLEANUP_JSON_PARSER; +} + +Ensure (jsonpull, fails_for_expand_read_error) +{ + cJSON *cjson_value; + gchar *error_message; + INIT_READ_ERROR_JSON_PARSER ("[ 123 "); + + gvm_json_pull_parser_next (&parser, &event); + assert_that (event.type, is_equal_to (GVM_JSON_PULL_EVENT_ARRAY_START)); + + cjson_value = gvm_json_pull_expand_container (&parser, &error_message); + assert_that (cjson_value, is_null); + assert_that (error_message, is_equal_to_string (JSON_READ_ERROR)); + + CLEANUP_JSON_PARSER; +} + +int +main (int argc, char **argv) +{ + TestSuite *suite; + + suite = create_test_suite (); + + add_test_with_context (suite, jsonpull, can_json_escape_strings); + + add_test_with_context (suite, jsonpull, can_init_parser_with_defaults); + + add_test_with_context (suite, jsonpull, can_parse_false); + add_test_with_context (suite, jsonpull, can_parse_true); + add_test_with_context (suite, jsonpull, can_parse_null); + + add_test_with_context (suite, jsonpull, can_parse_empty_strings); + add_test_with_context (suite, jsonpull, can_parse_strings_with_content); + + add_test_with_context (suite, jsonpull, can_parse_integer_numbers); + add_test_with_context (suite, jsonpull, can_parse_floating_point_numbers); + + add_test_with_context (suite, jsonpull, can_parse_empty_arrays); + add_test_with_context (suite, jsonpull, can_parse_single_elem_arrays); + add_test_with_context (suite, jsonpull, can_parse_multiple_elem_arrays); + + add_test_with_context (suite, jsonpull, can_parse_empty_objects); + add_test_with_context (suite, jsonpull, can_parse_single_elem_objects); + add_test_with_context (suite, jsonpull, can_parse_multiple_elem_objects); + add_test_with_context (suite, jsonpull, can_parse_nested_containers); + add_test_with_context (suite, jsonpull, can_expand_arrays); + add_test_with_context (suite, jsonpull, can_expand_objects); + + add_test_with_context (suite, jsonpull, fails_for_read_error); + + add_test_with_context (suite, jsonpull, fails_for_misspelled_true); + add_test_with_context (suite, jsonpull, fails_for_incomplete_true); + add_test_with_context (suite, jsonpull, fails_for_misspelled_false); + add_test_with_context (suite, jsonpull, fails_for_misspelled_null); + + add_test_with_context (suite, jsonpull, fails_for_string_eof); + add_test_with_context (suite, jsonpull, fails_for_string_read_error); + add_test_with_context (suite, jsonpull, fails_for_overlong_string); + add_test_with_context (suite, jsonpull, fails_for_invalid_string); + + add_test_with_context (suite, jsonpull, fails_for_number_read_error); + add_test_with_context (suite, jsonpull, fails_for_overlong_number); + add_test_with_context (suite, jsonpull, fails_for_invalid_number); + + add_test_with_context (suite, jsonpull, fails_for_array_eof); + add_test_with_context (suite, jsonpull, fails_for_array_eof_after_value); + add_test_with_context (suite, jsonpull, fails_for_array_eof_after_comma); + add_test_with_context (suite, jsonpull, fails_for_array_read_error); + add_test_with_context (suite, jsonpull, fails_for_invalid_array_bracket); + add_test_with_context (suite, jsonpull, + fails_for_invalid_array_bracket_after_value); + add_test_with_context (suite, jsonpull, fails_for_invalid_array_other_char); + add_test_with_context (suite, jsonpull, + fails_for_invalid_array_other_char_after_value); + + add_test_with_context (suite, jsonpull, fails_for_object_key_eof); + add_test_with_context (suite, jsonpull, fails_for_object_key_read_error); + add_test_with_context (suite, jsonpull, fails_for_object_key_invalid_string); + add_test_with_context (suite, jsonpull, fails_for_invalid_object_key_bracket); + add_test_with_context (suite, jsonpull, + fails_for_invalid_object_key_other_char); + + add_test_with_context (suite, jsonpull, fails_for_object_colon_eof); + add_test_with_context (suite, jsonpull, fails_for_object_colon_read_error); + add_test_with_context (suite, jsonpull, fails_for_object_colon_other_char); + + add_test_with_context (suite, jsonpull, fails_for_object_value_eof); + add_test_with_context (suite, jsonpull, fails_for_object_value_read_error); + add_test_with_context (suite, jsonpull, fails_for_object_value_curly_brace); + add_test_with_context (suite, jsonpull, + fails_for_object_value_square_bracket); + add_test_with_context (suite, jsonpull, fails_for_object_eof_after_value); + add_test_with_context (suite, jsonpull, fails_for_object_eof_after_comma); + add_test_with_context (suite, jsonpull, + fails_for_object_square_bracket_after_value); + + add_test_with_context (suite, jsonpull, fails_for_read_error_after_doc_end); + add_test_with_context (suite, jsonpull, fails_for_content_after_doc_end); + + add_test_with_context (suite, jsonpull, fails_for_expand_before_container); + add_test_with_context (suite, jsonpull, fails_for_expand_after_value); + add_test_with_context (suite, jsonpull, fails_for_expand_invalid_content); + add_test_with_context (suite, jsonpull, fails_for_expand_overlong); + add_test_with_context (suite, jsonpull, + fails_for_expand_unexpected_curly_brace); + add_test_with_context (suite, jsonpull, + fails_for_expand_unexpected_square_bracket); + add_test_with_context (suite, jsonpull, fails_for_expand_read_error); + add_test_with_context (suite, jsonpull, fails_for_expand_eof); + + if (argc > 1) + return run_single_test (suite, argv[1], create_text_reporter ()); + return run_test_suite (suite, create_text_reporter ()); +}