diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
commit | 0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch) | |
tree | 3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/lib/test-json-parser.c | |
parent | Initial commit. (diff) | |
download | dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip |
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/test-json-parser.c')
-rw-r--r-- | src/lib/test-json-parser.c | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/src/lib/test-json-parser.c b/src/lib/test-json-parser.c new file mode 100644 index 0000000..30ac5da --- /dev/null +++ b/src/lib/test-json-parser.c @@ -0,0 +1,436 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "str.h" +#include "istream-private.h" +#include "json-parser.h" + +#define TYPE_SKIP 100 +#define TYPE_STREAM 101 + +static const char json_input[] = + "{\n" + "\t\"key\"\t:\t\t\"string\"," + " \"key2\" : 1234, \n" + "\"key3\":true," + "\"key4\":false," + "\"skip1\": \"jsifjaisfjiasji\"," + "\"skip2\": { \"x\":{ \"y\":123}, \"z\":[5,[6],{\"k\":0},3]}," + "\"key5\":null," + "\"key6\": {}," + "\"key7\": {" + " \"sub1\":\"value\"" + "}," + "\"key8\": {" + " \"sub2\":-12.456,\n" + " \"sub3\":12.456e9,\n" + " \"sub4\":0.456e-789" + "}," + "\"key9\": \"foo\\\\\\\"\\b\\f\\n\\r\\t\\u0001\\u10ff\"," + "\"key10\": \"foo\\\\\\\"\\b\\f\\n\\r\\t\\u0001\\u10ff\"," + "\"key11\": []," + "\"key12\": [ \"foo\" , 5.24,[true],{\"aobj\":[]}]," + "\"key13\": \"\\ud801\\udc37\"," + "\"key14\": \"\xd8\xb3\xd9\x84\xd8\xa7\xd9\x85\"," + "\"key15\": \"\\u10000\"" + "}\n"; + +static const struct { + enum json_type type; + const char *value; +} json_output[] = { + { JSON_TYPE_OBJECT_KEY, "key" }, + { JSON_TYPE_STRING, "string" }, + { JSON_TYPE_OBJECT_KEY, "key2" }, + { JSON_TYPE_NUMBER, "1234" }, + { JSON_TYPE_OBJECT_KEY, "key3" }, + { JSON_TYPE_TRUE, "true" }, + { JSON_TYPE_OBJECT_KEY, "key4" }, + { JSON_TYPE_FALSE, "false" }, + { JSON_TYPE_OBJECT_KEY, "skip1" }, + { TYPE_SKIP, NULL }, + { JSON_TYPE_OBJECT_KEY, "skip2" }, + { TYPE_SKIP, NULL }, + { JSON_TYPE_OBJECT_KEY, "key5" }, + { JSON_TYPE_NULL, NULL }, + { JSON_TYPE_OBJECT_KEY, "key6" }, + { JSON_TYPE_OBJECT, NULL }, + { JSON_TYPE_OBJECT_END, NULL }, + { JSON_TYPE_OBJECT_KEY, "key7" }, + { JSON_TYPE_OBJECT, NULL }, + { JSON_TYPE_OBJECT_KEY, "sub1" }, + { JSON_TYPE_STRING, "value" }, + { JSON_TYPE_OBJECT_END, NULL }, + { JSON_TYPE_OBJECT_KEY, "key8" }, + { JSON_TYPE_OBJECT, NULL }, + { JSON_TYPE_OBJECT_KEY, "sub2" }, + { JSON_TYPE_NUMBER, "-12.456" }, + { JSON_TYPE_OBJECT_KEY, "sub3" }, + { JSON_TYPE_NUMBER, "12.456e9" }, + { JSON_TYPE_OBJECT_KEY, "sub4" }, + { JSON_TYPE_NUMBER, "0.456e-789" }, + { JSON_TYPE_OBJECT_END, NULL }, + { JSON_TYPE_OBJECT_KEY, "key9" }, + { JSON_TYPE_STRING, "foo\\\"\b\f\n\r\t\001\xe1\x83\xbf" }, + { JSON_TYPE_OBJECT_KEY, "key10" }, + { TYPE_STREAM, "foo\\\"\b\f\n\r\t\001\xe1\x83\xbf" }, + { JSON_TYPE_OBJECT_KEY, "key11" }, + { JSON_TYPE_ARRAY, NULL }, + { JSON_TYPE_ARRAY_END, NULL }, + { JSON_TYPE_OBJECT_KEY, "key12" }, + { JSON_TYPE_ARRAY, NULL }, + { JSON_TYPE_STRING, "foo" }, + { JSON_TYPE_NUMBER, "5.24" }, + { JSON_TYPE_ARRAY, NULL }, + { JSON_TYPE_TRUE, "true" }, + { JSON_TYPE_ARRAY_END, NULL }, + { JSON_TYPE_OBJECT, NULL }, + { JSON_TYPE_OBJECT_KEY, "aobj" }, + { JSON_TYPE_ARRAY, NULL }, + { JSON_TYPE_ARRAY_END, NULL }, + { JSON_TYPE_OBJECT_END, NULL }, + { JSON_TYPE_ARRAY_END, NULL }, + { JSON_TYPE_OBJECT_KEY, "key13" }, + { JSON_TYPE_STRING, "\xf0\x90\x90\xb7" }, + { JSON_TYPE_OBJECT_KEY, "key14" }, + { JSON_TYPE_STRING, "\xd8\xb3\xd9\x84\xd8\xa7\xd9\x85" }, + { JSON_TYPE_OBJECT_KEY, "key15" }, + { JSON_TYPE_STRING, "\xe1\x80\x80""0" }, +}; + +static int +stream_read_value(struct istream **input, const char **value_r) +{ + const unsigned char *data; + size_t size; + ssize_t ret; + + while ((ret = i_stream_read(*input)) > 0) ; + if (ret == 0) + return 0; + i_assert(ret == -1); + if ((*input)->stream_errno != 0) + return -1; + + data = i_stream_get_data(*input, &size); + *value_r = t_strndup(data, size); + i_stream_unref(input); + return 1; +} + +static void test_json_parser_success(bool full_size) +{ + struct json_parser *parser; + struct istream *input, *jsoninput = NULL; + enum json_type type; + const char *value, *error; + unsigned int i, pos, json_input_len = strlen(json_input); + int ret = 0; + + test_begin(full_size ? "json parser" : "json parser (nonblocking)"); + input = test_istream_create_data(json_input, json_input_len); + test_istream_set_allow_eof(input, FALSE); + parser = json_parser_init(input); + + i = full_size ? json_input_len : 0; + for (pos = 0; i <= json_input_len; i++) { + test_istream_set_size(input, i); + + for (;;) { + value = NULL; + if (pos < N_ELEMENTS(json_output) && + json_output[pos].type == (enum json_type)TYPE_SKIP) { + json_parse_skip_next(parser); + pos++; + continue; + } else if (pos == N_ELEMENTS(json_output) || + json_output[pos].type != (enum json_type)TYPE_STREAM) { + ret = json_parse_next(parser, &type, &value); + } else { + ret = jsoninput != NULL ? 1 : + json_parse_next_stream(parser, &jsoninput); + if (ret > 0 && jsoninput != NULL) + ret = stream_read_value(&jsoninput, &value); + type = TYPE_STREAM; + } + if (ret <= 0) + break; + + i_assert(pos < N_ELEMENTS(json_output)); + test_assert_idx(json_output[pos].type == type, pos); + test_assert_idx(null_strcmp(json_output[pos].value, value) == 0, pos); + + pos++; + } + test_assert_idx(ret == 0, pos); + } + test_assert(pos == N_ELEMENTS(json_output)); + test_istream_set_allow_eof(input, TRUE); + test_assert(json_parse_next(parser, &type, &value) == -1); + + i_stream_unref(&input); + test_assert(json_parser_deinit(&parser, &error) == 0); + test_end(); +} + +static void test_json_parser_skip_array(void) +{ + static const char *test_input = + "[ 1, {\"foo\": 1 }, 2, \"bar\", 3, 1.234, 4, [], 5, [[]], 6, true ]"; + struct json_parser *parser; + struct istream *input; + enum json_type type; + const char *value, *error; + int i; + + test_begin("json parser skip array"); + + input = test_istream_create_data(test_input, strlen(test_input)); + parser = json_parser_init_flags(input, JSON_PARSER_NO_ROOT_OBJECT); + test_assert(json_parse_next(parser, &type, &value) > 0 && + type == JSON_TYPE_ARRAY); + for (i = 1; i <= 6; i++) { + test_assert(json_parse_next(parser, &type, &value) > 0 && + type == JSON_TYPE_NUMBER && atoi(value) == i); + json_parse_skip_next(parser); + } + test_assert(json_parse_next(parser, &type, &value) > 0 && + type == JSON_TYPE_ARRAY_END); + test_assert(json_parser_deinit(&parser, &error) == 0); + i_stream_unref(&input); + test_end(); +} + +static void test_json_parser_skip_object_fields(void) +{ + static const char *test_input = + "{\"access_token\":\"9a2dea3c-f8be-4271-b9c8-5b37da4f2f7e\"," + "\"grant_type\":\"authorization_code\"," + "\"openid\":\"\"," + "\"scope\":[\"openid\",\"profile\",\"email\"]," + "\"profile\":\"\"," + "\"realm\":\"/employees\"," + "\"token_type\":\"Bearer\"," + "\"expires_in\":2377," + "\"client_i\\u0064\":\"mosaic\\u0064\"," + "\"email\":\"\"," + "\"extensions\":" + "{\"algorithm\":\"cuttlefish\"," + "\"tentacles\":8" + "}" + "}"; + static const char *const keys[] = { + "access_token", "grant_type", "openid", "scope", "profile", + "realm", "token_type", "expires_in", "client_id", "email", + "extensions" + }; + static const unsigned int keys_count = N_ELEMENTS(keys); + struct json_parser *parser; + struct istream *input; + enum json_type type; + const char *value, *error; + unsigned int i; + size_t pos; + int ret; + + test_begin("json parser skip object fields (by key)"); + input = test_istream_create_data(test_input, strlen(test_input)); + parser = json_parser_init(input); + for (i = 0; i < keys_count; i++) { + ret = json_parse_next(parser, &type, &value); + if (ret < 0) + break; + test_assert(ret > 0 && type == JSON_TYPE_OBJECT_KEY); + test_assert(strcmp(value, keys[i]) == 0); + json_parse_skip_next(parser); + } + test_assert(i == keys_count); + test_assert(json_parser_deinit(&parser, &error) == 0); + i_stream_unref(&input); + + i = 0; + input = test_istream_create_data(test_input, strlen(test_input)); + parser = json_parser_init(input); + for (pos = 0; pos <= strlen(test_input)*2; pos++) { + test_istream_set_size(input, pos/2); + ret = json_parse_next(parser, &type, &value); + if (ret == 0) + continue; + if (ret < 0) + break; + i_assert(i < keys_count); + test_assert(ret > 0 && type == JSON_TYPE_OBJECT_KEY); + test_assert(strcmp(value, keys[i]) == 0); + json_parse_skip_next(parser); + i++; + } + test_assert(i == keys_count); + test_assert(json_parser_deinit(&parser, &error) == 0); + i_stream_unref(&input); + test_end(); + + test_begin("json parser skip object fields (by value type)"); + input = test_istream_create_data(test_input, strlen(test_input)); + parser = json_parser_init(input); + for (i = 0; i < keys_count; i++) { + ret = json_parse_next(parser, &type, &value); + if (ret < 0) + break; + test_assert(ret > 0 && type == JSON_TYPE_OBJECT_KEY); + test_assert(strcmp(value, keys[i]) == 0); + ret = json_parse_next(parser, &type, &value); + test_assert(ret > 0 && type != JSON_TYPE_OBJECT_KEY); + json_parse_skip(parser); + } + test_assert(i == keys_count); + test_assert(json_parser_deinit(&parser, &error) == 0); + i_stream_unref(&input); + + i = 0; + input = test_istream_create_data(test_input, strlen(test_input)); + parser = json_parser_init(input); + for (pos = 0; pos <= strlen(test_input)*2; pos++) { + test_istream_set_size(input, pos/2); + ret = json_parse_next(parser, &type, &value); + if (ret < 0) + break; + if (ret == 0) + continue; + test_assert(ret > 0); + if (type == JSON_TYPE_OBJECT_KEY) { + i_assert(i < keys_count); + test_assert(strcmp(value, keys[i]) == 0); + i++; + } else { + json_parse_skip(parser); + } + } + test_assert(i == keys_count); + test_assert(json_parser_deinit(&parser, &error) == 0); + i_stream_unref(&input); + + test_end(); +} + +static int +test_json_parse_input(const void *test_input, size_t test_input_size, + enum json_parser_flags flags) +{ + struct json_parser *parser; + struct istream *input; + enum json_type type; + const char *value, *error; + int ret = 0; + + input = test_istream_create_data(test_input, test_input_size); + parser = json_parser_init_flags(input, flags); + while (json_parse_next(parser, &type, &value) > 0) + ret++; + if (json_parser_deinit(&parser, &error) < 0) + ret = -1; + i_stream_unref(&input); + return ret; +} + +static void test_json_parser_primitive_values(void) +{ + static const struct { + const char *str; + int ret; + } test_inputs[] = { + { "\"hello\"", 1 }, + { "null", 1 }, + { "1234", 1 }, + { "1234.1234", 1 }, + { "{}", 2 }, + { "[]", 2 }, + { "true", 1 }, + { "false", 1 } + }; + unsigned int i; + + test_begin("json_parser (primitives)"); + for (i = 0; i < N_ELEMENTS(test_inputs); i++) + test_assert_idx(test_json_parse_input(test_inputs[i].str, + strlen(test_inputs[i].str), + JSON_PARSER_NO_ROOT_OBJECT) == test_inputs[i].ret, i); + test_end(); +} + +static void test_json_parser_errors(void) +{ + static const char *test_inputs[] = { + "{", + "{:}", + "{\"foo\":}", + "{\"foo\" []}", + "{\"foo\": [1}", + "{\"foo\": [1,]}", + "{\"foo\": [1,]}", + "{\"foo\": 1,}", + "{\"foo\": 1.}}", + "{\"foo\": 1},{}", + "{\"foo\": \"\\ud808\"}", + "{\"foo\": \"\\udfff\"}", + "{\"foo\": \"\\uyyyy\"}", + }; + unsigned int i; + + test_begin("json parser error handling"); + for (i = 0; i < N_ELEMENTS(test_inputs); i++) + test_assert_idx(test_json_parse_input(test_inputs[i], + strlen(test_inputs[i]), + 0) < 0, i); + test_end(); +} + +static void test_json_parser_nuls_in_string(void) +{ + static const unsigned char test_input[] = + { '{', '"', 'k', '"', ':', '"', '\0', '"', '}' }; + static const unsigned char test_input2[] = + { '{', '"', 'k', '"', ':', '"', '\\', '\0', '"', '}' }; + static const unsigned char test_input3[] = + { '{', '"', 'k', '"', ':', '"', '\\', 'u', '0', '0', '0', '0', '"', '}' }; + + test_begin("json parser nuls in string"); + test_assert(test_json_parse_input(test_input, sizeof(test_input), 0) < 0); + test_assert(test_json_parse_input(test_input2, sizeof(test_input2), 0) < 0); + test_assert(test_json_parse_input(test_input3, sizeof(test_input3), 0) < 0); + test_end(); +} + +static void test_json_append_escaped(void) +{ + string_t *str = t_str_new(32); + + test_begin("json_append_escaped()"); + json_append_escaped(str, "\b\f\r\n\t\"\\\001\002-\xC3\xA4\xf0\x90\x90\xb7\xe2\x80\xa8\xe2\x80\xa9\xff"); + test_assert(strcmp(str_c(str), "\\b\\f\\r\\n\\t\\\"\\\\\\u0001\\u0002-\xC3\xA4\xf0\x90\x90\xb7\\u2028\\u2029" UNICODE_REPLACEMENT_CHAR_UTF8) == 0); + test_end(); +} + +static void test_json_append_escaped_data(void) +{ + static const unsigned char test_input[] = + "\b\f\r\n\t\"\\\000\001\002-\xC3\xA4\xf0\x90\x90\xb7\xe2\x80\xa8\xe2\x80\xa9\xff"; + string_t *str = t_str_new(32); + + test_begin("json_append_escaped_data()"); + json_append_escaped_data(str, test_input, sizeof(test_input)-1); + test_assert(strcmp(str_c(str), "\\b\\f\\r\\n\\t\\\"\\\\\\u0000\\u0001\\u0002-\xC3\xA4\xf0\x90\x90\xb7\\u2028\\u2029" UNICODE_REPLACEMENT_CHAR_UTF8) == 0); + test_end(); +} + +void test_json_parser(void) +{ + test_json_parser_success(TRUE); + test_json_parser_success(FALSE); + test_json_parser_skip_array(); + test_json_parser_skip_object_fields(); + test_json_parser_primitive_values(); + test_json_parser_errors(); + test_json_parser_nuls_in_string(); + test_json_append_escaped(); + test_json_append_escaped_data(); +} |