summaryrefslogtreecommitdiffstats
path: root/src/lib/test-json-parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/test-json-parser.c')
-rw-r--r--src/lib/test-json-parser.c436
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();
+}