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)
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)
--- /dev/null
+/* 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 <math.h>
+
+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++;
+ }
+ }
+}
--- /dev/null
+#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
--- /dev/null
+/* 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 <unistd.h>
+
+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;
+
+ /* <JSON-text> */
+ test_begin("json write <JSON-text>");
+ 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;
+
+ /* <JSON-text> - long */
+ test_begin("json write <JSON-text> - 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;
+
+ /* [ <JSON-text> ] */
+ test_begin("json write array - [ <JSON-text> ]");
+ 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, <JSON-text> ] */
+ test_begin("json write array - [ string, <JSON-text> ]");
+ 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;
+
+ /* [ <JSON-text>, <JSON-text>, <JSON-text> ] */
+ test_begin("json write array - "
+ "[ <JSON-text>, <JSON-text>, <JSON-text> ]");
+ 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": <JSON-text> } */
+ test_begin("json write object - { \"frop\": <JSON-text> }");
+ 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);
+}
--- /dev/null
+/* 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 <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+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;
+}