From: Stephan Bosch Date: Wed, 7 Aug 2019 18:52:24 +0000 (+0200) Subject: lib-json: Implement low-level JSON generator X-Git-Tag: 2.4.0~2394 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4d34586bc9af87decd5c6bf31466b6e2f8c17997;p=thirdparty%2Fdovecot%2Fcore.git lib-json: Implement low-level JSON generator --- diff --git a/src/lib-json/Makefile.am b/src/lib-json/Makefile.am index 6ce494ea9f..e170e8c859 100644 --- a/src/lib-json/Makefile.am +++ b/src/lib-json/Makefile.am @@ -7,16 +7,20 @@ AM_CPPFLAGS = \ libjson_la_SOURCES = \ json-syntax.c \ json-types.c \ - json-parser.new.c + json-parser.new.c \ + json-generator.c libjson_la_LIBADD = -lm headers = \ json-syntax.h \ json-types.h \ - json-parser.new.h + json-parser.new.h \ + json-generator.h test_programs = \ - test-json-parser + test-json-parser \ + test-json-generator \ + test-json-io noinst_PROGRAMS = $(test_programs) @@ -39,6 +43,20 @@ test_json_parser_LDADD = \ test_json_parser_DEPENDENCIES = \ $(test_deps) +test_json_generator_SOURCE = \ + test-json-generator.c +test_json_generator_LDADD = \ + $(test_libs) +test_json_generator_DEPENDENCIES = \ + $(test_deps) + +test_json_io_SOURCE = \ + test-json-io.c +test_json_io_LDADD = \ + $(test_libs) +test_json_io_DEPENDENCIES = \ + $(test_deps) + pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) diff --git a/src/lib-json/json-generator.c b/src/lib-json/json-generator.c new file mode 100644 index 0000000000..858bfb91a9 --- /dev/null +++ b/src/lib-json/json-generator.c @@ -0,0 +1,1034 @@ +/* Copyright (c) 2017-2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "hex-dec.h" +#include "ostream-private.h" + +#include "json-syntax.h" +#include "json-generator.h" + +#include + +enum json_generator_state { + JSON_GENERATOR_STATE_VALUE = 0, + JSON_GENERATOR_STATE_VALUE_END, + JSON_GENERATOR_STATE_VALUE_NEXT, + JSON_GENERATOR_STATE_OBJECT_MEMBER, + JSON_GENERATOR_STATE_OBJECT_VALUE, + JSON_GENERATOR_STATE_STRING, + JSON_GENERATOR_STATE_TEXT, + JSON_GENERATOR_STATE_END, +}; + +struct json_generator_level { + bool object:1; +}; + +struct json_generator { + struct ostream *output; + enum json_generator_flags flags; + + /* Buffer for elements that the generator has assumed responsibility for + by returning > 0, but could not be written to the output stream right + away. */ + string_t *buf; + /* Write position */ + size_t buf_pos; + + /* API state: based on called API functions */ + enum json_generator_state state; + /* Write state: based on what is written to the output so far */ + enum json_generator_state write_state; + + /* Stack of syntax levels */ + ARRAY(struct json_generator_level) level_stack; + /* API state: stack position of opened syntax levels */ + unsigned int level_stack_pos; + /* Write state: stack position of written syntax levels */ + unsigned int level_stack_written; + + /* We are in an object */ + bool object_level_written:1; /* write state */ + bool object_level:1; /* API state */ + /* We closed an empty string */ + bool string_empty:1; /* API state */ +}; + +static struct json_generator * +json_generator_new(enum json_generator_flags flags) +{ + struct json_generator *generator; + + generator = i_new(struct json_generator, 1); + generator->flags = flags; + i_array_init(&generator->level_stack, 16); + + return generator; +} + +struct json_generator * +json_generator_init(struct ostream *output, enum json_generator_flags flags) +{ + struct json_generator *generator; + + generator = json_generator_new(flags); + generator->buf = str_new(default_pool, 128); + generator->output = output; + o_stream_ref(output); + + return generator; +} + +struct json_generator * +json_generator_init_str(string_t *buf, enum json_generator_flags flags) +{ + struct json_generator *generator; + + generator = json_generator_new(flags); + generator->buf = buf; + + return generator; +} + +void json_generator_deinit(struct json_generator **_generator) +{ + struct json_generator *generator = *_generator; + + if (generator == NULL) + return; + *_generator = NULL; + + if (generator->output != NULL) { + o_stream_unref(&generator->output); + str_free(&generator->buf); + } + array_free(&generator->level_stack); + i_free(generator); +} + +static inline size_t +json_generator_bytes_available(struct json_generator *generator) +{ + if (generator->output == NULL || generator->output->blocking) + return SIZE_MAX; + return o_stream_get_buffer_avail_size(generator->output); +} + +static int +json_generator_make_space(struct json_generator *generator, size_t space, + size_t *avail_r) +{ + *avail_r = json_generator_bytes_available(generator); + if (*avail_r >= space) + return 1; + if (o_stream_flush(generator->output) < 0) + return -1; + *avail_r = json_generator_bytes_available(generator); + return (*avail_r >= space ? 1 : 0); +} + +static int +json_generator_write(struct json_generator *generator, + const void *data, size_t size) +{ + ssize_t ret; + + if (generator->output == NULL) { + str_append_data(generator->buf, data, size); + return 1; + } + ret = o_stream_send(generator->output, data, size); + if (ret < 0) + return -1; + i_assert((size_t)ret == size); + return 1; +} + +static inline int +json_generator_write_all(struct json_generator *generator, + const void *data, size_t size) +{ + size_t avail; + int ret; + + ret = json_generator_make_space(generator, size, &avail); + if (ret <= 0) + return ret; + + return json_generator_write(generator, data, size); +} + +static int +json_generator_write_buffered(struct json_generator *generator, + const void *data, size_t size, bool continued) +{ + size_t avail, write; + + if (!continued || generator->output == NULL || + str_len(generator->buf) == 0) { + /* Try to write to output first */ + if (json_generator_make_space(generator, size, &avail) < 0) + return -1; + write = (avail < size ? avail : size); + if (write > 0) { + i_assert(generator->output == NULL || + str_len(generator->buf) == 0); + if (json_generator_write(generator, data, write) < 0) + return -1; + data = PTR_OFFSET(data, write); + size -= write; + } + } + + if (size > 0) { + i_assert(generator->output != NULL); + /* Prevent buffer from growing needlessly */ + if (str_len(generator->buf) + size > 1024 && + generator->buf_pos > 0) + str_delete(generator->buf, 0, generator->buf_pos); + /* Append data to buffer */ + str_append_data(generator->buf, data, size); + } + return 1; +} + +static int json_generator_flush_buffer(struct json_generator *generator) +{ + const unsigned char *data; + size_t size, avail; + + if (generator->output == NULL) + return 1; + if (str_len(generator->buf) == 0) + return 1; + + data = str_data(generator->buf); + size = str_len(generator->buf); + i_assert(generator->buf_pos < size); + + data += generator->buf_pos; + size -= generator->buf_pos; + + if (json_generator_make_space(generator, size, &avail) < 0) + return -1; + if (avail == 0) + return 0; + if (avail < size) { + if (json_generator_write(generator, data, avail) < 0) + return -1; + generator->buf_pos += avail; + return 0; + } + if (json_generator_write(generator, data, size) < 0) + return -1; + generator->buf_pos = 0; + str_truncate(generator->buf, 0); + return 1; +} + +int json_generator_flush(struct json_generator *generator) +{ + bool hide_root = HAS_ALL_BITS(generator->flags, + JSON_GENERATOR_FLAG_HIDE_ROOT); + int ret; + + /* Flush buffer */ + ret = json_generator_flush_buffer(generator); + if (ret <= 0) + return ret; + /* Flush closing string */ + if (generator->write_state == JSON_GENERATOR_STATE_STRING && + generator->state != JSON_GENERATOR_STATE_STRING) { + ret = json_generator_write_all(generator, "\"", 1); + if (ret <= 0) + return ret; + generator->write_state = JSON_GENERATOR_STATE_VALUE_END; + } + /* Flush object member */ + if (generator->write_state == JSON_GENERATOR_STATE_OBJECT_VALUE) { + ret = json_generator_write_all(generator, ":", 1); + if (ret <= 0) + return ret; + generator->write_state = JSON_GENERATOR_STATE_VALUE; + } + /* Flush opening objects/arrays */ + for (;;) { + struct json_generator_level *level; + + i_assert(generator->level_stack_written <= + generator->level_stack_pos); + if (generator->level_stack_written == generator->level_stack_pos) + break; + + i_assert(generator->write_state != JSON_GENERATOR_STATE_STRING && + generator->write_state != JSON_GENERATOR_STATE_TEXT); + if (generator->write_state == JSON_GENERATOR_STATE_VALUE_END) + generator->write_state = JSON_GENERATOR_STATE_VALUE_NEXT; + if (generator->write_state == JSON_GENERATOR_STATE_VALUE_NEXT) { + ret = json_generator_write_all(generator, ",", 1); + if (ret <= 0) + return ret; + generator->write_state = JSON_GENERATOR_STATE_VALUE; + } + + // FIXME: add indent + + level = array_idx_get_space(&generator->level_stack, + generator->level_stack_written); + if (level->object) { + if (!hide_root || generator->level_stack_written > 0) { + ret = json_generator_write_all(generator, "{", 1); + if (ret <= 0) + return ret; + } + generator->level_stack_written++; + generator->write_state = JSON_GENERATOR_STATE_OBJECT_MEMBER; + generator->object_level_written = TRUE; + } else { + if (!hide_root || generator->level_stack_written > 0) { + ret = json_generator_write_all(generator, "[", 1); + if (ret <= 0) + return ret; + } + generator->level_stack_written++; + generator->object_level_written = FALSE; + generator->write_state = JSON_GENERATOR_STATE_VALUE; + } + } + /* Flush separator */ + switch (generator->write_state) { + /* Flush comma */ + case JSON_GENERATOR_STATE_VALUE_END: + if (generator->level_stack_pos == 0) { + generator->write_state = JSON_GENERATOR_STATE_END; + break; + } + if (generator->state != JSON_GENERATOR_STATE_STRING && + generator->state != JSON_GENERATOR_STATE_TEXT) + break; + generator->write_state = JSON_GENERATOR_STATE_VALUE_NEXT; + /* Fall through */ + case JSON_GENERATOR_STATE_VALUE_NEXT: + ret = json_generator_write_all(generator, ",", 1); + if (ret <= 0) + return ret; + if (generator->object_level_written) { + generator->write_state = JSON_GENERATOR_STATE_OBJECT_MEMBER; + } else { + generator->write_state = JSON_GENERATOR_STATE_VALUE; + } + break; + /* Flush colon */ + case JSON_GENERATOR_STATE_OBJECT_VALUE: + ret = json_generator_write_all(generator, ":", 1); + if (ret <= 0) + return ret; + generator->write_state = JSON_GENERATOR_STATE_VALUE; + break; + default: + break; + } + /* Flush opening empty string */ + if (generator->string_empty && + generator->write_state != JSON_GENERATOR_STATE_STRING) { + i_assert(generator->write_state == JSON_GENERATOR_STATE_VALUE || + generator->write_state == JSON_GENERATOR_STATE_OBJECT_VALUE); + ret = json_generator_write_all(generator, "\"", 1); + if (ret <= 0) + return ret; + generator->string_empty = FALSE; + ret = json_generator_write_all(generator, "\"", 1); + if (ret < 0) + return -1; + if (ret == 0) { + generator->write_state = JSON_GENERATOR_STATE_STRING; + return 0; + } + generator->write_state = JSON_GENERATOR_STATE_VALUE_END; + /* Flush opening string */ + } else if (generator->state == JSON_GENERATOR_STATE_STRING && + generator->write_state != JSON_GENERATOR_STATE_STRING) { + i_assert(generator->write_state == JSON_GENERATOR_STATE_VALUE || + generator->write_state == JSON_GENERATOR_STATE_OBJECT_VALUE); + ret = json_generator_write_all(generator, "\"", 1); + if (ret <= 0) + return ret; + generator->write_state = JSON_GENERATOR_STATE_STRING; + } + /* Flush opening text */ + if (generator->state == JSON_GENERATOR_STATE_TEXT && + generator->write_state != JSON_GENERATOR_STATE_TEXT) + generator->write_state = JSON_GENERATOR_STATE_TEXT; + return 1; +} + +/* + * value begin/end + */ + +static inline void +json_generator_value_begin(struct json_generator *generator) +{ + i_assert(generator->state == JSON_GENERATOR_STATE_VALUE); +} + +static inline int +json_generator_value_begin_flushed(struct json_generator *generator) +{ + int ret; + + json_generator_value_begin(generator); + if (generator->write_state == JSON_GENERATOR_STATE_VALUE_END) + generator->write_state = JSON_GENERATOR_STATE_VALUE_NEXT; + ret = json_generator_flush(generator); + if (ret <= 0) + return ret; + i_assert(generator->write_state == generator->state); + return 1; +} + +static inline void +json_generator_value_end(struct json_generator *generator) +{ + if (generator->level_stack_pos == 0) + generator->state = JSON_GENERATOR_STATE_END; + else if (generator->object_level) + generator->state = JSON_GENERATOR_STATE_OBJECT_MEMBER; + else + generator->state = JSON_GENERATOR_STATE_VALUE; + generator->write_state = JSON_GENERATOR_STATE_VALUE_END; +} + +/* + * number + */ + +int json_generate_number(struct json_generator *generator, intmax_t number) +{ + int ret; + + ret = json_generator_value_begin_flushed(generator); + if (ret <= 0) + return ret; + + str_printfa(generator->buf, "%"PRIdMAX, number); + + json_generator_value_end(generator); + return (json_generator_flush(generator) < 0 ? -1 : 1); +} + +int json_generate_number_raw(struct json_generator *generator, + const char *number) +{ + int ret; + + ret = json_generator_value_begin_flushed(generator); + if (ret <= 0) + return ret; + if (json_generator_write_buffered(generator, number, + strlen(number), FALSE) < 0) + return -1; + json_generator_value_end(generator); + return 1; +} + +/* + * string + */ + +void json_generate_string_open(struct json_generator *generator) +{ + json_generator_value_begin(generator); + generator->state = JSON_GENERATOR_STATE_STRING; +} + +static ssize_t +json_generate_string_write_data(struct json_generator *generator, + const void *data, size_t size, + bool buffered, bool last) +{ + const unsigned char *p, *pbegin, *poffset, *pend; + size_t avail; + int ret; + + p = pbegin = poffset = data; + pend = p + size; + while (p < pend) { + unsigned char esc_hex[6]; + const char *esc = NULL; + unsigned int esc_len = 2; + int octets = 0; + unichar_t ch; + + if (buffered) + avail = SIZE_MAX; + else { + ret = json_generator_make_space(generator, pend - p, + &avail); + if (ret < 0) + return -1; + } + if (avail == 0) + break; + + poffset = p; + while (avail > 0 && p < pend && esc == NULL) { + octets = uni_utf8_get_char_n(p, (pend - p), &ch); + if (octets < 0 || (octets == 0 && last) || + (octets > 0 && !uni_is_valid_ucs4(ch))) { + /* Replace invalid UTF-8/Unicode with the + replacement character. */ + esc = UNICODE_REPLACEMENT_CHAR_UTF8; + esc_len = UTF8_REPLACEMENT_CHAR_LEN; + octets = (octets <= 0 ? 1 : octets); + break; + } + if (octets == 0 || (size_t)octets > avail) + break; + switch (ch) { + /* %x22 / ; " quotation mark U+0022 */ + case '"': + esc = "\\\""; + break; + /* %x5C / ; \ reverse solidus U+005C */ + case '\\': + esc = "\\\\"; + break; + /* %x62 / ; b backspace U+0008 */ + case '\b': + esc = "\\b"; + break; + /* %x66 / ; f form feed U+000C */ + case '\f': + esc = "\\f"; + break; + /* %x6E / ; n line feed U+000A */ + case '\n': + esc = "\\n"; + break; + /* %x72 / ; r carriage return U+000D */ + case '\r': + esc = "\\r"; + break; + /* %x74 / ; t tab U+0009 */ + case '\t': + esc = "\\t"; + break; + default: + if (ch < 0x20 || ch == 0x2028 || ch == 0x2029) { + esc_hex[0] = '\\'; + esc_hex[1] = 'u'; + dec2hex(&esc_hex[2], (uintmax_t)ch, 4); + esc = (const char *)esc_hex; + esc_len = sizeof(esc_hex); + } else { + p += octets; + avail -= octets; + } + } + } + + if ((p - poffset) > 0) { + if (buffered) { + if (json_generator_write_buffered( + generator, poffset, p -poffset, + TRUE) < 0) + return -1; + } else { + if (json_generator_write( + generator, poffset, p - poffset) < 0) + return -1; + } + } + if (esc != NULL) { + if (esc_len > avail) { + break; + } else { + if (buffered) { + if (json_generator_write_buffered( + generator, esc, esc_len, + TRUE) < 0) + return -1; + } else { + if (json_generator_write( + generator, esc, esc_len) < 0) + return -1; + } + p += octets; + } + } + if (octets == 0 || (size_t)octets > avail) + break; + } + + return (ssize_t)(p - pbegin); +} + +ssize_t json_generate_string_more(struct json_generator *generator, + const void *data, size_t size, bool last) +{ + int ret; + + i_assert(generator->state == JSON_GENERATOR_STATE_STRING); + ret = json_generator_flush(generator); + if (ret <= 0) + return (ssize_t)ret; + i_assert(generator->write_state == JSON_GENERATOR_STATE_STRING); + + return json_generate_string_write_data(generator, data, size, + FALSE, last); +} + +void json_generate_string_close(struct json_generator *generator) +{ + i_assert(generator->state == JSON_GENERATOR_STATE_STRING); + if (generator->write_state != JSON_GENERATOR_STATE_STRING) { + /* This function does not flush first before changing state, nor + does the string_open() function. So, we need to remember + closing the an empty string, because otherwise nothing will + be emitted. */ + generator->string_empty = TRUE; + } + if (generator->level_stack_pos == 0) + generator->state = JSON_GENERATOR_STATE_END; + else if (generator->object_level) + generator->state = JSON_GENERATOR_STATE_OBJECT_MEMBER; + else + generator->state = JSON_GENERATOR_STATE_VALUE; +} + +int json_generate_string_write_close(struct json_generator *generator) +{ + if (generator->state == JSON_GENERATOR_STATE_STRING) + json_generate_string_close(generator); + return json_generator_flush(generator); +} + +int json_generate_string_data(struct json_generator *generator, + const void *data, size_t size) +{ + int ret; + + ret = json_generator_value_begin_flushed(generator); + if (ret <= 0) + return ret; + + if (json_generator_write_buffered(generator, "\"", 1, FALSE) < 0) + return -1; + if (json_generate_string_write_data(generator, data, size, + TRUE, TRUE) < 0) + return -1; + if (json_generator_write_buffered(generator, "\"", 1, TRUE) < 0) + return -1; + + json_generator_value_end(generator); + return 1; +} + +int json_generate_string(struct json_generator *generator, const char *str) +{ + return json_generate_string_data(generator, + (const unsigned char *)str, + strlen(str)); +} + +/* + * null, true, false + */ + +static int +json_generate_literal(struct json_generator *generator, const char *literal) +{ + size_t lit_size = strlen(literal); + int ret; + + ret = json_generator_value_begin_flushed(generator); + if (ret <= 0) + return ret; + + ret = json_generator_write_all(generator, literal, lit_size); + if (ret <= 0) + return ret; + + json_generator_value_end(generator); + return ret; +} + +int json_generate_null(struct json_generator *generator) +{ + return json_generate_literal(generator, "null"); +} + +int json_generate_false(struct json_generator *generator) +{ + return json_generate_literal(generator, "false"); +} + +int json_generate_true(struct json_generator *generator) +{ + return json_generate_literal(generator, "true"); +} + +/* + * stack level + */ + +static void +json_generator_level_open(struct json_generator *generator, bool object) +{ + struct json_generator_level *level; + + level = array_idx_get_space(&generator->level_stack, + generator->level_stack_pos++); + i_zero(level); + level->object = object; + generator->object_level = object; +} + +static void +json_generator_level_close(struct json_generator *generator, bool object) +{ + struct json_generator_level *level, *under_level; + + i_assert(generator->level_stack_pos > 0); + + i_assert(generator->level_stack_written == generator->level_stack_pos); + generator->level_stack_written--; + + if (generator->level_stack_pos < 2) { + generator->object_level_written = FALSE; + generator->object_level = FALSE; + } else { + under_level = array_idx_modifiable( + &generator->level_stack, generator->level_stack_pos-2); + generator->object_level_written = under_level->object; + generator->object_level = under_level->object; + } + level = array_idx_modifiable(&generator->level_stack, + --generator->level_stack_pos); + i_assert(level->object == object); +} + +/* + * array + */ + +void json_generate_array_open(struct json_generator *generator) +{ + json_generator_value_begin(generator); + json_generator_level_open(generator, FALSE); + generator->state = JSON_GENERATOR_STATE_VALUE; +} + +int json_generate_array_close(struct json_generator *generator) +{ + bool hide_root = HAS_ALL_BITS(generator->flags, + JSON_GENERATOR_FLAG_HIDE_ROOT); + int ret; + + i_assert(generator->state == JSON_GENERATOR_STATE_VALUE); + ret = json_generator_flush(generator); + if (ret <= 0) + return ret; + i_assert(generator->write_state == JSON_GENERATOR_STATE_VALUE || + generator->write_state == JSON_GENERATOR_STATE_VALUE_END); + + i_assert(generator->level_stack_written > 0); + if (!hide_root || generator->level_stack_written > 1) { + ret = json_generator_write_all(generator, "]", 1); + if (ret <= 0) + return ret; + } + json_generator_level_close(generator, FALSE); + json_generator_value_end(generator); + return 1; +} + +/* + * object + */ + +void json_generate_object_open(struct json_generator *generator) +{ + json_generator_value_begin(generator); + json_generator_level_open(generator, TRUE); + generator->state = JSON_GENERATOR_STATE_OBJECT_MEMBER; +} + +int json_generate_object_member(struct json_generator *generator, + const char *name) +{ + int ret; + + i_assert(generator->state == JSON_GENERATOR_STATE_OBJECT_MEMBER); + if (generator->write_state == JSON_GENERATOR_STATE_VALUE_END) { + generator->write_state = JSON_GENERATOR_STATE_VALUE_NEXT; + } + ret = json_generator_flush(generator); + if (ret <= 0) + return ret; + i_assert(generator->write_state == generator->state); + generator->state = JSON_GENERATOR_STATE_VALUE; + + if (json_generator_write_buffered(generator, "\"", 1, FALSE) < 0) + return -1; + if (json_generate_string_write_data( + generator, name, strlen(name), TRUE, TRUE) < 0) + return -1; + if (json_generator_write_buffered(generator, "\"", 1, TRUE) < 0) + return -1; + generator->write_state = JSON_GENERATOR_STATE_OBJECT_VALUE; + return 1; +} + +int json_generate_object_close(struct json_generator *generator) +{ + bool hide_root = HAS_ALL_BITS(generator->flags, + JSON_GENERATOR_FLAG_HIDE_ROOT); + int ret; + + i_assert(generator->state == JSON_GENERATOR_STATE_OBJECT_MEMBER); + ret = json_generator_flush(generator); + if (ret <= 0) + return ret; + i_assert(generator->write_state == JSON_GENERATOR_STATE_OBJECT_MEMBER || + generator->write_state == JSON_GENERATOR_STATE_VALUE_END); + i_assert(generator->level_stack_written > 0); + if (!hide_root || generator->level_stack_written > 1) { + ret = json_generator_write_all(generator, "}", 1); + if (ret <= 0) + return ret; + } + json_generator_level_close(generator, TRUE); + json_generator_value_end(generator); + return 1; +} + +/* + * JSON-text + */ + +void json_generate_text_open(struct json_generator *generator) +{ + json_generator_value_begin(generator); + generator->state = JSON_GENERATOR_STATE_TEXT; +} + +static ssize_t +json_generate_text_write_data(struct json_generator *generator, + const void *data, size_t size, bool buffered) +{ + int ret; + + if (!buffered) { + size_t avail; + + ret = json_generator_make_space(generator, size, &avail); + if (ret < 0) + return -1; + if (avail == 0) + return 0; + if (size > avail) + size = avail; + } + + if (buffered) { + if (json_generator_write_buffered(generator, data, size, + FALSE) < 0) + return -1; + } else { + if (json_generator_write(generator, data, size) < 0) + return -1; + } + return (ssize_t)size; +} + +ssize_t json_generate_text_more(struct json_generator *generator, + const void *data, size_t size) +{ + int ret; + + i_assert(generator->state == JSON_GENERATOR_STATE_TEXT); + ret = json_generator_flush(generator); + if (ret <= 0) + return (ssize_t)ret; + i_assert(generator->write_state == JSON_GENERATOR_STATE_TEXT); + + return json_generate_text_write_data(generator, data, size, FALSE); +} + +int json_generate_text_close(struct json_generator *generator) +{ + int ret; + + i_assert(generator->state == JSON_GENERATOR_STATE_TEXT); + ret = json_generator_flush(generator); + if (ret <= 0) + return ret; + i_assert(generator->write_state == JSON_GENERATOR_STATE_TEXT); + + json_generator_value_end(generator); + return 1; +} + +int json_generate_text_data(struct json_generator *generator, + const void *data, size_t size) +{ + int ret; + + ret = json_generator_value_begin_flushed(generator); + if (ret <= 0) + return ret; + + if (json_generate_text_write_data(generator, data, size, TRUE) < 0) + return -1; + json_generator_value_end(generator); + return 1; +} + +int json_generate_text(struct json_generator *generator, const char *str) +{ + return json_generate_text_data(generator, (const unsigned char *)str, + strlen(str)); +} + +/* + * value + */ + +int json_generate_value(struct json_generator *generator, + enum json_type type, + const struct json_value *value) +{ + switch (type) { + /* string */ + case JSON_TYPE_STRING: + switch (value->content_type) { + case JSON_CONTENT_TYPE_STRING: + return json_generate_string(generator, + value->content.str); + case JSON_CONTENT_TYPE_DATA: + return json_generate_string_data( + generator, value->content.data->data, + value->content.data->size); + default: + break; + } + break; + /* number */ + case JSON_TYPE_NUMBER: + switch (value->content_type) { + case JSON_CONTENT_TYPE_STRING: + return json_generate_number_raw(generator, + value->content.str); + case JSON_CONTENT_TYPE_INTEGER: + return json_generate_number(generator, + value->content.intnum); + default: + break; + } + break; + /* true */ + case JSON_TYPE_TRUE: + return json_generate_true(generator); + /* false */ + case JSON_TYPE_FALSE: + return json_generate_false(generator); + /* null */ + case JSON_TYPE_NULL: + return json_generate_null(generator); + /* JSON-text */ + case JSON_TYPE_TEXT: + switch (value->content_type) { + case JSON_CONTENT_TYPE_STRING: + return json_generate_text(generator, + value->content.str); + case JSON_CONTENT_TYPE_DATA: + return json_generate_text_data( + generator, value->content.data->data, + value->content.data->size); + default: + break; + } + break; + /* ?? */ + default: + break; + } + i_unreached(); +} + +/* + * Simple string output + */ + +static void json_append_escaped_char(string_t *dest, unsigned char src) +{ + switch (src) { + case '\b': + str_append(dest, "\\b"); + break; + case '\f': + str_append(dest, "\\f"); + break; + case '\n': + str_append(dest, "\\n"); + break; + case '\r': + str_append(dest, "\\r"); + break; + case '\t': + str_append(dest, "\\t"); + break; + case '"': + str_append(dest, "\\\""); + break; + case '\\': + str_append(dest, "\\\\"); + break; + default: + if (src < 0x20 || src >= 0x80) + str_printfa(dest, "\\u%04x", src); + else + str_append_c(dest, src); + break; + } +} + +static void json_append_escaped_ucs4(string_t *dest, unichar_t chr) +{ + if (chr < 0x80) + json_append_escaped_char(dest, (unsigned char)chr); + else if (chr == 0x2028 || chr == 0x2029) + str_printfa(dest, "\\u%04x", chr); + else + uni_ucs4_to_utf8_c(chr, dest); +} + + +void json_append_escaped(string_t *dest, const char *src) +{ + json_append_escaped_data(dest, (const unsigned char*)src, strlen(src)); +} + +void json_append_escaped_data(string_t *dest, const unsigned char *src, + size_t size) +{ + size_t i; + int bytes = 0; + unichar_t chr; + + for (i = 0; i < size;) { + bytes = uni_utf8_get_char_n(src+i, size-i, &chr); + if (bytes > 0 && uni_is_valid_ucs4(chr)) { + json_append_escaped_ucs4(dest, chr); + i += bytes; + } else { + str_append_data(dest, UNICODE_REPLACEMENT_CHAR_UTF8, + UTF8_REPLACEMENT_CHAR_LEN); + i++; + } + } +} diff --git a/src/lib-json/json-generator.h b/src/lib-json/json-generator.h new file mode 100644 index 0000000000..1670f99f50 --- /dev/null +++ b/src/lib-json/json-generator.h @@ -0,0 +1,97 @@ +#ifndef JSON_GENERATOR_H +#define JSON_GENERATOR_H + +#include "json-types.h" + +#define json_append_escaped json_append_escaped_new +#define json_append_escaped_data json_append_escaped_data_new + +// FIXME: add settings for formatting/indenting the output + +struct json_generator; + +enum json_generator_flags { + /* Hide the root array or object node. So, the top-level '[' and ']' or + '{' and '}' will not be written to the output. Generating a nomal + value as root with this flag set will trigger an assertion failure. + */ + JSON_GENERATOR_FLAG_HIDE_ROOT = BIT(0), +}; + +struct json_generator * +json_generator_init(struct ostream *output, enum json_generator_flags flags); +struct json_generator * +json_generator_init_str(string_t *buf, enum json_generator_flags flags); + +void json_generator_deinit(struct json_generator **_generator); + +int json_generator_flush(struct json_generator *generator); + +/* number */ + +int json_generate_number(struct json_generator *generator, + intmax_t number); +int json_generate_number_raw(struct json_generator *generator, + const char *number); + +/* string */ + +void json_generate_string_open(struct json_generator *generator); +ssize_t json_generate_string_more(struct json_generator *generator, + const void *data, size_t size, bool last); +void json_generate_string_close(struct json_generator *generator); +int json_generate_string_write_close(struct json_generator *generator); + +int json_generate_string_data(struct json_generator *generator, + const void *data, size_t size); +int json_generate_string(struct json_generator *generator, const char *str); + +/* null */ + +int json_generate_null(struct json_generator *generator); + +/* false */ + +int json_generate_false(struct json_generator *generator); + +/* true */ + +int json_generate_true(struct json_generator *generator); + +/* object */ + +void json_generate_object_open(struct json_generator *generator); +int json_generate_object_member(struct json_generator *generator, + const char *name); +int json_generate_object_close(struct json_generator *generator); + +/* array */ + +void json_generate_array_open(struct json_generator *generator); +int json_generate_array_close(struct json_generator *generator); + +/* JSON-text */ + +void json_generate_text_open(struct json_generator *generator); +ssize_t json_generate_text_more(struct json_generator *generator, + const void *data, size_t size); +int json_generate_text_close(struct json_generator *generator); + +int json_generate_text_data(struct json_generator *generator, + const void *data, size_t size); +int json_generate_text(struct json_generator *generator, const char *str); + +/* value */ + +int json_generate_value(struct json_generator *generator, + enum json_type type, const struct json_value *value); + +/* + * Simple string output + */ + +void json_append_escaped(string_t *dest, const char *src); +void json_append_escaped_data(string_t *dest, const unsigned char *src, + size_t size); + +#endif diff --git a/src/lib-json/test-json-generator.c b/src/lib-json/test-json-generator.c new file mode 100644 index 0000000000..c3f33fdc77 --- /dev/null +++ b/src/lib-json/test-json-generator.c @@ -0,0 +1,1436 @@ +/* Copyright (c) 2017-2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "ostream.h" +#include "unichar.h" +#include "test-common.h" + +#include "json-generator.h" + +#include + +static bool debug = FALSE; + +static void test_json_generate_buffer(void) +{ + string_t *buffer; + struct ostream *output; + struct json_generator *generator; + unsigned int state, pos; + ssize_t sret; + int ret; + + buffer = str_new(default_pool, 256); + output = o_stream_create_buffer(buffer); + o_stream_set_no_error_handling(output, TRUE); + + /* number - integer */ + test_begin("json write number - integer"); + generator = json_generator_init(output, 0); + ret = json_generate_number(generator, 23423); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("23423", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* number - raw */ + test_begin("json write number - raw"); + generator = json_generator_init(output, 0); + ret = json_generate_number_raw(generator, "23423"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("23423", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* false */ + test_begin("json write false"); + generator = json_generator_init(output, 0); + ret = json_generate_false(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("false", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* false */ + test_begin("json write null"); + generator = json_generator_init(output, 0); + ret = json_generate_null(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("null", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* true */ + test_begin("json write true"); + generator = json_generator_init(output, 0); + ret = json_generate_true(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("true", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string */ + test_begin("json write string"); + generator = json_generator_init(output, 0); + ret = json_generate_string(generator, "frop!"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"frop!\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string - TAB */ + test_begin("json write string - TAB"); + generator = json_generator_init(output, 0); + ret = json_generate_string(generator, "frop\tfriep"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"frop\\tfriep\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string - LF */ + test_begin("json write string - LF"); + generator = json_generator_init(output, 0); + ret = json_generate_string(generator, "frop\nfriep"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"frop\\nfriep\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string - LF,TAB */ + test_begin("json write string - CR,LF,TAB"); + generator = json_generator_init(output, 0); + ret = json_generate_string(generator, "frop\r\n\tfriep"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"frop\\r\\n\\tfriep\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string - quotes */ + test_begin("json write string - quotes"); + generator = json_generator_init(output, 0); + ret = json_generate_string(generator, "\"frop\""); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"\\\"frop\\\"\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string - slashes */ + test_begin("json write string - slashes"); + generator = json_generator_init(output, 0); + ret = json_generate_string(generator, "frop\\friep/frml"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"frop\\\\friep/frml\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string - slashes */ + test_begin("json write string - BS,FF"); + generator = json_generator_init(output, 0); + ret = json_generate_string(generator, "\x08\x0c"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"\\b\\f\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string - bad UTF-8 */ + test_begin("json write string - bad UTF-8"); + generator = json_generator_init(output, 0); + ret = json_generate_string(generator, "\xc3\x28"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"\xEF\xBF\xBD(\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string - bad UTF-8 code point */ + test_begin("json write string - bad UTF-8 code point"); + generator = json_generator_init(output, 0); + ret = json_generate_string(generator, "\xed\xa0\xbd"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + // FIXME: this should ideally produce just one replacement char + test_assert(strcmp("\"\xEF\xBF\xBD\xEF\xBF\xBD\xEF\xBF\xBD\"", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string - long */ + test_begin("json write string - long"); + generator = json_generator_init(output, 0); + json_generate_string_open(generator); + sret = (int)json_generate_string_more(generator, + "frop", strlen("frop"), FALSE); + test_assert(sret > 0); + sret = (int)json_generate_string_more(generator, + "frop", strlen("frop"), FALSE); + test_assert(sret > 0); + sret = (int)json_generate_string_more(generator, + "frop", strlen("frop"), TRUE); + test_assert(sret > 0); + json_generate_string_close(generator); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"fropfropfrop\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* */ + test_begin("json write "); + generator = json_generator_init(output, 0); + ret = json_generate_text(generator, "[\"frop!\"]"); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[\"frop!\"]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* - long */ + test_begin("json write - long"); + generator = json_generator_init(output, 0); + json_generate_text_open(generator); + sret = (int)json_generate_text_more(generator, + "\"frop", strlen("\"frop")); + test_assert(sret > 0); + sret = (int)json_generate_text_more(generator, + "frop", strlen("frop")); + test_assert(sret > 0); + sret = (int)json_generate_text_more(generator, + "frop\"", strlen("frop\"")); + test_assert(sret > 0); + ret = json_generate_text_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"fropfropfrop\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ ] */ + test_begin("json write array - [ ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ number ] */ + test_begin("json write array - [ number ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_number(generator, 23423); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[23423]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ string ] */ + test_begin("json write array - [ string ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_string(generator, "frop"); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[\"frop\"]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ false ] */ + test_begin("json write array - [ false ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_false(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[false]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ null ] */ + test_begin("json write array - [ null ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_null(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[null]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ true ] */ + test_begin("json write array - [ true ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_true(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[true]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ [] ] */ + test_begin("json write array - [ [] ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[[]]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ {} ] */ + test_begin("json write array - [ {} ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + json_generate_object_open(generator); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[{}]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ ] */ + test_begin("json write array - [ ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_text(generator, "{\"a\":1,\"b\":2,\"c\":3}"); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[{\"a\":1,\"b\":2,\"c\":3}]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ string, ] */ + test_begin("json write array - [ string, ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + json_generate_string(generator, "frop"); + ret = json_generate_text(generator, "{\"a\":1,\"b\":2,\"c\":3}"); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[\"frop\",{\"a\":1,\"b\":2,\"c\":3}]", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ true, true ] */ + test_begin("json write array - [ true, true ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_true(generator); + test_assert(ret > 0); + ret = json_generate_true(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[true,true]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ true, true, true ] */ + test_begin("json write array - [ true, true, true ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_true(generator); + test_assert(ret > 0); + ret = json_generate_true(generator); + test_assert(ret > 0); + ret = json_generate_true(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[true,true,true]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ "frop", "friep", "frml" ] */ + test_begin("json write array - [ \"frop\", \"friep\", \"frml\" ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_string(generator, "frop"); + test_assert(ret > 0); + ret = json_generate_string(generator, "friep"); + test_assert(ret > 0); + ret = json_generate_string(generator, "frml"); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[\"frop\",\"friep\",\"frml\"]", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ 1, 2, 3 ] */ + test_begin("json write array - [ 1, 2, 3 ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_number(generator, 1); + test_assert(ret > 0); + ret = json_generate_number(generator, 2); + test_assert(ret > 0); + ret = json_generate_number(generator, 3); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[1,2,3]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ [], [], [] ] */ + test_begin("json write array - [ [], [], [] ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[[],[],[]]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ {}, {}, {} ] */ + test_begin("json write array - [ {}, {}, {} ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + json_generate_object_open(generator); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generate_object_open(generator); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generate_object_open(generator); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[{},{},{}]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ [ [], [], [] ], [ [], [], [] ], [ [], [], [] ] ] */ + test_begin("json write array - " + "[ [ [], [], [] ], [ [], [], [] ], [ [], [], [] ] ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + json_generate_array_open(generator); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[[[],[],[]],[[],[],[]],[[],[],[]]]", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ , , ] */ + test_begin("json write array - " + "[ , , ]"); + generator = json_generator_init(output, 0); + json_generate_array_open(generator); + ret = json_generate_text(generator, "true"); + test_assert(ret > 0); + ret = json_generate_text(generator, "1234234"); + test_assert(ret > 0); + ret = json_generate_text(generator, "\"frml\""); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("[true,1234234,\"frml\"]", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* array - hidden root */ + test_begin("json write array - hidden_root"); + generator = json_generator_init(output, JSON_GENERATOR_FLAG_HIDE_ROOT); + json_generate_array_open(generator); + ret = json_generate_string(generator, "frop"); + test_assert(ret > 0); + ret = json_generate_string(generator, "friep"); + test_assert(ret > 0); + ret = json_generate_string(generator, "frml"); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"frop\",\"friep\",\"frml\"", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { } */ + test_begin("json write object - { }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{}", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": 1 } */ + test_begin("json write object - { \"frop\": 1 }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + ret = json_generate_number(generator, 1); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"frop\":1}", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": "friep" } */ + test_begin("json write object - { \"frop\": \"friep\" }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + ret = json_generate_string(generator, "friep"); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"frop\":\"friep\"}", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": false } */ + test_begin("json write object - { \"frop\": false }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + ret = json_generate_false(generator); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"frop\":false}", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": [] } */ + test_begin("json write object - { \"frop\": [] }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"frop\":[]}", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": {} } */ + test_begin("json write object - { \"frop\": {} }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + json_generate_object_open(generator); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"frop\":{}}", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": } */ + test_begin("json write object - { \"frop\": }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + ret = json_generate_text(generator, "[\"friep\",1,true]"); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"frop\":[\"friep\",1,true]}", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": {}, "friep": {} } */ + test_begin("json write object - { \"frop\": {}, \"friep\": {} }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + json_generate_object_open(generator); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + ret = json_generate_object_member(generator, "friep"); + test_assert(ret > 0); + json_generate_object_open(generator); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"frop\":{},\"friep\":{}}", str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": [], "friep": [], "frml": [] } */ + test_begin("json write object - " + "{ \"frop\": [], \"friep\": [], \"frml\": [] }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_member(generator, "friep"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_member(generator, "frml"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"frop\":[],\"friep\":[],\"frml\":[]}", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": [1], "friep": [true], "frml": ["a"] } */ + test_begin("json write object - " + "{ \"frop\": [1], \"friep\": [true], \"frml\": [\"a\"] }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_number(generator, 1); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_member(generator, "friep"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_true(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_member(generator, "frml"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_string(generator, "a"); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"frop\":[1],\"friep\":[true],\"frml\":[\"a\"]}", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */ + test_begin("json write object - " + "{ \"a\": [{\"d\": 1}], \"b\": [{\"e\": 2}], " + "\"c\": [{\"f\": 3}] }"); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "a"); + test_assert(ret > 0); + json_generate_array_open(generator); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "d"); + test_assert(ret > 0); + ret = json_generate_number(generator, 1); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_member(generator, "b"); + test_assert(ret > 0); + json_generate_array_open(generator); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "e"); + test_assert(ret > 0); + ret = json_generate_number(generator, 2); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_member(generator, "c"); + test_assert(ret > 0); + json_generate_array_open(generator); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "f"); + test_assert(ret > 0); + ret = json_generate_number(generator, 3); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("{\"a\":[{\"d\":1}]," + "\"b\":[{\"e\":2}],\"c\":[{\"f\":3}]}", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* object - hidden root */ + test_begin("json write object - hidden root"); + generator = json_generator_init(output, JSON_GENERATOR_FLAG_HIDE_ROOT); + json_generate_object_open(generator); + ret = json_generate_object_member(generator, "frop"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_number(generator, 1); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_member(generator, "friep"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_true(generator); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_member(generator, "frml"); + test_assert(ret > 0); + json_generate_array_open(generator); + ret = json_generate_string(generator, "a"); + test_assert(ret > 0); + ret = json_generate_array_close(generator); + test_assert(ret > 0); + ret = json_generate_object_close(generator); + test_assert(ret > 0); + json_generator_flush(generator); + test_assert(ret > 0); + json_generator_deinit(&generator); + test_assert(strcmp("\"frop\":[1],\"friep\":[true],\"frml\":[\"a\"]", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle [1] */ + test_begin("json write object - trickle[1]"); + o_stream_set_max_buffer_size(output, 0); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + state = 0; + for (pos = 0; pos < 65535 && state <= 15; pos++) { + o_stream_set_max_buffer_size(output, pos); + switch (state) { + case 0: + ret = json_generate_object_member(generator, "aaaaaa"); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_array_open(generator); + json_generate_object_open(generator); + state++; + continue; + case 1: + ret = json_generate_object_member(generator, "dddddd"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 2: + ret = json_generate_number(generator, 1); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 3: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 4: + ret = json_generate_array_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 5: + ret = json_generate_object_member(generator, "bbbbbb"); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_array_open(generator); + json_generate_object_open(generator); + state++; + continue; + case 6: + ret = json_generate_object_member(generator, "eeeeee"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 7: + ret = json_generate_number(generator, 2); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 8: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 9: + ret = json_generate_array_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 10: + ret = json_generate_object_member(generator, "cccccc"); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_array_open(generator); + json_generate_object_open(generator); + state++; + continue; + case 11: + ret = json_generate_object_member(generator, "ffffff"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 12: + ret = json_generate_number(generator, 3); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 13: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 14: + ret = json_generate_array_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 15: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + } + } + json_generator_deinit(&generator); + test_assert(state == 16); + test_assert(strcmp("{\"aaaaaa\":[{\"dddddd\":1}]," + "\"bbbbbb\":[{\"eeeeee\":2}]," + "\"cccccc\":[{\"ffffff\":3}]}", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle [2] */ + test_begin("json write object - trickle[2]"); + o_stream_set_max_buffer_size(output, 0); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + state = 0; + for (pos = 0; pos < 65535 && state <= 24; pos++) { + o_stream_set_max_buffer_size(output, pos); + switch (state) { + case 0: + ret = json_generate_object_member(generator, "aaaaaa"); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_array_open(generator); + json_generate_object_open(generator); + state++; + continue; + case 1: + ret = json_generate_object_member(generator, "dddddd"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 2: + ret = json_generate_number(generator, 1); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 3: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_object_open(generator); + state++; + continue; + case 4: + ret = json_generate_object_member(generator, "gggggg"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 5: + ret = json_generate_number(generator, 4); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 6: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 7: + ret = json_generate_array_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 8: + ret = json_generate_object_member(generator, "bbbbbb"); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_array_open(generator); + json_generate_object_open(generator); + state++; + continue; + case 9: + ret = json_generate_object_member(generator, "eeeeee"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 10: + ret = json_generate_number(generator, 2); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 11: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_object_open(generator); + state++; + continue; + case 12: + ret = json_generate_object_member(generator, "hhhhhh"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 13: + ret = json_generate_number(generator, 5); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 14: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 15: + ret = json_generate_array_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 16: + ret = json_generate_object_member(generator, "cccccc"); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_array_open(generator); + json_generate_object_open(generator); + state++; + continue; + case 17: + ret = json_generate_object_member(generator, "ffffff"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 18: + ret = json_generate_number(generator, 3); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 19: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_object_open(generator); + state++; + continue; + case 20: + ret = json_generate_object_member(generator, "iiiiii"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 21: + ret = json_generate_number(generator, 6); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 22: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 23: + ret = json_generate_array_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 24: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + } + } + json_generator_deinit(&generator); + test_assert(state == 25); + test_assert(strcmp("{\"aaaaaa\":[{\"dddddd\":1},{\"gggggg\":4}]," + "\"bbbbbb\":[{\"eeeeee\":2},{\"hhhhhh\":5}]," + "\"cccccc\":[{\"ffffff\":3},{\"iiiiii\":6}]}", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle[3] */ + test_begin("json write object - trickle[3]"); + o_stream_set_max_buffer_size(output, 0); + generator = json_generator_init(output, 0); + json_generate_object_open(generator); + state = 0; + for (pos = 0; pos < 65535 && state <= 15; pos++) { + o_stream_set_max_buffer_size(output, pos); + switch (state) { + case 0: + ret = json_generate_object_member(generator, "aaaaaa"); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_array_open(generator); + json_generate_object_open(generator); + state++; + continue; + case 1: + ret = json_generate_object_member(generator, "dddddd"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 2: + ret = json_generate_text(generator, "1234567"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 3: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 4: + ret = json_generate_array_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 5: + ret = json_generate_object_member(generator, "bbbbbb"); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_array_open(generator); + json_generate_object_open(generator); + state++; + continue; + case 6: + ret = json_generate_object_member(generator, "eeeeee"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 7: + ret = json_generate_text(generator, "[1,2,3,4,5,6,7]"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 8: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 9: + ret = json_generate_array_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 10: + ret = json_generate_object_member(generator, "cccccc"); + test_assert(ret >= 0); + if (ret == 0) break; + json_generate_array_open(generator); + json_generate_object_open(generator); + state++; + continue; + case 11: + ret = json_generate_object_member(generator, "ffffff"); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 12: + ret = json_generate_text(generator, "\"1234567\""); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 13: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 14: + ret = json_generate_array_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 15: + ret = json_generate_object_close(generator); + test_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + } + } + json_generator_deinit(&generator); + test_assert(state == 16); + test_assert(strcmp("{\"aaaaaa\":[{\"dddddd\":1234567}]," + "\"bbbbbb\":[{\"eeeeee\":[1,2,3,4,5,6,7]}]," + "\"cccccc\":[{\"ffffff\":\"1234567\"}]}", + str_c(buffer)) == 0); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + o_stream_destroy(&output); + str_free(&buffer); +} + +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(); +} + +int main(int argc, char *argv[]) +{ + int c; + + static void (*test_functions[])(void) = { + test_json_generate_buffer, + test_json_append_escaped, + test_json_append_escaped_data, + NULL + }; + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + + return test_run(test_functions); +} diff --git a/src/lib-json/test-json-io.c b/src/lib-json/test-json-io.c new file mode 100644 index 0000000000..31f8e6f98f --- /dev/null +++ b/src/lib-json/test-json-io.c @@ -0,0 +1,1284 @@ +/* Copyright (c) 2017-2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "randgen.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "iostream-temp.h" +#include "iostream-pump.h" +#include "test-common.h" + +#include "json-parser.new.h" +#include "json-generator.h" + +#include +#include +#include +#include +#include +#include + +static bool debug = FALSE; + +struct json_io_test { + const char *input; + const char *output; + struct json_limits limits; + enum json_parser_flags flags; +}; + +static const struct json_io_test +tests[] = { + { + .input = "123456789" + },{ + .input = "\"frop\"" + },{ + .input = "false" + },{ + .input = "null" + },{ + .input = "true" + },{ + .input = "[]" + },{ + .input = "[[]]" + },{ + .input = "[[[[[[[[[[[[]]]]]]]]]]]]" + },{ + .input = "[[],[],[]]" + },{ + .input = "[[[],[],[]],[[],[],[]],[[],[],[]]]" + },{ + .input = "{}" + },{ + .input = "[\"frop\"]" + },{ + .input = "[\"frop\",\"friep\"]" + },{ + .input = "[\"frop\",\"friep\",\"frml\"]" + },{ + .input = "[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\"]" + },{ + .input = "[true]" + },{ + .input = "[null]" + },{ + .input = "[true,false]" + },{ + .input = "[true,true,false,false]" + },{ + .input = "[1]" + },{ + .input = "[1,12]" + },{ + .input = "[1,12,123]" + },{ + .input = "[1,12,123,1234]" + },{ + .input = "[1,2,3,4,5,6,7]" + },{ + .input = "{\"frop\":1}" + },{ + .input = "{\"a\":\"1\",\"b\":\"2\",\"c\":\"3\",\"d\":\"4\",\"e\":\"5\",\"f\":\"6\",\"g\":\"7\"}" + },{ + .input = "[{\"frop\":1},{\"frop\":1},{\"frop\":1},{\"frop\":1},{\"frop\":1}]" + },{ + .input = "[[\"frop\",1],[\"frop\",1],[\"frop\",1],[\"frop\",1],[\"frop\",1]]" + },{ + .input = "[[\"frop\",[]],[\"frop\",[]],[\"frop\",[]],[\"frop\",[]],[\"frop\",[]]]" + },{ + .input = "[[\"frop\"],[1],[\"frop\"],[1],[\"frop\"],[1],[\"frop\"],[1],[\"frop\"],[1]]" + },{ + .input = "[[\"frop\"],[1,2,false],[\"frop\"],[1,2,false],[\"frop\"],[1,2,false],[\"frop\"],[1,2,false],[\"frop\"],[1,2,false]]" + },{ + .input = "[[\"frop\",{}],[\"frop\",{}],[\"frop\",{}],[\"frop\",{}],[\"frop\",{}]]" + },{ + .input = "{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":{\"f\":{\"g\":{\"h\":{}}}}}}}}}" + },{ + .input = + "{\n" + " \"glossary\": {\n" + " \"title\": \"example glossary\",\n" + " \"GlossDiv\": {\n" + " \"title\": \"S\",\n" + " \"GlossList\": {\n" + " \"GlossEntry\": {\n" + " \"ID\": \"SGML\",\n" + " \"SortAs\": \"SGML\",\n" + " \"GlossTerm\": \"Standard Generalized Markup Language\",\n" + " \"Acronym\": \"SGML\",\n" + " \"Abbrev\": \"ISO 8879:1986\",\n" + " \"GlossDef\": {\n" + " \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\",\n" + " \"GlossSeeAlso\": [\"GML\", \"XML\"]\n" + " },\n" + " \"GlossSee\": \"markup\"\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}\n", + .output = + "{\"glossary\":{\"title\":\"example glossary\",\"GlossDiv\":{" + "\"title\":\"S\",\"GlossList\":{\"GlossEntry\":{\"ID\":\"SGML\"," + "\"SortAs\":\"SGML\",\"GlossTerm\":\"Standard Generalized Markup Language\"," + "\"Acronym\":\"SGML\",\"Abbrev\":\"ISO 8879:1986\",\"GlossDef\":{" + "\"para\":\"A meta-markup language, used to create markup languages such as DocBook.\"," + "\"GlossSeeAlso\":[\"GML\",\"XML\"]},\"GlossSee\":\"markup\"}}}}}" + },{ + .input = + "{\"menu\": {\n" + " \"id\": \"file\",\n" + " \"value\": \"File\",\n" + " \"popup\": {\n" + " \"menuitem\": [\n" + " {\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\n" + " {\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\n" + " {\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\n" + " ]\n" + " }\n" + "}}\n", + .output = + "{\"menu\":{\"id\":\"file\",\"value\":\"File\"," + "\"popup\":{\"menuitem\":[{\"value\":\"New\",\"onclick\":" + "\"CreateNewDoc()\"},{\"value\":\"Open\",\"onclick\":\"OpenDoc()\"}," + "{\"value\":\"Close\",\"onclick\":\"CloseDoc()\"}]}}}" + },{ + .input = + "{\"widget\": {\n" + " \"debug\": \"on\",\n" + " \"window\": {\n" + " \"title\": \"Sample Konfabulator Widget\",\n" + " \"name\": \"main_window\",\n" + " \"width\": 500,\n" + " \"height\": 500\n" + " },\n" + " \"image\": { \n" + " \"src\": \"Images/Sun.png\",\n" + " \"name\": \"sun1\",\n" + " \"hOffset\": 250,\n" + " \"vOffset\": 250,\n" + " \"alignment\": \"center\"\n" + " },\n" + " \"text\": {\n" + " \"data\": \"Click Here\",\n" + " \"size\": 36,\n" + " \"style\": \"bold\",\n" + " \"name\": \"text1\",\n" + " \"hOffset\": 250,\n" + " \"vOffset\": 100,\n" + " \"alignment\": \"center\",\n" + " \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n" + " }\n" + "}}\n", + .output = + "{\"widget\":{\"debug\":\"on\",\"window\":{" + "\"title\":\"Sample Konfabulator Widget\"," + "\"name\":\"main_window\",\"width\":500," + "\"height\":500},\"image\":{\"src\":\"Images/Sun.png\"," + "\"name\":\"sun1\",\"hOffset\":250,\"vOffset\":250," + "\"alignment\":\"center\"},\"text\":{\"data\":\"Click Here\"," + "\"size\":36,\"style\":\"bold\",\"name\":\"text1\"," + "\"hOffset\":250,\"vOffset\":100,\"alignment\":\"center\"," + "\"onMouseUp\":\"sun1.opacity = (sun1.opacity / 100) * 90;\"}}}" + },{ + .input = + "{\"web-app\": {\r\n" + " \"servlet\": [ \r\n" + " {\r\n" + " \"servlet-name\": \"cofaxCDS\",\r\n" + " \"servlet-class\": \"org.cofax.cds.CDSServlet\",\r\n" + " \"init-param\": {\r\n" + " \"configGlossary:installationAt\": \"Philadelphia, PA\",\r\n" + " \"configGlossary:adminEmail\": \"ksm@pobox.com\",\r\n" + " \"configGlossary:poweredBy\": \"Cofax\",\r\n" + " \"configGlossary:poweredByIcon\": \"/images/cofax.gif\",\r\n" + " \"configGlossary:staticPath\": \"/content/static\",\r\n" + " \"templateProcessorClass\": \"org.cofax.WysiwygTemplate\",\r\n" + " \"templateLoaderClass\": \"org.cofax.FilesTemplateLoader\",\r\n" + " \"templatePath\": \"templates\",\r\n" + " \"templateOverridePath\": \"\",\r\n" + " \"defaultListTemplate\": \"listTemplate.htm\",\r\n" + " \"defaultFileTemplate\": \"articleTemplate.htm\",\r\n" + " \"useJSP\": false,\r\n" + " \"jspListTemplate\": \"listTemplate.jsp\",\r\n" + " \"jspFileTemplate\": \"articleTemplate.jsp\",\r\n" + " \"cachePackageTagsTrack\": 200,\r\n" + " \"cachePackageTagsStore\": 200,\r\n" + " \"cachePackageTagsRefresh\": 60,\r\n" + " \"cacheTemplatesTrack\": 100,\r\n" + " \"cacheTemplatesStore\": 50,\r\n" + " \"cacheTemplatesRefresh\": 15,\r\n" + " \"cachePagesTrack\": 200,\r\n" + " \"cachePagesStore\": 100,\r\n" + " \"cachePagesRefresh\": 10,\r\n" + " \"cachePagesDirtyRead\": 10,\r\n" + " \"searchEngineListTemplate\": \"forSearchEnginesList.htm\",\r\n" + " \"searchEngineFileTemplate\": \"forSearchEngines.htm\",\r\n" + " \"searchEngineRobotsDb\": \"WEB-INF/robots.db\",\r\n" + " \"useDataStore\": true,\r\n" + " \"dataStoreClass\": \"org.cofax.SqlDataStore\",\r\n" + " \"redirectionClass\": \"org.cofax.SqlRedirection\",\r\n" + " \"dataStoreName\": \"cofax\",\r\n" + " \"dataStoreDriver\": \"com.microsoft.jdbc.sqlserver.SQLServerDriver\",\r\n" + " \"dataStoreUrl\": \"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon\",\r\n" + " \"dataStoreUser\": \"sa\",\r\n" + " \"dataStorePassword\": \"dataStoreTestQuery\",\r\n" + " \"dataStoreTestQuery\": \"SET NOCOUNT ON;select test='test';\",\r\n" + " \"dataStoreLogFile\": \"/usr/local/tomcat/logs/datastore.log\",\r\n" + " \"dataStoreInitConns\": 10,\r\n" + " \"dataStoreMaxConns\": 100,\r\n" + " \"dataStoreConnUsageLimit\": 100,\r\n" + " \"dataStoreLogLevel\": \"debug\",\r\n" + " \"maxUrlLength\": 500}},\r\n" + " {\r\n" + " \"servlet-name\": \"cofaxEmail\",\r\n" + " \"servlet-class\": \"org.cofax.cds.EmailServlet\",\r\n" + " \"init-param\": {\r\n" + " \"mailHost\": \"mail1\",\r\n" + " \"mailHostOverride\": \"mail2\"}},\r\n" + " {\r\n" + " \"servlet-name\": \"cofaxAdmin\",\r\n" + " \"servlet-class\": \"org.cofax.cds.AdminServlet\"},\r\n" + " \r\n" + " {\r\n" + " \"servlet-name\": \"fileServlet\",\r\n" + " \"servlet-class\": \"org.cofax.cds.FileServlet\"},\r\n" + " {\r\n" + " \"servlet-name\": \"cofaxTools\",\r\n" + " \"servlet-class\": \"org.cofax.cms.CofaxToolsServlet\",\r\n" + " \"init-param\": {\r\n" + " \"templatePath\": \"toolstemplates/\",\r\n" + " \"log\": 1,\r\n" + " \"logLocation\": \"/usr/local/tomcat/logs/CofaxTools.log\",\r\n" + " \"logMaxSize\": \"\",\r\n" + " \"dataLog\": 1,\r\n" + " \"dataLogLocation\": \"/usr/local/tomcat/logs/dataLog.log\",\r\n" + " \"dataLogMaxSize\": \"\",\r\n" + " \"removePageCache\": \"/content/admin/remove?cache=pages&id=\",\r\n" + " \"removeTemplateCache\": \"/content/admin/remove?cache=templates&id=\",\r\n" + " \"fileTransferFolder\": \"/usr/local/tomcat/webapps/content/fileTransferFolder\",\r\n" + " \"lookInContext\": 1,\r\n" + " \"adminGroupID\": 4,\r\n" + " \"betaServer\": true}}],\r\n" + " \"servlet-mapping\": {\r\n" + " \"cofaxCDS\": \"/\",\r\n" + " \"cofaxEmail\": \"/cofaxutil/aemail/*\",\r\n" + " \"cofaxAdmin\": \"/admin/*\",\r\n" + " \"fileServlet\": \"/static/*\",\r\n" + " \"cofaxTools\": \"/tools/*\"},\r\n" + " \r\n" + " \"taglib\": {\r\n" + " \"taglib-uri\": \"cofax.tld\",\r\n" + " \"taglib-location\": \"/WEB-INF/tlds/cofax.tld\"}}}", + .output = + "{\"web-app\":{\"servlet\":[{\"servlet-name\":\"cofaxCDS\"," + "\"servlet-class\":\"org.cofax.cds.CDSServlet\"," + "\"init-param\":{\"configGlossary:installationAt\":\"Philadelphia, PA\"," + "\"configGlossary:adminEmail\":\"ksm@pobox.com\"," + "\"configGlossary:poweredBy\":\"Cofax\"," + "\"configGlossary:poweredByIcon\":\"/images/cofax.gif\"," + "\"configGlossary:staticPath\":\"/content/static\"," + "\"templateProcessorClass\":\"org.cofax.WysiwygTemplate\"," + "\"templateLoaderClass\":\"org.cofax.FilesTemplateLoader\"," + "\"templatePath\":\"templates\"," + "\"templateOverridePath\":\"\"," + "\"defaultListTemplate\":\"listTemplate.htm\"," + "\"defaultFileTemplate\":\"articleTemplate.htm\"," + "\"useJSP\":false,\"jspListTemplate\":\"listTemplate.jsp\"," + "\"jspFileTemplate\":\"articleTemplate.jsp\"," + "\"cachePackageTagsTrack\":200,\"cachePackageTagsStore\":200," + "\"cachePackageTagsRefresh\":60,\"cacheTemplatesTrack\":100," + "\"cacheTemplatesStore\":50,\"cacheTemplatesRefresh\":15," + "\"cachePagesTrack\":200,\"cachePagesStore\":100," + "\"cachePagesRefresh\":10,\"cachePagesDirtyRead\":10," + "\"searchEngineListTemplate\":\"forSearchEnginesList.htm\"," + "\"searchEngineFileTemplate\":\"forSearchEngines.htm\"," + "\"searchEngineRobotsDb\":\"WEB-INF/robots.db\"," + "\"useDataStore\":true,\"dataStoreClass\":\"org.cofax.SqlDataStore\"," + "\"redirectionClass\":\"org.cofax.SqlRedirection\"," + "\"dataStoreName\":\"cofax\"," + "\"dataStoreDriver\":\"com.microsoft.jdbc.sqlserver.SQLServerDriver\"," + "\"dataStoreUrl\":\"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon\"," + "\"dataStoreUser\":\"sa\",\"dataStorePassword\":\"dataStoreTestQuery\"," + "\"dataStoreTestQuery\":\"SET NOCOUNT ON;select test='test';\"," + "\"dataStoreLogFile\":\"/usr/local/tomcat/logs/datastore.log\"," + "\"dataStoreInitConns\":10,\"dataStoreMaxConns\":100," + "\"dataStoreConnUsageLimit\":100,\"dataStoreLogLevel\":\"debug\"," + "\"maxUrlLength\":500}},{\"servlet-name\":\"cofaxEmail\"," + "\"servlet-class\":\"org.cofax.cds.EmailServlet\"," + "\"init-param\":{\"mailHost\":\"mail1\",\"mailHostOverride\":\"mail2\"}}," + "{\"servlet-name\":\"cofaxAdmin\"," + "\"servlet-class\":\"org.cofax.cds.AdminServlet\"},{" + "\"servlet-name\":\"fileServlet\"," + "\"servlet-class\":\"org.cofax.cds.FileServlet\"},{" + "\"servlet-name\":\"cofaxTools\"," + "\"servlet-class\":\"org.cofax.cms.CofaxToolsServlet\"," + "\"init-param\":{\"templatePath\":\"toolstemplates/\"," + "\"log\":1,\"logLocation\":\"/usr/local/tomcat/logs/CofaxTools.log\"," + "\"logMaxSize\":\"\",\"dataLog\":1," + "\"dataLogLocation\":\"/usr/local/tomcat/logs/dataLog.log\"," + "\"dataLogMaxSize\":\"\"," + "\"removePageCache\":\"/content/admin/remove?cache=pages&id=\"," + "\"removeTemplateCache\":\"/content/admin/remove?cache=templates&id=\"," + "\"fileTransferFolder\":\"/usr/local/tomcat/webapps/content/fileTransferFolder\"," + "\"lookInContext\":1,\"adminGroupID\":4,\"betaServer\":true}}]," + "\"servlet-mapping\":{\"cofaxCDS\":\"/\"," + "\"cofaxEmail\":\"/cofaxutil/aemail/*\"," + "\"cofaxAdmin\":\"/admin/*\",\"fileServlet\":\"/static/*\"," + "\"cofaxTools\":\"/tools/*\"},\"taglib\":{" + "\"taglib-uri\":\"cofax.tld\"," + "\"taglib-location\":\"/WEB-INF/tlds/cofax.tld\"}}}" + },{ + .input = + "{\"menu\": {\r\n" + " \"header\": \"SVG Viewer\",\r\n" + " \"items\": [\r\n" + " {\"id\": \"Open\"},\r\n" + " {\"id\": \"OpenNew\", \"label\": \"Open New\"},\r\n" + " null,\r\n" + " {\"id\": \"ZoomIn\", \"label\": \"Zoom In\"},\r\n" + " {\"id\": \"ZoomOut\", \"label\": \"Zoom Out\"},\r\n" + " {\"id\": \"OriginalView\", \"label\": \"Original View\"},\r\n" + " null,\r\n" + " {\"id\": \"Quality\"},\r\n" + " {\"id\": \"Pause\"},\r\n" + " {\"id\": \"Mute\"},\r\n" + " null,\r\n" + " {\"id\": \"Find\", \"label\": \"Find...\"},\r\n" + " {\"id\": \"FindAgain\", \"label\": \"Find Again\"},\r\n" + " {\"id\": \"Copy\"},\r\n" + " {\"id\": \"CopyAgain\", \"label\": \"Copy Again\"},\r\n" + " {\"id\": \"CopySVG\", \"label\": \"Copy SVG\"},\r\n" + " {\"id\": \"ViewSVG\", \"label\": \"View SVG\"},\r\n" + " {\"id\": \"ViewSource\", \"label\": \"View Source\"},\r\n" + " {\"id\": \"SaveAs\", \"label\": \"Save As\"},\r\n" + " null,\r\n" + " {\"id\": \"Help\"},\r\n" + " {\"id\": \"About\", \"label\": \"About Adobe CVG Viewer...\"}\r\n" + " ]\r\n" + "}}", + .output = + "{\"menu\":{\"header\":\"SVG Viewer\",\"items\":[" + "{\"id\":\"Open\"},{\"id\":\"OpenNew\",\"label\":\"Open New\"}," + "null,{\"id\":\"ZoomIn\",\"label\":\"Zoom In\"}," + "{\"id\":\"ZoomOut\",\"label\":\"Zoom Out\"}," + "{\"id\":\"OriginalView\",\"label\":\"Original View\"}," + "null,{\"id\":\"Quality\"},{\"id\":\"Pause\"}," + "{\"id\":\"Mute\"},null,{\"id\":\"Find\",\"label\":\"Find...\"}," + "{\"id\":\"FindAgain\",\"label\":\"Find Again\"}," + "{\"id\":\"Copy\"},{\"id\":\"CopyAgain\",\"label\":\"Copy Again\"}," + "{\"id\":\"CopySVG\",\"label\":\"Copy SVG\"}," + "{\"id\":\"ViewSVG\",\"label\":\"View SVG\"}," + "{\"id\":\"ViewSource\",\"label\":\"View Source\"}," + "{\"id\":\"SaveAs\",\"label\":\"Save As\"}," + "null,{\"id\":\"Help\"}," + "{\"id\":\"About\",\"label\":\"About Adobe CVG Viewer...\"}]}}" + },{ + .input = + "{\r\n" + " \"$schema\": \"http://json-schema.org/draft-06/schema#\",\r\n" + " \"$id\": \"http://json-schema.org/draft-06/schema#\",\r\n" + " \"title\": \"Core schema meta-schema\",\r\n" + " \"definitions\": {\r\n" + " \"schemaArray\": {\r\n" + " \"type\": \"array\",\r\n" + " \"minItems\": 1,\r\n" + " \"items\": { \"$ref\": \"#\" }\r\n" + " },\r\n" + " \"nonNegativeInteger\": {\r\n" + " \"type\": \"integer\",\r\n" + " \"minimum\": 0\r\n" + " },\r\n" + " \"nonNegativeIntegerDefault0\": {\r\n" + " \"allOf\": [\r\n" + " { \"$ref\": \"#/definitions/nonNegativeInteger\" },\r\n" + " { \"default\": 0 }\r\n" + " ]\r\n" + " },\r\n" + " \"simpleTypes\": {\r\n" + " \"enum\": [\r\n" + " \"array\",\r\n" + " \"boolean\",\r\n" + " \"integer\",\r\n" + " \"null\",\r\n" + " \"number\",\r\n" + " \"object\",\r\n" + " \"string\"\r\n" + " ]\r\n" + " },\r\n" + " \"stringArray\": {\r\n" + " \"type\": \"array\",\r\n" + " \"items\": { \"type\": \"string\" },\r\n" + " \"uniqueItems\": true,\r\n" + " \"default\": []\r\n" + " }\r\n" + " },\r\n" + " \"type\": [\"object\", \"boolean\"],\r\n" + " \"properties\": {\r\n" + " \"$id\": {\r\n" + " \"type\": \"string\",\r\n" + " \"format\": \"uri-reference\"\r\n" + " },\r\n" + " \"$schema\": {\r\n" + " \"type\": \"string\",\r\n" + " \"format\": \"uri\"\r\n" + " },\r\n" + " \"$ref\": {\r\n" + " \"type\": \"string\",\r\n" + " \"format\": \"uri-reference\"\r\n" + " },\r\n" + " \"title\": {\r\n" + " \"type\": \"string\"\r\n" + " },\r\n" + " \"description\": {\r\n" + " \"type\": \"string\"\r\n" + " },\r\n" + " \"default\": {},\r\n" + " \"multipleOf\": {\r\n" + " \"type\": \"number\",\r\n" + " \"exclusiveMinimum\": 0\r\n" + " },\r\n" + " \"maximum\": {\r\n" + " \"type\": \"number\"\r\n" + " },\r\n" + " \"exclusiveMaximum\": {\r\n" + " \"type\": \"number\"\r\n" + " },\r\n" + " \"minimum\": {\r\n" + " \"type\": \"number\"\r\n" + " },\r\n" + " \"exclusiveMinimum\": {\r\n" + " \"type\": \"number\"\r\n" + " },\r\n" + " \"maxLength\": { \"$ref\": \"#/definitions/nonNegativeInteger\" },\r\n" + " \"minLength\": { \"$ref\": \"#/definitions/nonNegativeIntegerDefault0\" },\r\n" + " \"pattern\": {\r\n" + " \"type\": \"string\",\r\n" + " \"format\": \"regex\"\r\n" + " },\r\n" + " \"additionalItems\": { \"$ref\": \"#\" },\r\n" + " \"items\": {\r\n" + " \"anyOf\": [\r\n" + " { \"$ref\": \"#\" },\r\n" + " { \"$ref\": \"#/definitions/schemaArray\" }\r\n" + " ],\r\n" + " \"default\": {}\r\n" + " },\r\n" + " \"maxItems\": { \"$ref\": \"#/definitions/nonNegativeInteger\" },\r\n" + " \"minItems\": { \"$ref\": \"#/definitions/nonNegativeIntegerDefault0\" },\r\n" + " \"uniqueItems\": {\r\n" + " \"type\": \"boolean\",\r\n" + " \"default\": false\r\n" + " },\r\n" + " \"contains\": { \"$ref\": \"#\" },\r\n" + " \"maxProperties\": { \"$ref\": \"#/definitions/nonNegativeInteger\" },\r\n" + " \"minProperties\": { \"$ref\": \"#/definitions/nonNegativeIntegerDefault0\" },\r\n" + " \"required\": { \"$ref\": \"#/definitions/stringArray\" },\r\n" + " \"additionalProperties\": { \"$ref\": \"#\" },\r\n" + " \"definitions\": {\r\n" + " \"type\": \"object\",\r\n" + " \"additionalProperties\": { \"$ref\": \"#\" },\r\n" + " \"default\": {}\r\n" + " },\r\n" + " \"properties\": {\r\n" + " \"type\": \"object\",\r\n" + " \"additionalProperties\": { \"$ref\": \"#\" },\r\n" + " \"default\": {}\r\n" + " },\r\n" + " \"patternProperties\": {\r\n" + " \"type\": \"object\",\r\n" + " \"additionalProperties\": { \"$ref\": \"#\" },\r\n" + " \"default\": {}\r\n" + " },\r\n" + " \"dependencies\": {\r\n" + " \"type\": \"object\",\r\n" + " \"additionalProperties\": {\r\n" + " \"anyOf\": [\r\n" + " { \"$ref\": \"#\" },\r\n" + " { \"$ref\": \"#/definitions/stringArray\" }\r\n" + " ]\r\n" + " }\r\n" + " },\r\n" + " \"propertyNames\": { \"$ref\": \"#\" },\r\n" + " \"const\": {},\r\n" + " \"enum\": {\r\n" + " \"type\": \"array\",\r\n" + " \"minItems\": 1,\r\n" + " \"uniqueItems\": true\r\n" + " },\r\n" + " \"type\": {\r\n" + " \"anyOf\": [\r\n" + " { \"$ref\": \"#/definitions/simpleTypes\" },\r\n" + " {\r\n" + " \"type\": \"array\",\r\n" + " \"items\": { \"$ref\": \"#/definitions/simpleTypes\" },\r\n" + " \"minItems\": 1,\r\n" + " \"uniqueItems\": true\r\n" + " }\r\n" + " ]\r\n" + " },\r\n" + " \"format\": { \"type\": \"string\" },\r\n" + " \"allOf\": { \"$ref\": \"#/definitions/schemaArray\" },\r\n" + " \"anyOf\": { \"$ref\": \"#/definitions/schemaArray\" },\r\n" + " \"oneOf\": { \"$ref\": \"#/definitions/schemaArray\" },\r\n" + " \"not\": { \"$ref\": \"#\" }\r\n" + " },\r\n" + " \"default\": {}\r\n" + "}\r\n", + .output = + "{\"$schema\":\"http://json-schema.org/draft-06/schema#\"," + "\"$id\":\"http://json-schema.org/draft-06/schema#\"," + "\"title\":\"Core schema meta-schema\",\"definitions\":{" + "\"schemaArray\":{\"type\":\"array\",\"minItems\":1," + "\"items\":{\"$ref\":\"#\"}},\"nonNegativeInteger\":{" + "\"type\":\"integer\",\"minimum\":0}," + "\"nonNegativeIntegerDefault0\":{\"allOf\":[" + "{\"$ref\":\"#/definitions/nonNegativeInteger\"}," + "{\"default\":0}]},\"simpleTypes\":{\"enum\":[" + "\"array\",\"boolean\",\"integer\",\"null\"," + "\"number\",\"object\",\"string\"]},\"stringArray\":{" + "\"type\":\"array\",\"items\":{\"type\":\"string\"}," + "\"uniqueItems\":true,\"default\":[]}}," + "\"type\":[\"object\",\"boolean\"]," + "\"properties\":{\"$id\":{\"type\":\"string\"," + "\"format\":\"uri-reference\"},\"$schema\":{" + "\"type\":\"string\",\"format\":\"uri\"}," + "\"$ref\":{\"type\":\"string\",\"format\":\"uri-reference\"" + "},\"title\":{\"type\":\"string\"},\"description\":{" + "\"type\":\"string\"},\"default\":{},\"multipleOf\":{" + "\"type\":\"number\",\"exclusiveMinimum\":0}," + "\"maximum\":{\"type\":\"number\"},\"exclusiveMaximum\":{" + "\"type\":\"number\"},\"minimum\":{\"type\":\"number\"" + "},\"exclusiveMinimum\":{\"type\":\"number\"}," + "\"maxLength\":{\"$ref\":\"#/definitions/nonNegativeInteger\"}," + "\"minLength\":{\"$ref\":\"#/definitions/nonNegativeIntegerDefault0\"}," + "\"pattern\":{\"type\":\"string\",\"format\":\"regex\"" + "},\"additionalItems\":{\"$ref\":\"#\"},\"items\":{" + "\"anyOf\":[{\"$ref\":\"#\"},{\"$ref\":\"#/definitions/schemaArray\"}" + "],\"default\":{}}," + "\"maxItems\":{\"$ref\":\"#/definitions/nonNegativeInteger\"}," + "\"minItems\":{\"$ref\":\"#/definitions/nonNegativeIntegerDefault0\"}," + "\"uniqueItems\":{\"type\":\"boolean\",\"default\":false}," + "\"contains\":{\"$ref\":\"#\"}," + "\"maxProperties\":{\"$ref\":\"#/definitions/nonNegativeInteger\"}," + "\"minProperties\":{\"$ref\":\"#/definitions/nonNegativeIntegerDefault0\"}," + "\"required\":{\"$ref\":\"#/definitions/stringArray\"}," + "\"additionalProperties\":{\"$ref\":\"#\"},\"definitions\":{" + "\"type\":\"object\",\"additionalProperties\":{\"$ref\":\"#\"}," + "\"default\":{}},\"properties\":{\"type\":\"object\"," + "\"additionalProperties\":{\"$ref\":\"#\"},\"default\":{}" + "},\"patternProperties\":{\"type\":\"object\"," + "\"additionalProperties\":{\"$ref\":\"#\"}," + "\"default\":{}},\"dependencies\":{\"type\":\"object\"," + "\"additionalProperties\":{\"anyOf\":[{\"$ref\":\"#\"}," + "{\"$ref\":\"#/definitions/stringArray\"}" + "]}},\"propertyNames\":{\"$ref\":\"#\"},\"const\":{}," + "\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true" + "},\"type\":{\"anyOf\":[{\"$ref\":\"#/definitions/simpleTypes\"}," + "{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/simpleTypes\"}," + "\"minItems\":1,\"uniqueItems\":true}]},\"format\":{\"type\":\"string\"}," + "\"allOf\":{\"$ref\":\"#/definitions/schemaArray\"}," + "\"anyOf\":{\"$ref\":\"#/definitions/schemaArray\"}," + "\"oneOf\":{\"$ref\":\"#/definitions/schemaArray\"}," + "\"not\":{\"$ref\":\"#\"}},\"default\":{}}" + }, + /* escape sequences */ + { + .input = "\"\\u0020\"", + .output = "\" \"", + },{ + .input = "\"\\u0020\\u0020\"", + .output = "\" \"", + },{ + .input = "\"\\\"\"", + .output = "\"\\\"\"", + },{ + .input = "\"\\\\\"", + .output = "\"\\\\\"", + },{ + .input = "\"\\/\"", + .output = "\"/\"", + },{ + .input = "\"\\b\"", + .output = "\"\\b\"", + },{ + .input = "\"\\f\"", + .output = "\"\\f\"", + },{ + .input = "\"\\n\"", + .output = "\"\\n\"", + },{ + .input = "\"\\r\"", + .output = "\"\\r\"", + },{ + .input = "\"\\t\"", + .output = "\"\\t\"", + },{ + .input = "\"\\u0020\\\"\\\\\\/\\b\\f\\n\\r\\t\"", + .output = "\" \\\"\\\\/\\b\\f\\n\\r\\t\"", + },{ + .input = "\"\\u00a2\"", + .output = "\"\xC2\xA2\"" + },{ + .input = "\"\\u20AC\"", + .output = "\"\xE2\x82\xAC\"" + },{ + .input = "\"\\uD808\\uDC00\"", + .output = "\"\xF0\x92\x80\x80\"" + },{ + .input = "\"\\u00a2\\u20AC\\uD808\\uDC00\"", + .output = "\"\xC2\xA2\xE2\x82\xAC\xF0\x92\x80\x80\"" + },{ + .input = "\"\\uD81A\\uDCD8\"", + .output = "\"\xF0\x96\xA3\x98\"" + },{ + .input = "\"\\uD836\\uDD49\"", + .output = "\"\xF0\x9D\xA5\x89\"", + },{ + .input = "\"\xF0\x92\x80\x80\"", + .output = "\"\xF0\x92\x80\x80\"" + },{ + .input = "\"\xF0\x96\xA3\x98\"", + .output = "\"\xF0\x96\xA3\x98\"" + },{ + .input = "\"\xF0\x9D\xA5\x89\"", + .output = "\"\xF0\x9D\xA5\x89\"", + },{ + .input = "\"\\\\xFF\\\\xFF\\\\xFF\"", + .output = "\"\\\\xFF\\\\xFF\\\\xFF\"", + },{ + .input = "\"\xe2\x80\xa8\xe2\x80\xa9\"", + .output = "\"\\u2028\\u2029\"", + } +}; + +static const unsigned tests_count = N_ELEMENTS(tests); + +/* + * Low-level I/O + */ + +struct test_io_context; + +enum test_io_state { + TEST_STATE_NONE = 0, + TEST_STATE_OBJECT_OPEN, + TEST_STATE_ARRAY_OPEN, + TEST_STATE_OBJECT_MEMBER, + TEST_STATE_VALUE, + TEST_STATE_OBJECT_CLOSE, + TEST_STATE_ARRAY_CLOSE, +}; + +struct test_io_processor { + struct test_io_context *tctx; + const char *name; + + string_t *membuf, *strbuf; + struct ostream *output; + struct istream *input; + struct io *io; + + enum test_io_state state; + + enum json_type type; + struct json_value value; + struct json_data data; + + struct json_generator *generator; + struct json_parser *parser; + + unsigned int pos; +}; + +struct test_io_context { + const struct json_io_test *test; + unsigned int scenario; + + struct iostream_pump *pump_in, *pump_out; +}; + +static void +test_copy_value(struct test_io_processor *tproc, enum json_type type, + const struct json_value *value) +{ + tproc->type = type; + tproc->value = *value; + switch (value->content_type) { + case JSON_CONTENT_TYPE_STRING: + str_truncate(tproc->strbuf, 0); + str_append(tproc->strbuf, value->content.str); + tproc->value.content.str = str_c(tproc->strbuf); + break; + case JSON_CONTENT_TYPE_DATA: + tproc->data = *value->content.data; + tproc->value.content.data = &tproc->data; + str_truncate(tproc->strbuf, 0); + str_append_data(tproc->strbuf, tproc->data.data, + tproc->data.size); + tproc->data.data = str_data(tproc->strbuf); + break; + default: + break; + } +} + +static void +test_parse_list_open(void *context, void *parent_context ATTR_UNUSED, + const char *name, bool object, + void **list_context_r ATTR_UNUSED) +{ + struct test_io_processor *tproc = context; + int ret; + + if (object) { + tproc->state = TEST_STATE_OBJECT_OPEN; + } else { + tproc->state = TEST_STATE_ARRAY_OPEN; + } + + if (name != NULL) { + ret = json_generate_object_member(tproc->generator, name); + if (ret <= 0) { + str_truncate(tproc->membuf, 0); + str_append(tproc->membuf, name); + json_parser_interrupt(tproc->parser); + return; + } + } + + tproc->state = TEST_STATE_NONE; + if (object) + json_generate_object_open(tproc->generator); + else + json_generate_array_open(tproc->generator); +} + +static void +test_parse_list_close(void *context, void *list_context ATTR_UNUSED, + bool object) +{ + struct test_io_processor *tproc = context; + int ret; + + if (object) { + tproc->state = TEST_STATE_OBJECT_CLOSE; + ret = json_generate_object_close(tproc->generator); + } else { + tproc->state = TEST_STATE_ARRAY_CLOSE; + ret = json_generate_array_close(tproc->generator); + } + if (ret <= 0) { + json_parser_interrupt(tproc->parser); + return; + } + + tproc->state = TEST_STATE_NONE; +} + +static void +test_parse_value(void *context, void *parent_context ATTR_UNUSED, + const char *name, enum json_type type, + const struct json_value *value) +{ + struct test_io_processor *tproc = context; + int ret; + + tproc->state = TEST_STATE_OBJECT_MEMBER; + + if (name != NULL) { + ret = json_generate_object_member(tproc->generator, name); + if (ret <= 0) { + str_truncate(tproc->membuf, 0); + str_append(tproc->membuf, name); + json_parser_interrupt(tproc->parser); + test_copy_value(tproc, type, value); + return; + } + } + + tproc->state = TEST_STATE_VALUE; + + ret = json_generate_value(tproc->generator, type, value); + if (ret <= 0) { + if (ret == 0) + test_copy_value(tproc, type, value); + json_parser_interrupt(tproc->parser); + return; + } + + tproc->state = TEST_STATE_NONE; +} + +static int test_write(struct test_io_processor *tproc) +{ + int ret; + + switch (tproc->state) { + case TEST_STATE_NONE: + break; + case TEST_STATE_OBJECT_OPEN: + ret = json_generate_object_member(tproc->generator, + str_c(tproc->membuf)); + if (ret <= 0) + return ret; + tproc->state = TEST_STATE_VALUE; + json_generate_object_open(tproc->generator); + break; + case TEST_STATE_ARRAY_OPEN: + ret = json_generate_object_member(tproc->generator, + str_c(tproc->membuf)); + if (ret <= 0) + return ret; + tproc->state = TEST_STATE_VALUE; + json_generate_array_open(tproc->generator); + break; + case TEST_STATE_OBJECT_MEMBER: + ret = json_generate_object_member(tproc->generator, + str_c(tproc->membuf)); + if (ret <= 0) + return ret; + tproc->state = TEST_STATE_VALUE; + /* fall through */ + case TEST_STATE_VALUE: + ret = json_generate_value(tproc->generator, + tproc->type, &tproc->value); + if (ret <= 0) + return ret; + break; + case TEST_STATE_OBJECT_CLOSE: + ret = json_generate_object_close(tproc->generator); + if (ret <= 0) + return ret; + break; + case TEST_STATE_ARRAY_CLOSE: + ret = json_generate_array_close(tproc->generator); + if (ret <= 0) + return ret; + break; + } + + tproc->state = TEST_STATE_NONE; + return 1; +} + +struct json_parser_callbacks parser_callbacks = { + .parse_list_open = test_parse_list_open, + .parse_list_close = test_parse_list_close, + + .parse_value = test_parse_value +}; + +static void +test_io_processor_init(struct test_io_processor *tproc, + const struct json_io_test *test, + struct istream *input, struct ostream *output) +{ + i_zero(tproc); + tproc->membuf = str_new(default_pool, 256);; + tproc->strbuf = str_new(default_pool, 256); + + tproc->output = output; + o_stream_set_no_error_handling(tproc->output, TRUE); + tproc->input = input; + + tproc->parser = json_parser_init( + tproc->input, &test->limits, test->flags, + &parser_callbacks, tproc); + tproc->generator = json_generator_init(tproc->output, 0); +} + +static void test_io_processor_deinit(struct test_io_processor *tproc) +{ + json_generator_deinit(&tproc->generator); + json_parser_deinit(&tproc->parser); + + buffer_free(&tproc->strbuf); + buffer_free(&tproc->membuf); +} + +static void test_json_io(void) +{ + static const unsigned int margins[] = { 0, 1, 2, 10, 50 }; + string_t *outbuf; + unsigned int i, j; + + outbuf = str_new(default_pool, 256); + + for (i = 0; i < tests_count; i++) T_BEGIN { + const struct json_io_test *test; + struct test_io_processor tproc; + const char *text, *text_out; + unsigned int pos, margin, text_len; + + test = &tests[i]; + text = test->input; + text_out = test->output; + if (text_out == NULL) + text_out = test->input; + text_len = strlen(text); + + test_begin(t_strdup_printf("json io [%d]", i)); + + for (j = 0; j < N_ELEMENTS(margins); j++) { + struct istream *input; + struct ostream *output; + const char *error = NULL; + int pret = 0, wret = 0; + + margin = margins[j]; + + buffer_set_used_size(outbuf, 0); + + input = test_istream_create_data(text, text_len); + output = o_stream_create_buffer(outbuf); + test_io_processor_init(&tproc, test, input, output); + + o_stream_set_max_buffer_size(output, 0); + pret = 0; wret = 1; + for (pos = 0; + pos <= (text_len+margin) && + (pret == 0 || wret == 0); + pos++) { + test_istream_set_size(input, pos); + o_stream_set_max_buffer_size(output, + (pos > margin ? pos - margin : 0)); + if (wret > 0 && pret == 0) { + pret = json_parse_more(tproc.parser, + &error); + if (pret < 0) + break; + } + wret = test_write(&tproc); + if (wret == 0) + continue; + if (wret < 0) + break; + } + + if (pret == 0) + pret = json_parse_more(tproc.parser, &error); + + o_stream_set_max_buffer_size(output, SIZE_MAX); + wret = json_generator_flush(tproc.generator); + + test_out_reason_quiet( + t_strdup_printf("parse success " + "(trickle, margin=%u)", margin), + pret > 0, error); + test_out_quiet( + t_strdup_printf("write success " + "(trickle, margin=%u)", margin), + wret > 0); + test_out_quiet( + t_strdup_printf("io match (trickle, margin=%u)", + margin), + strcmp(text_out, str_c(outbuf)) == 0); + if (debug) { + i_debug("OUT: >%s<", text_out); + i_debug("OUT: >%s<", str_c(outbuf)); + } + + test_io_processor_deinit(&tproc); + i_stream_destroy(&input); + o_stream_destroy(&output); + } + + test_end(); + + } T_END; + + buffer_free(&outbuf); +} + +static void test_json_async_io_input_callback(struct test_io_processor *tproc) +{ + const char *error; + int ret; + + ret = json_parse_more(tproc->parser, &error); + if (ret == 0) { + ret = test_write(tproc); + if (ret == 0) { + o_stream_set_flush_pending(tproc->output, TRUE); + io_remove(&tproc->io); + return; + } + if (ret < 0) { + test_assert(FALSE); + io_loop_stop(current_ioloop); + } + return; + } + + test_out_reason_quiet( + t_strdup_printf("%u: %s: parse success (async)", + tproc->tctx->scenario, tproc->name), + ret > 0, error); + if (ret < 0) { + io_loop_stop(current_ioloop); + } else { + ret = test_write(tproc); + if (ret > 0) + ret = json_generator_flush(tproc->generator); + if (ret == 0) { + o_stream_set_flush_pending(tproc->output, TRUE); + io_remove(&tproc->io); + return; + } + test_out_quiet(t_strdup_printf("%u: %s: write success (async)", + tproc->tctx->scenario, + tproc->name), ret > 0); + if (ret < 0) { + io_loop_stop(current_ioloop); + return; + } + + io_remove(&tproc->io); + o_stream_close(tproc->output); + } +} + +static int test_json_async_io_flush_callback(struct test_io_processor *tproc) +{ + int ret; + + ret = json_generator_flush(tproc->generator); + if (ret == 0) + return ret; + if (ret < 0) { + test_assert(FALSE); + io_loop_stop(current_ioloop); + return -1; + } + + ret = test_write(tproc); + if (ret == 0) + return 0; + if (ret < 0) { + test_assert(FALSE); + io_loop_stop(current_ioloop); + return -1; + } + + if (tproc->io == NULL) { + tproc->io = io_add_istream( + tproc->input, test_json_async_io_input_callback, tproc); + i_stream_set_input_pending(tproc->input, TRUE); + } + return 1; +} + +static void +test_json_async_io_pump_in_callback(enum iostream_pump_status status, + struct test_io_context *tctx) +{ + if (status != IOSTREAM_PUMP_STATUS_INPUT_EOF) { + test_assert(FALSE); + io_loop_stop(current_ioloop); + return; + } + + struct ostream *output = iostream_pump_get_output(tctx->pump_in); + + o_stream_close(output); + iostream_pump_destroy(&tctx->pump_in); +} + +static void +test_json_async_io_pump_out_callback(enum iostream_pump_status status, + struct test_io_context *tctx) +{ + if (status != IOSTREAM_PUMP_STATUS_INPUT_EOF) + test_assert(FALSE); + + io_loop_stop(current_ioloop); + iostream_pump_destroy(&tctx->pump_out); +} + +static void +test_json_async_io_run(const struct json_io_test *test, unsigned int scenario) +{ + struct test_io_context tctx; + string_t *outbuf; + struct test_io_processor tproc1, tproc2; + struct ioloop *ioloop; + int fd_pipe1[2], fd_pipe2[2], fd_pipe3[2]; + const char *text, *text_out; + unsigned int text_len; + struct istream *input, *pipe1_input, *pipe2_input, *pipe3_input; + struct ostream *output, *pipe1_output, *pipe2_output, *pipe3_output; + + i_zero(&tctx); + tctx.test = test; + tctx.scenario = scenario; + + text = test->input; + text_out = test->output; + if (text_out == NULL) + text_out = test->input; + text_len = strlen(text); + + outbuf = str_new(default_pool, 256); + + if (pipe(fd_pipe1) < 0) + i_fatal("pipe() failed: %m"); + if (pipe(fd_pipe2) < 0) + i_fatal("pipe() failed: %m"); + if (pipe(fd_pipe3) < 0) + i_fatal("pipe() failed: %m"); + fd_set_nonblock(fd_pipe1[0], TRUE); + fd_set_nonblock(fd_pipe1[1], TRUE); + fd_set_nonblock(fd_pipe2[0], TRUE); + fd_set_nonblock(fd_pipe2[1], TRUE); + fd_set_nonblock(fd_pipe3[0], TRUE); + fd_set_nonblock(fd_pipe3[1], TRUE); + + ioloop = io_loop_create(); + + input = i_stream_create_from_data(text, text_len); + output = o_stream_create_buffer(outbuf); + + switch (scenario) { + case 0: case 2: + pipe1_input = i_stream_create_fd_autoclose(&fd_pipe1[0], 16); + pipe2_input = i_stream_create_fd_autoclose(&fd_pipe2[0], 32); + pipe3_input = i_stream_create_fd_autoclose(&fd_pipe3[0], 64); + break; + case 1: case 3: + pipe1_input = i_stream_create_fd_autoclose(&fd_pipe1[0], 128); + pipe2_input = i_stream_create_fd_autoclose(&fd_pipe2[0], 64); + pipe3_input = i_stream_create_fd_autoclose(&fd_pipe3[0], 32); + break; + default: + i_unreached(); + } + + switch (scenario) { + case 0: case 1: + pipe1_output = o_stream_create_fd_autoclose(&fd_pipe1[1], 32); + pipe2_output = o_stream_create_fd_autoclose(&fd_pipe2[1], 64); + pipe3_output = o_stream_create_fd_autoclose(&fd_pipe3[1], 128); + break; + case 2: case 3: + pipe1_output = o_stream_create_fd_autoclose(&fd_pipe1[1], 64); + pipe2_output = o_stream_create_fd_autoclose(&fd_pipe2[1], 32); + pipe3_output = o_stream_create_fd_autoclose(&fd_pipe3[1], 16); + break; + default: + i_unreached(); + } + + tctx.pump_in = iostream_pump_create(input, pipe1_output); + tctx.pump_out = iostream_pump_create(pipe3_input, output); + + iostream_pump_set_completion_callback( + tctx.pump_in, test_json_async_io_pump_in_callback, &tctx); + iostream_pump_set_completion_callback( + tctx.pump_out, test_json_async_io_pump_out_callback, &tctx); + + /* Processor 1 */ + test_io_processor_init(&tproc1, test, pipe1_input, pipe2_output); + tproc1.tctx = &tctx; + tproc1.name = "proc_a"; + o_stream_uncork(tproc1.output); + + o_stream_set_flush_callback(tproc1.output, + test_json_async_io_flush_callback, &tproc1); + tproc1.io = io_add_istream(tproc1.input, + test_json_async_io_input_callback, &tproc1); + + /* Processor 2 */ + test_io_processor_init(&tproc2, test, pipe2_input, pipe3_output); + tproc2.tctx = &tctx; + tproc2.name = "proc_b"; + o_stream_uncork(tproc2.output); + + o_stream_set_flush_callback(tproc2.output, + test_json_async_io_flush_callback, &tproc2); + tproc2.io = io_add_istream(tproc2.input, + test_json_async_io_input_callback, &tproc2); + + struct timeout *to = timeout_add(5000, io_loop_stop, ioloop); + + iostream_pump_start(tctx.pump_in); + iostream_pump_start(tctx.pump_out); + + io_loop_run(ioloop); + + timeout_remove(&to); + + test_io_processor_deinit(&tproc1); + test_io_processor_deinit(&tproc2); + + iostream_pump_destroy(&tctx.pump_in); + iostream_pump_destroy(&tctx.pump_out); + + i_stream_destroy(&input); + i_stream_destroy(&pipe1_input); + i_stream_destroy(&pipe2_input); + i_stream_destroy(&pipe3_input); + + o_stream_destroy(&output); + o_stream_destroy(&pipe1_output); + o_stream_destroy(&pipe2_output); + o_stream_destroy(&pipe3_output); + + io_loop_destroy(&ioloop); + + test_out_quiet(t_strdup_printf("%u: io match (async)", scenario), + strcmp(text_out, str_c(outbuf)) == 0); + + buffer_free(&outbuf); +} + +static void test_json_io_async(void) +{ + unsigned int i, sc; + + for (i = 0; i < tests_count; i++) T_BEGIN { + test_begin(t_strdup_printf("json io async [%d]", i)); + + for (sc = 0; sc < 4; sc++) + test_json_async_io_run(&tests[i], sc); + + test_end(); + } T_END; +} + +int main(int argc, char *argv[]) +{ + int ret, c; + + random_init(); + + static void (*test_functions[])(void) = { + test_json_io, + test_json_io_async, + NULL + }; + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + argc -= optind; + argv += optind; + + if (argc > 0) + i_fatal("Usage: %s [-D]", argv[0]); + + ret = test_run(test_functions); + + random_deinit(); + + return ret; +}