json-types.c \
json-parser.new.c \
json-generator.c \
- json-istream.c
+ json-istream.c \
+ json-ostream.c
libjson_la_LIBADD = -lm
headers = \
json-types.h \
json-parser.new.h \
json-generator.h \
- json-istream.h
+ json-istream.h \
+ json-ostream.h
test_programs = \
test-json-parser \
test-json-generator \
test-json-io \
- test-json-istream
+ test-json-istream \
+ test-json-ostream
noinst_PROGRAMS = $(test_programs)
test_json_istream_DEPENDENCIES = \
$(test_deps)
+test_json_ostream_SOURCE = \
+ test-json-ostream.c
+test_json_ostream_LDADD = \
+ $(test_libs)
+test_json_ostream_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 "ostream.h"
+
+#include "json-ostream.h"
+
+enum json_ostream_node_state {
+ JSON_OSTREAM_NODE_STATE_NONE = 0,
+ JSON_OSTREAM_NODE_STATE_VALUE,
+ JSON_OSTREAM_NODE_STATE_ARRAY_CLOSE,
+ JSON_OSTREAM_NODE_STATE_OBJECT_CLOSE,
+};
+
+struct json_ostream {
+ int refcount;
+
+ struct ostream *output;
+ struct json_generator *generator;
+
+ struct json_node node;
+ enum json_ostream_node_state node_state;
+ unsigned int write_node_level;
+
+ struct json_data node_data;
+ string_t *buffer;
+
+ char *error;
+
+ bool member_name_written:1;
+ bool value_opened:1;
+ bool value_persists:1;
+ bool string_opened:1;
+ bool last_errors_not_checked:1;
+ bool error_handling_disabled:1;
+ bool nfailed:1;
+ bool closed:1;
+};
+
+static int json_ostream_write_node_more(struct json_ostream *stream);
+
+struct json_ostream *
+json_ostream_create(struct ostream *output,
+ enum json_generator_flags gen_flags)
+{
+ struct json_ostream *stream;
+
+ stream = i_new(struct json_ostream, 1);
+ stream->refcount = 1;
+
+ stream->output = output;
+ o_stream_ref(output);
+
+ stream->generator = json_generator_init(output, gen_flags);
+
+ return stream;
+}
+
+struct json_ostream *
+json_ostream_create_str(string_t *buf, enum json_generator_flags gen_flags)
+{
+ struct json_ostream *stream;
+
+ stream = i_new(struct json_ostream, 1);
+ stream->refcount = 1;
+
+ stream->generator = json_generator_init_str(buf, gen_flags);
+
+ return stream;
+}
+
+void json_ostream_ref(struct json_ostream *stream)
+{
+ i_assert(stream->refcount > 0);
+ stream->refcount++;
+}
+
+void json_ostream_unref(struct json_ostream **_stream)
+{
+ struct json_ostream *stream = *_stream;
+
+ if (stream == NULL)
+ return;
+ *_stream = NULL;
+
+ i_assert(stream->refcount > 0);
+ if (--stream->refcount != 0)
+ return;
+
+ if (stream->output != NULL && stream->last_errors_not_checked &&
+ !stream->error_handling_disabled) {
+ i_panic("JSON output stream %s is missing error handling",
+ o_stream_get_name(stream->output));
+ }
+
+ json_generator_deinit(&stream->generator);
+ o_stream_unref(&stream->output);
+ str_free(&stream->buffer);
+
+ i_free(stream->error);
+ i_free(stream);
+}
+
+void json_ostream_destroy(struct json_ostream **_stream)
+{
+ struct json_ostream *stream = *_stream;
+
+ if (stream == NULL)
+ return;
+
+ json_ostream_close(stream);
+ json_ostream_unref(_stream);
+}
+
+unsigned int json_ostream_get_write_node_level(struct json_ostream *stream)
+{
+ return stream->write_node_level;
+}
+
+static void ATTR_FORMAT(2, 3)
+json_ostream_set_error(struct json_ostream *stream, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ i_free(stream->error);
+ stream->error = i_strdup_vprintf(fmt, args);
+ va_end(args);
+}
+
+const char *json_ostream_get_error(struct json_ostream *stream)
+{
+ if (stream->error != NULL)
+ return stream->error;
+ if (stream->closed)
+ return "<closed>";
+ if (stream->output != NULL)
+ return o_stream_get_error(stream->output);
+ return "<no error>";
+}
+
+void json_ostream_close(struct json_ostream *stream)
+{
+ stream->closed = TRUE;
+}
+
+bool json_ostream_is_closed(struct json_ostream *stream)
+{
+ return stream->closed;
+}
+
+void json_ostream_cork(struct json_ostream *stream)
+{
+ if (stream->output != NULL)
+ o_stream_cork(stream->output);
+}
+
+void json_ostream_uncork(struct json_ostream *stream)
+{
+ if (stream->output != NULL)
+ o_stream_uncork(stream->output);
+}
+
+bool json_ostream_is_corked(struct json_ostream *stream)
+{
+ return (stream->output != NULL && o_stream_is_corked(stream->output));
+}
+
+int json_ostream_nfinish(struct json_ostream *stream)
+{
+ if (stream->closed)
+ return -1;
+ if (stream->error != NULL)
+ return -1;
+ json_ostream_nflush(stream);
+ if (stream->output == NULL)
+ return 0;
+ json_ostream_ignore_last_errors(stream);
+ if (stream->output->stream_errno == 0 && stream->nfailed) {
+ json_ostream_set_error(stream,
+ "Output stream buffer was full (%zu bytes)",
+ o_stream_get_max_buffer_size(stream->output));
+ return -1;
+ }
+ return (stream->output->stream_errno != 0 ? -1 : 0);
+}
+
+void json_ostream_nfinish_destroy(struct json_ostream **_stream)
+{
+ struct json_ostream *stream = *_stream;
+ int ret;
+
+ if (stream == NULL)
+ return;
+
+ ret = json_ostream_nfinish(stream);
+ i_assert(ret >= 0);
+ json_ostream_destroy(_stream);
+}
+
+void json_ostream_ignore_last_errors(struct json_ostream *stream)
+{
+ stream->last_errors_not_checked = FALSE;
+}
+
+void json_ostream_set_no_error_handling(struct json_ostream *stream, bool set)
+{
+ stream->error_handling_disabled = set;
+}
+
+/*
+ * Partial data
+ */
+
+static void json_ostream_persist_value(struct json_ostream *stream)
+{
+ if (!json_node_is_singular(&stream->node))
+ return;
+ if (stream->value_persists)
+ return;
+ stream->value_persists = TRUE;
+
+ switch (stream->node.value.content_type) {
+ case JSON_CONTENT_TYPE_STRING:
+ i_unreached();
+ case JSON_CONTENT_TYPE_DATA:
+ if (stream->buffer == NULL)
+ stream->buffer = str_new(default_pool, 128);
+ stream->node_data = *stream->node.value.content.data;
+ str_truncate(stream->buffer, 0);
+ str_append_data(stream->buffer,
+ stream->node_data.data, stream->node_data.size);
+ stream->node_data.data = str_data(stream->buffer);
+ stream->node.value.content.data = &stream->node_data;
+ break;
+ default:
+ break;
+ }
+}
+
+static int json_ostream_do_write_more(struct json_ostream *stream)
+{
+ struct json_data *jdata;
+ ssize_t sret;
+ int ret;
+
+ if (stream->closed)
+ return -1;
+
+ switch (stream->node.value.content_type) {
+ case JSON_CONTENT_TYPE_STRING:
+ i_zero(&stream->node_data);
+ stream->node_data.data = (const unsigned char *)
+ stream->node.value.content.str;
+ stream->node_data.size = strlen(stream->node.value.content.str);
+ stream->node.value.content.data = &stream->node_data;
+ stream->node.value.content_type =
+ JSON_CONTENT_TYPE_DATA;
+ /* fall through */
+ case JSON_CONTENT_TYPE_DATA:
+ jdata = stream->node.value.content.data;
+ switch (stream->node.type) {
+ /* string */
+ case JSON_TYPE_STRING:
+ if (stream->string_opened)
+ stream->value_opened = TRUE;
+ if (!stream->value_opened) {
+ json_generate_string_open(stream->generator);
+ stream->value_opened = TRUE;
+ }
+ while (jdata->size > 0) {
+ sret = json_generate_string_more(
+ stream->generator, jdata->data,
+ jdata->size, TRUE);
+ if (sret <= 0)
+ return (int)sret;
+ i_assert((size_t)sret <= jdata->size);
+ jdata->data += sret;
+ jdata->size -= (size_t)sret;
+ }
+ if (!stream->string_opened) {
+ ret = json_generate_string_write_close(
+ stream->generator);
+ if (ret <= 0)
+ return ret;
+ }
+ stream->value_opened = FALSE;
+ return 1;
+ /* number, JSON-text */
+ case JSON_TYPE_NUMBER:
+ case JSON_TYPE_TEXT:
+ i_assert(!stream->string_opened);
+ if (!stream->value_opened) {
+ json_generate_text_open(stream->generator);
+ stream->value_opened = TRUE;
+ }
+ while (jdata->size > 0) {
+ sret = json_generate_text_more(
+ stream->generator, jdata->data,
+ jdata->size);
+ if (sret <= 0)
+ return (int)sret;
+ i_assert((size_t)sret <= jdata->size);
+ jdata->data += sret;
+ jdata->size -= (size_t)sret;
+ }
+ ret = json_generate_text_close(stream->generator);
+ if (ret <= 0)
+ return ret;
+ stream->value_opened = FALSE;
+ return 1;
+ default:
+ i_unreached();
+ }
+ break;
+ default:
+ break;
+ }
+
+ return json_generate_value(stream->generator, stream->node.type,
+ &stream->node.value);
+}
+
+static int json_ostream_write_more(struct json_ostream *stream)
+{
+ int ret;
+
+ ret = json_ostream_do_write_more(stream);
+ if (ret <= 0) {
+ i_assert(stream->output != NULL);
+ return ret;
+ }
+ i_zero(&stream->node);
+ stream->value_persists = FALSE;
+ stream->value_opened = FALSE;
+ return 1;
+}
+
+int json_ostream_flush(struct json_ostream *stream)
+{
+ int ret;
+
+ if (stream->closed)
+ return -1;
+ if (stream->node_state != JSON_OSTREAM_NODE_STATE_NONE) {
+ ret = json_ostream_write_node_more(stream);
+ if (ret <= 0)
+ return ret;
+ }
+ if (json_node_is_none(&stream->node))
+ return json_generator_flush(stream->generator);
+ ret = json_ostream_write_more(stream);
+ if (ret <= 0)
+ return ret;
+ return 1;
+}
+
+void json_ostream_nflush(struct json_ostream *stream)
+{
+ if (unlikely(stream->closed))
+ return;
+ if (unlikely(stream->nfailed)) {
+ i_assert(stream->output != NULL);
+ return;
+ }
+ if (stream->output != NULL) {
+ if (unlikely(stream->output->closed ||
+ stream->output->stream_errno != 0))
+ return;
+ }
+ if (json_ostream_flush(stream) <= 0) {
+ i_assert(stream->output != NULL);
+ stream->nfailed = TRUE;
+ }
+ stream->last_errors_not_checked = TRUE;
+}
+
+static int
+json_ostream_write_init(struct json_ostream *stream, const char *name,
+ enum json_type type)
+{
+ int ret;
+
+ i_assert(name == NULL || !stream->string_opened);
+ i_assert(!stream->string_opened || type == JSON_TYPE_STRING);
+
+ ret = json_ostream_flush(stream);
+ if (ret <= 0)
+ return ret;
+
+ if (stream->string_opened)
+ return 1;
+
+ if (name != NULL) {
+ i_assert(!stream->member_name_written);
+ ret = json_generate_object_member(stream->generator, name);
+ if (ret <= 0)
+ return ret;
+ }
+ stream->member_name_written = FALSE;
+ return 1;
+}
+
+/*
+ * Values
+ */
+
+static inline bool json_ostream_nwrite_pre(struct json_ostream *stream)
+{
+ if (unlikely(stream->closed))
+ return FALSE;
+ if (unlikely(stream->nfailed)) {
+ i_assert(stream->output != NULL);
+ return FALSE;
+ }
+ if (stream->output != NULL) {
+ if (unlikely(stream->output->closed ||
+ stream->output->stream_errno != 0))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static inline void
+json_ostream_nwrite_post(struct json_ostream *stream, int ret)
+{
+ if (ret <= 0) {
+ i_assert(stream->output != NULL);
+ stream->nfailed = TRUE;
+ }
+ stream->last_errors_not_checked = TRUE;
+}
+
+/* object member */
+
+int json_ostream_write_object_member(struct json_ostream *stream,
+ const char *name)
+{
+ int ret;
+
+ ret = json_ostream_flush(stream);
+ if (ret <= 0)
+ return ret;
+
+ i_assert(!stream->member_name_written);
+ ret = json_generate_object_member(stream->generator, name);
+ if (ret <= 0)
+ return ret;
+ stream->member_name_written = TRUE;
+ return 1;
+}
+
+void json_ostream_nwrite_object_member(struct json_ostream *stream,
+ const char *name)
+{
+ int ret;
+
+ if (!json_ostream_nwrite_pre(stream))
+ return;
+ ret = json_ostream_write_object_member(stream, name);
+ json_ostream_nwrite_post(stream, ret);
+}
+
+/* value */
+
+static int
+json_ostream_do_write_value(struct json_ostream *stream,
+ const char *name, enum json_type type,
+ const struct json_value *value, bool persist)
+{
+ int ret;
+
+ ret = json_ostream_write_init(stream, name, type);
+ if (ret <= 0)
+ return ret;
+
+ i_zero(&stream->node);
+ stream->node.type = type;
+ stream->node.value = *value;
+
+ ret = json_ostream_write_more(stream);
+ if (ret < 0)
+ return ret;
+ if (ret == 0 && persist)
+ json_ostream_persist_value(stream);
+ return 1;
+}
+
+int json_ostream_write_value(struct json_ostream *stream,
+ const char *name, enum json_type type,
+ const struct json_value *value)
+{
+ return json_ostream_do_write_value(stream, name, type, value, TRUE);
+}
+
+void json_ostream_nwrite_value(struct json_ostream *stream,
+ const char *name, enum json_type type,
+ const struct json_value *value)
+{
+ int ret;
+
+ if (!json_ostream_nwrite_pre(stream))
+ return;
+ ret = json_ostream_do_write_value(stream, name, type, value, FALSE);
+ json_ostream_nwrite_post(stream, ret);
+}
+
+/* number */
+
+int json_ostream_write_number(struct json_ostream *stream,
+ const char *name, intmax_t number)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_INTEGER;
+ jvalue.content.intnum = number;
+
+ return json_ostream_write_value(stream, name,
+ JSON_TYPE_NUMBER, &jvalue);
+}
+
+void json_ostream_nwrite_number(struct json_ostream *stream,
+ const char *name, intmax_t number)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_INTEGER;
+ jvalue.content.intnum = number;
+
+ json_ostream_nwrite_value(stream, name,
+ JSON_TYPE_NUMBER, &jvalue);
+}
+
+int json_ostream_write_number_raw(struct json_ostream *stream,
+ const char *name, const char *number)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_STRING;
+ jvalue.content.str = number;
+
+ return json_ostream_write_value(stream, name,
+ JSON_TYPE_NUMBER, &jvalue);
+}
+
+void json_ostream_nwrite_number_raw(struct json_ostream *stream,
+ const char *name, const char *number)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_STRING;
+ jvalue.content.str = number;
+
+ json_ostream_nwrite_value(stream, name, JSON_TYPE_NUMBER, &jvalue);
+}
+
+/* string */
+
+int json_ostream_write_string_data(struct json_ostream *stream,
+ const char *name,
+ const void *data, size_t size)
+{
+ struct json_value jvalue;
+ struct json_data jdata;
+
+ i_zero(&jdata);
+ jdata.data = data;
+ jdata.size = size;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_DATA;
+ jvalue.content.data = &jdata;
+
+ return json_ostream_write_value(stream, name, JSON_TYPE_STRING,
+ &jvalue);
+}
+
+void json_ostream_nwrite_string_data(struct json_ostream *stream,
+ const char *name,
+ const void *data, size_t size)
+{
+ struct json_value jvalue;
+ struct json_data jdata;
+
+ i_zero(&jdata);
+ jdata.data = data;
+ jdata.size = size;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_DATA;
+ jvalue.content.data = &jdata;
+
+ json_ostream_nwrite_value(stream, name, JSON_TYPE_STRING, &jvalue);
+}
+
+int json_ostream_write_string(struct json_ostream *stream,
+ const char *name, const char *str)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_STRING;
+ jvalue.content.str = str;
+
+ return json_ostream_write_value(stream, name, JSON_TYPE_STRING,
+ &jvalue);
+}
+
+void json_ostream_nwrite_string(struct json_ostream *stream,
+ const char *name, const char *str)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_STRING;
+ jvalue.content.str = str;
+
+ json_ostream_nwrite_value(stream, name, JSON_TYPE_STRING, &jvalue);
+}
+
+void json_ostream_nwritef_string(struct json_ostream *stream,
+ const char *name, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ json_ostream_nwrite_string(stream, name,
+ t_strdup_vprintf(format, args));
+ va_end(args);
+}
+
+int json_ostream_open_string(struct json_ostream *stream, const char *name)
+{
+ int ret;
+
+ ret = json_ostream_write_init(stream, name, JSON_TYPE_STRING);
+ if (ret <= 0)
+ return ret;
+
+ i_zero(&stream->node);
+ json_generate_string_open(stream->generator);
+ stream->string_opened = TRUE;
+ return 1;
+}
+
+void json_ostream_nopen_string(struct json_ostream *stream, const char *name)
+{
+ int ret;
+
+ if (!json_ostream_nwrite_pre(stream))
+ return;
+ ret = json_ostream_open_string(stream, name);
+ json_ostream_nwrite_post(stream, ret);
+}
+
+int json_ostream_close_string(struct json_ostream *stream)
+{
+ int ret;
+
+ i_assert(stream->string_opened);
+
+ ret = json_ostream_flush(stream);
+ if (ret <= 0)
+ return ret;
+ i_zero(&stream->node);
+ ret = json_generate_string_write_close(stream->generator);
+ if (ret <= 0)
+ return ret;
+ stream->string_opened = FALSE;
+ return 1;
+}
+
+void json_ostream_nclose_string(struct json_ostream *stream)
+{
+ int ret;
+
+ if (!json_ostream_nwrite_pre(stream))
+ return;
+ ret = json_ostream_close_string(stream);
+ json_ostream_nwrite_post(stream, ret);
+}
+
+/* null */
+
+int json_ostream_write_null(struct json_ostream *stream, const char *name)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ return json_ostream_write_value(stream, name, JSON_TYPE_NULL, &jvalue);
+}
+
+void json_ostream_nwrite_null(struct json_ostream *stream, const char *name)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ json_ostream_nwrite_value(stream, name, JSON_TYPE_NULL, &jvalue);
+}
+
+
+/* false, true */
+
+int json_ostream_write_false(struct json_ostream *stream, const char *name)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ return json_ostream_write_value(stream, name, JSON_TYPE_FALSE,
+ &jvalue);
+}
+
+void json_ostream_nwrite_false(struct json_ostream *stream, const char *name)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ json_ostream_nwrite_value(stream, name, JSON_TYPE_FALSE, &jvalue);
+}
+
+int json_ostream_write_true(struct json_ostream *stream, const char *name)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ return json_ostream_write_value(stream, name, JSON_TYPE_TRUE, &jvalue);
+}
+
+void json_ostream_nwrite_true(struct json_ostream *stream, const char *name)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ json_ostream_nwrite_value(stream, name, JSON_TYPE_TRUE, &jvalue);
+}
+
+int json_ostream_write_bool(struct json_ostream *stream, const char *name,
+ bool value)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ return json_ostream_write_value(
+ stream, name, (value ? JSON_TYPE_TRUE : JSON_TYPE_FALSE),
+ &jvalue);
+}
+
+void json_ostream_nwrite_bool(struct json_ostream *stream, const char *name,
+ bool value)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ json_ostream_nwrite_value(
+ stream, name, (value ? JSON_TYPE_TRUE : JSON_TYPE_FALSE),
+ &jvalue);
+}
+
+/* object */
+
+int json_ostream_descend_object(struct json_ostream *stream, const char *name)
+{
+ int ret;
+
+ ret = json_ostream_write_init(stream, name, JSON_TYPE_OBJECT);
+ if (ret <= 0)
+ return ret;
+
+ i_zero(&stream->node);
+ json_generate_object_open(stream->generator);
+ stream->write_node_level++;
+ return 1;
+}
+
+void json_ostream_ndescend_object(struct json_ostream *stream,
+ const char *name)
+{
+ int ret;
+
+ if (!json_ostream_nwrite_pre(stream))
+ return;
+ ret = json_ostream_descend_object(stream, name);
+ json_ostream_nwrite_post(stream, ret);
+}
+
+int json_ostream_ascend_object(struct json_ostream *stream)
+{
+ int ret;
+
+ ret = json_ostream_flush(stream);
+ if (ret <= 0)
+ return ret;
+ ret = json_generate_object_close(stream->generator);
+ if (ret <= 0)
+ return ret;
+ i_assert(stream->write_node_level > 0);
+ stream->write_node_level--;
+ return 1;
+}
+
+void json_ostream_nascend_object(struct json_ostream *stream)
+{
+ int ret;
+
+ if (!json_ostream_nwrite_pre(stream))
+ return;
+ ret = json_ostream_ascend_object(stream);
+ json_ostream_nwrite_post(stream, ret);
+}
+
+/* array */
+
+int json_ostream_descend_array(struct json_ostream *stream, const char *name)
+{
+ int ret;
+
+ ret = json_ostream_write_init(stream, name, JSON_TYPE_ARRAY);
+ if (ret <= 0)
+ return ret;
+
+ i_zero(&stream->node);
+ json_generate_array_open(stream->generator);
+ stream->write_node_level++;
+ return 1;
+}
+
+void json_ostream_ndescend_array(struct json_ostream *stream,
+ const char *name)
+{
+ int ret;
+
+ if (!json_ostream_nwrite_pre(stream))
+ return;
+ ret = json_ostream_descend_array(stream, name);
+ json_ostream_nwrite_post(stream, ret);
+}
+
+int json_ostream_ascend_array(struct json_ostream *stream)
+{
+ int ret;
+
+ ret = json_ostream_flush(stream);
+ if (ret <= 0)
+ return ret;
+ ret = json_generate_array_close(stream->generator);
+ if (ret <= 0)
+ return ret;
+ i_assert(stream->write_node_level > 0);
+ stream->write_node_level--;
+ return 1;
+}
+
+void json_ostream_nascend_array(struct json_ostream *stream)
+{
+ int ret;
+
+ if (!json_ostream_nwrite_pre(stream))
+ return;
+ ret = json_ostream_ascend_array(stream);
+ json_ostream_nwrite_post(stream, ret);
+}
+
+/* JSON-text */
+
+/* string */
+
+int json_ostream_write_text_data(struct json_ostream *stream,
+ const char *name,
+ const void *data, size_t size)
+{
+ struct json_value jvalue;
+ struct json_data jdata;
+
+ i_zero(&jdata);
+ jdata.data = data;
+ jdata.size = size;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_DATA;
+ jvalue.content.data = &jdata;
+
+ return json_ostream_write_value(stream, name, JSON_TYPE_TEXT, &jvalue);
+}
+
+void json_ostream_nwrite_text_data(struct json_ostream *stream,
+ const char *name,
+ const void *data, size_t size)
+{
+ struct json_value jvalue;
+ struct json_data jdata;
+
+ i_zero(&jdata);
+ jdata.data = data;
+ jdata.size = size;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_DATA;
+ jvalue.content.data = &jdata;
+
+ json_ostream_nwrite_value(stream, name, JSON_TYPE_TEXT, &jvalue);
+}
+
+int json_ostream_write_text(struct json_ostream *stream,
+ const char *name, const char *str)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_STRING;
+ jvalue.content.str = str;
+
+ return json_ostream_write_value(stream, name, JSON_TYPE_TEXT, &jvalue);
+}
+
+void json_ostream_nwrite_text(struct json_ostream *stream,
+ const char *name, const char *str)
+{
+ struct json_value jvalue;
+
+ i_zero(&jvalue);
+ jvalue.content_type = JSON_CONTENT_TYPE_STRING;
+ jvalue.content.str = str;
+
+ json_ostream_nwrite_value(stream, name, JSON_TYPE_TEXT, &jvalue);
+}
+
+/*
+ * Nodes
+ */
+
+static int json_ostream_write_node_more(struct json_ostream *stream)
+{
+ int ret;
+
+ switch (stream->node_state) {
+ case JSON_OSTREAM_NODE_STATE_NONE:
+ break;
+ case JSON_OSTREAM_NODE_STATE_VALUE:
+ /* Continue value */
+ ret = json_ostream_write_more(stream);
+ if (ret <= 0)
+ return ret;
+ stream->node_state = JSON_OSTREAM_NODE_STATE_NONE;
+ i_zero(&stream->node);
+ break;
+ case JSON_OSTREAM_NODE_STATE_ARRAY_CLOSE:
+ /* Continue array close */
+ ret = json_generate_array_close(stream->generator);
+ if (ret <= 0)
+ return ret;
+ stream->node_state = JSON_OSTREAM_NODE_STATE_NONE;
+ i_zero(&stream->node);
+ break;
+ case JSON_OSTREAM_NODE_STATE_OBJECT_CLOSE:
+ /* Continue object close */
+ ret = json_generate_object_close(stream->generator);
+ if (ret <= 0)
+ return ret;
+ stream->node_state = JSON_OSTREAM_NODE_STATE_NONE;
+ i_zero(&stream->node);
+ break;
+ default:
+ i_unreached();
+ }
+ return 1;
+}
+
+static int
+json_ostream_do_write_node(struct json_ostream *stream,
+ const struct json_node *node, bool flush,
+ bool persist)
+{
+ int ret;
+
+ if (flush) {
+ ret = json_ostream_flush(stream);
+ if (ret <= 0)
+ return ret;
+
+ i_assert(stream->node_state == JSON_OSTREAM_NODE_STATE_NONE);
+ } else if (stream->node_state != JSON_OSTREAM_NODE_STATE_NONE) {
+ return 0;
+ }
+
+ i_assert(!json_node_is_none(node));
+
+ if (!json_node_is_end(node) && node->name != NULL) {
+ i_assert(!stream->member_name_written);
+ ret = json_generate_object_member(stream->generator,
+ node->name);
+ if (ret <= 0)
+ return ret;
+ }
+ stream->member_name_written = FALSE;
+
+ switch (node->type) {
+ case JSON_TYPE_ARRAY:
+ if (!json_node_is_array_end(node)) {
+ /* Open array */
+ json_generate_array_open(stream->generator);
+ return 1;
+ }
+ /* Close array */
+ stream->node = *node;
+ stream->node_state = JSON_OSTREAM_NODE_STATE_ARRAY_CLOSE;
+ break;
+ case JSON_TYPE_OBJECT:
+ if (!json_node_is_object_end(node)) {
+ /* Open object */
+ json_generate_object_open(stream->generator);
+ return 1;
+ }
+ /* Close object */
+ stream->node = *node;
+ stream->node_state = JSON_OSTREAM_NODE_STATE_OBJECT_CLOSE;
+ break;
+ default:
+ /* Write normal value */
+ stream->node = *node;
+ stream->node_state = JSON_OSTREAM_NODE_STATE_VALUE;
+ break;
+ }
+
+ ret = json_ostream_write_node_more(stream);
+ if (ret < 0)
+ return -1;
+ if (ret == 0 && persist)
+ json_ostream_persist_value(stream);
+
+ return 1;
+}
+
+int json_ostream_write_node(struct json_ostream *stream,
+ const struct json_node *node, bool copy)
+{
+ return json_ostream_do_write_node(stream, node, TRUE, copy);
+}
+
+void json_ostream_nwrite_node(struct json_ostream *stream,
+ const struct json_node *node)
+{
+ switch (node->type) {
+ case JSON_TYPE_ARRAY:
+ if (!json_node_is_array_end(node)) {
+ /* Open array */
+ json_ostream_ndescend_array(stream, node->name);
+ return;
+ }
+ /* Close array */
+ json_ostream_nascend_array(stream);
+ return;
+ case JSON_TYPE_OBJECT:
+ if (!json_node_is_object_end(node)) {
+ /* Open object */
+ json_ostream_ndescend_object(stream, node->name);
+ return;
+ }
+ /* Close object */
+ json_ostream_nascend_object(stream);
+ return;
+ default:
+ break;
+ }
+
+ json_ostream_nwrite_value(stream, node->name,
+ node->type, &node->value);
+}
--- /dev/null
+#ifndef JSON_OSTREAM_H
+#define JSON_OSTREAM_H
+
+#include "lib.h"
+
+#include "json-types.h"
+#include "json-generator.h"
+
+struct json_ostream;
+
+/*
+ * JSON ostream
+ */
+
+struct json_ostream *
+json_ostream_create(struct ostream *output,
+ enum json_generator_flags gen_flags);
+struct json_ostream *
+json_ostream_create_str(string_t *buf,
+ enum json_generator_flags gen_flags);
+
+void json_ostream_ref(struct json_ostream *stream);
+void json_ostream_unref(struct json_ostream **_stream);
+void json_ostream_destroy(struct json_ostream **_stream);
+
+void json_ostream_close(struct json_ostream *stream);
+bool json_ostream_is_closed(struct json_ostream *stream) ATTR_PURE;
+
+/*
+ * Position
+ */
+
+unsigned int json_ostream_get_write_node_level(struct json_ostream *stream);
+
+/*
+ * Cork
+ */
+
+void json_ostream_cork(struct json_ostream *stream);
+void json_ostream_uncork(struct json_ostream *stream);
+bool json_ostream_is_corked(struct json_ostream *stream);
+
+/*
+ * Flush
+ */
+
+/* Try to flush the output stream. Returns 1 if all sent, 0 if not,
+ -1 if error. */
+int json_ostream_flush(struct json_ostream *stream);
+void json_ostream_nflush(struct json_ostream *stream);
+
+/*
+ * Error handling
+ */
+
+/* Returns error string for the previous error. */
+const char *json_ostream_get_error(struct json_ostream *stream);
+
+/* Marks the stream's error handling as completed. Flushes the stream and
+ returns -1 if any of the nwrite*(), ndescend*(), etc. calls didn't write
+ all data. */
+int json_ostream_nfinish(struct json_ostream *stream);
+/* Same as json_ostream_nfinish() but expects guaranteed success and implicitly
+ destroys the stream. This will assert fail if the internal
+ json_ostream_nfinish() call fails, so this is mostly only suitable for
+ buffer output. */
+void json_ostream_nfinish_destroy(struct json_ostream **_stream);
+/* Marks the stream's error handling as completed to avoid i_panic() on
+ destroy. */
+void json_ostream_ignore_last_errors(struct json_ostream *stream);
+/* If error handling is disabled, the i_panic() on destroy is never called.
+ This function can be called immediately after the stream is created. */
+void json_ostream_set_no_error_handling(struct json_ostream *stream, bool set);
+
+/*
+ * Write functions
+ */
+
+/* The 'name' argument is the name of the object member if the value is
+ written in the context of an object. If not, it MUST be NULL, or the
+ underlying JSON generator will trigger an assertion panic. The object member
+ name can also be written earlier using json_ostream_write_object_member(),
+ in which case the name argument of the subsequent value write function must
+ also be NULL.
+
+ Just like an ostream, the 'n' functions send their data with delayed error
+ handling. json_ostream_nfinish() or json_ostream_ignore_last_errors()
+ must be called after these functions before the stream is destroyed. If
+ any of the data can't be sent due to stream's buffer getting full, all
+ further 'n' function calls are ignored and json_ostream_nfinish() will
+ fail.
+ */
+
+/* value */
+
+/* object member */
+
+int json_ostream_write_object_member(struct json_ostream *stream,
+ const char *name);
+void json_ostream_nwrite_object_member(struct json_ostream *stream,
+ const char *name);
+
+/* Try to write the value to the output stream. Returns 1 if buffered, 0
+ if not, -1 if error. */
+int json_ostream_write_value(struct json_ostream *stream,
+ const char *name, enum json_type type,
+ const struct json_value *value);
+void json_ostream_nwrite_value(struct json_ostream *stream,
+ const char *name, enum json_type type,
+ const struct json_value *value);
+
+/* node */
+
+/* Try to write the JSON node to the output stream. Returns 1 if buffered,
+ 0 if not, -1 if error. Value is copied to stream upon partial write if
+ copy is TRUE, otherwise caller is responsible for keeping it allocated until
+ the potentially buffered node is flushed by the stream. */
+int json_ostream_write_node(struct json_ostream *stream,
+ const struct json_node *node, bool copy);
+void json_ostream_nwrite_node(struct json_ostream *stream,
+ const struct json_node *node);
+
+/* number */
+
+/* Try to write the number to the output stream. Returns 1 if buffered,
+ 0 if not, -1 if error. */
+int json_ostream_write_number(struct json_ostream *stream,
+ const char *name, intmax_t number);
+void json_ostream_nwrite_number(struct json_ostream *stream,
+ const char *name, intmax_t number);
+/* Try to write the number (the string) to the output stream. Returns 1
+ if buffered, 0 if not, -1 if error. */
+int json_ostream_write_number_raw(struct json_ostream *stream,
+ const char *name, const char *number);
+void json_ostream_nwrite_number_raw(struct json_ostream *stream,
+ const char *name, const char *number);
+
+/* string */
+
+/* Try to write the data to the output stream as a string. Returns 1 if
+ buffered, 0 if not, -1 if error. */
+int json_ostream_write_string_data(struct json_ostream *stream,
+ const char *name,
+ const void *data, size_t size);
+void json_ostream_nwrite_string_data(struct json_ostream *stream,
+ const char *name,
+ const void *data, size_t size);
+/* Try to write the buffer to the output stream as a string. Returns 1 if
+ buffered, 0 if not, -1 if error. */
+static inline int
+json_ostream_write_string_buffer(struct json_ostream *stream,
+ const char *name, const buffer_t *buf)
+{
+ return json_ostream_write_string_data(stream, name,
+ buf->data, buf->used);
+}
+static inline void
+json_ostream_nwrite_string_buffer(struct json_ostream *stream,
+ const char *name, const buffer_t *buf)
+{
+ json_ostream_nwrite_string_data(stream, name, buf->data, buf->used);
+}
+/* Try to write the string to the output stream. Returns 1 if buffered,
+ 0 if not, -1 if error. */
+int json_ostream_write_string(struct json_ostream *stream,
+ const char *name, const char *str);
+void json_ostream_nwrite_string(struct json_ostream *stream,
+ const char *name, const char *str);
+void json_ostream_nwritef_string(struct json_ostream *stream,
+ const char *name,
+ const char *format, ...) ATTR_FORMAT(3, 4);
+
+/* Open a string on the stream, which means that all subsequent string
+ write functions are concatenated into a single JSON string value. Note that
+ the individual string values need to be valid and complete UTF-8. Any invalid
+ or incomplete UTF-8 code point will yield replacement characters in the
+ output, so code points cannot span sequential string values and must always
+ be fully contained within a single write. */
+int json_ostream_open_string(struct json_ostream *stream, const char *name);
+void json_ostream_nopen_string(struct json_ostream *stream, const char *name);
+/* Close the earlier opened string value on the stream. All subsequent string
+ write functions will create separate string values once more. */
+int json_ostream_close_string(struct json_ostream *stream);
+void json_ostream_nclose_string(struct json_ostream *stream);
+
+/* null */
+
+/* Try to write the `null' literal to the output stream. Returns 1 if
+ buffered, 0 if not, -1 if error. */
+int json_ostream_write_null(struct json_ostream *stream, const char *name);
+void json_ostream_nwrite_null(struct json_ostream *stream, const char *name);
+
+/* false, true */
+
+/* Try to write the `false' literal to the output stream. Returns 1 if
+ buffered, 0 if not, -1 if error. */
+int json_ostream_write_false(struct json_ostream *stream, const char *name);
+void json_ostream_nwrite_false(struct json_ostream *stream, const char *name);
+/* Try to write the `true' literal to the output stream. Returns 1 if
+ buffered, 0 if not, -1 if error. */
+int json_ostream_write_true(struct json_ostream *stream, const char *name);
+void json_ostream_nwrite_true(struct json_ostream *stream, const char *name);
+/* Try to write the boolean value to the output stream. Returns 1 if
+ buffered, 0 if not, -1 if error. */
+int json_ostream_write_bool(struct json_ostream *stream,
+ const char *name, bool value);
+void json_ostream_nwrite_bool(struct json_ostream *stream,
+ const char *name, bool value);
+
+/* object */
+
+/* Try to descend into a JSON object by writing '{' to the output stream.
+ Returns 1 if buffered, 0 if not, -1 if error. */
+int json_ostream_descend_object(struct json_ostream *stream,
+ const char *name);
+void json_ostream_ndescend_object(struct json_ostream *stream,
+ const char *name);
+
+/* Try to ascend from a JSON object by writing '}' to the output stream.
+ Returns 1 if buffered, 0 if not, -1 if error. */
+int json_ostream_ascend_object(struct json_ostream *stream);
+void json_ostream_nascend_object(struct json_ostream *stream);
+
+/* array */
+
+/* Try to descend into a JSON array by writing '[' to the output stream.
+ Returns 1 if buffered, 0 if not, -1 if error. */
+int json_ostream_descend_array(struct json_ostream *stream,
+ const char *name);
+void json_ostream_ndescend_array(struct json_ostream *stream,
+ const char *name);
+
+/* Try to ascend from a JSON arrayh by writing ']' to the output stream.
+ Returns 1 if buffered, 0 if not, -1 if error. */
+int json_ostream_ascend_array(struct json_ostream *stream);
+void json_ostream_nascend_array(struct json_ostream *stream);
+
+/* JSON-text */
+
+/* Try to write the data to the output stream directly (JSON-text, not as
+ a string). Returns 1 if buffered, 0 if not, -1 if error. */
+int json_ostream_write_text_data(struct json_ostream *stream,
+ const char *name,
+ const void *data, size_t size);
+void json_ostream_nwrite_text_data(struct json_ostream *stream,
+ const char *name,
+ const void *data, size_t size);
+
+/* Try to write the string to the output stream directly (JSON-text, not as
+ a string). Returns 1 if buffered, 0 if not, -1 if error. */
+int json_ostream_write_text(struct json_ostream *stream,
+ const char *name, const char *str);
+void json_ostream_nwrite_text(struct json_ostream *stream,
+ const char *name, const char *str);
+
+#endif
#include "json-parser.new.h"
#include "json-generator.h"
+#include "json-istream.h"
+#include "json-ostream.h"
#include <stdio.h>
#include <sys/stat.h>
} T_END;
}
+/*
+ * Stream I/O
+ */
+
+struct test_sio_context;
+
+struct test_sio_processor {
+ struct test_sio_context *tctx;
+ const char *name;
+
+ struct istream *input;
+ struct ostream *output;
+ struct io *io;
+
+ struct json_node jnode;
+
+ struct json_istream *jinput;
+ struct json_ostream *joutput;
+
+ bool input_finished:1;
+};
+
+struct test_sio_context {
+ const struct json_io_test *test;
+ unsigned int scenario;
+
+ struct iostream_pump *pump_in, *pump_out;
+};
+
+static void test_json_stream_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;
+ 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 stream io [%d]", i));
+
+ for (j = 0; j < N_ELEMENTS(margins); j++) {
+ struct istream *input;
+ struct ostream *output;
+ struct json_istream *jinput;
+ struct json_ostream *joutput;
+ struct json_node jnode;
+ int pret = 0, wret = 0;
+ const char *error = NULL;
+
+ margin = margins[j];
+
+ buffer_set_used_size(outbuf, 0);
+
+ output = o_stream_create_buffer(outbuf);
+ o_stream_set_no_error_handling(output, TRUE);
+ input = test_istream_create_data(text, text_len);
+
+ jinput = json_istream_create(input, 0, &test->limits, test->flags);
+ joutput = json_ostream_create(output, 0);
+
+ o_stream_set_max_buffer_size(output, 0);
+ pret = 0; wret = 1;
+ i_zero(&jnode);
+ for (pos = 0;
+ pos <= (text_len+margin+1) && (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_istream_walk(jinput, &jnode);
+ if (pret < 0)
+ break;
+ test_assert(!json_istream_failed(jinput));
+ }
+ if (json_node_is_none(&jnode))
+ wret = 1;
+ else
+ wret = json_ostream_write_node(joutput, &jnode, TRUE);
+ if (wret == 0)
+ continue;
+ if (wret < 0)
+ break;
+ i_zero(&jnode);
+ pret = 0;
+ }
+
+ o_stream_set_max_buffer_size(output, SIZE_MAX);
+ wret = json_ostream_flush(joutput);
+ json_ostream_destroy(&joutput);
+
+ pret = json_istream_finish(&jinput, &error);
+
+ test_out_reason_quiet(
+ t_strdup_printf("read 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));
+ }
+
+ o_stream_unref(&output);
+ i_stream_unref(&input);
+ }
+
+ test_end();
+
+ } T_END;
+
+ buffer_free(&outbuf);
+}
+
+static void test_json_stream_io_input_callback(struct test_sio_processor *tproc)
+{
+ int ret;
+
+ if (!json_node_is_none(&tproc->jnode)) {
+ io_remove(&tproc->io);
+ return;
+ }
+
+ ret = json_istream_walk(tproc->jinput, &tproc->jnode);
+ test_assert(!json_istream_failed(tproc->jinput) || ret < 0);
+ if (ret < 0) {
+ const char *error = NULL;
+
+ ret = json_istream_finish(&tproc->jinput, &error);
+ i_assert(ret != 0);
+
+ test_out_reason_quiet(
+ t_strdup_printf("%u: %s: read success (async)",
+ tproc->tctx->scenario, tproc->name),
+ ret > 0, error);
+
+ if (ret < 0)
+ io_loop_stop(current_ioloop);
+ else {
+ tproc->input_finished = TRUE;
+ io_remove(&tproc->io);
+ o_stream_set_flush_pending(tproc->output, TRUE);
+ }
+ return;
+ }
+ if (ret == 0)
+ return;
+
+ o_stream_set_flush_pending(tproc->output, TRUE);
+}
+
+static int test_json_stream_io_flush_callback(struct test_sio_processor *tproc)
+{
+ int ret;
+
+ if (json_node_is_none(&tproc->jnode))
+ ret = 1;
+ else {
+ ret = json_ostream_write_node(tproc->joutput, &tproc->jnode,
+ TRUE);
+ }
+ if (ret < 0) {
+ test_assert(FALSE);
+ io_loop_stop(current_ioloop);
+ return -1;
+ }
+ if (ret == 0) {
+ io_remove(&tproc->io);
+ return 0;
+ }
+ i_zero(&tproc->jnode);
+
+ if (tproc->input_finished) {
+ ret = json_ostream_flush(tproc->joutput);
+ if (ret == 0)
+ return 0;
+ 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);
+ else {
+ io_remove(&tproc->io);
+ o_stream_close(tproc->output);
+ }
+ return ret;
+ }
+
+ if (tproc->io == NULL) {
+ tproc->io = io_add_istream(
+ tproc->input, test_json_stream_io_input_callback, tproc);
+ i_stream_set_input_pending(tproc->input, TRUE);
+ }
+ return 1;
+}
+
+static void
+test_json_stream_io_pump_in_callback(enum iostream_pump_status status,
+ struct test_sio_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_stream_io_pump_out_callback(enum iostream_pump_status status,
+ struct test_sio_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_sio_processor_init(struct test_sio_processor *tproc,
+ const struct json_io_test *test,
+ struct istream *input, struct ostream *output)
+{
+ i_zero(tproc);
+
+ tproc->output = output;
+ o_stream_set_no_error_handling(tproc->output, TRUE);
+ o_stream_set_flush_callback(tproc->output,
+ test_json_stream_io_flush_callback, tproc);
+ o_stream_uncork(tproc->output);
+
+ tproc->input = input;
+ tproc->io = io_add_istream(tproc->input,
+ test_json_stream_io_input_callback, tproc);
+
+ tproc->jinput = json_istream_create(input, 0, &test->limits,
+ test->flags);
+ tproc->joutput = json_ostream_create(output, 0);
+}
+
+static void test_sio_processor_deinit(struct test_sio_processor *tproc)
+{
+ json_ostream_destroy(&tproc->joutput);
+ json_istream_destroy(&tproc->jinput);
+}
+
+static void
+test_json_stream_io_async_run(const struct json_io_test *test,
+ unsigned int scenario)
+{
+ struct test_sio_context tctx;
+ string_t *outbuf;
+ struct test_sio_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_stream_io_pump_in_callback, &tctx);
+ iostream_pump_set_completion_callback(
+ tctx.pump_out, test_json_stream_io_pump_out_callback, &tctx);
+
+ /* Processor 1 */
+ test_sio_processor_init(&tproc1, test, pipe1_input, pipe2_output);
+ tproc1.tctx = &tctx;
+ tproc1.name = "proc_a";
+
+ /* Processor 2 */
+ test_sio_processor_init(&tproc2, test, pipe2_input, pipe3_output);
+ tproc2.tctx = &tctx;
+ tproc2.name = "proc_b";
+
+ 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_sio_processor_deinit(&tproc1);
+ test_sio_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_stream_io_async(void)
+{
+ unsigned int i, sc;
+
+ for (i = 0; i < tests_count; i++) T_BEGIN {
+ test_begin(t_strdup_printf("json stream io async [%d]", i));
+
+ for (sc = 0; sc < 4; sc++)
+ test_json_stream_io_async_run(&tests[i], sc);
+ test_end();
+ } T_END;
+}
+
+/*
+ * File I/O
+ */
+
+static void
+test_json_file_io_run(struct istream *input, struct ostream *output)
+{
+ struct json_istream *jinput;
+ struct json_ostream *joutput;
+ struct json_node jnode;
+ struct json_limits json_limits;
+ int rret, wret;
+
+ i_zero(&json_limits);
+ json_limits.max_name_size = SIZE_MAX;
+ json_limits.max_string_size = SIZE_MAX;
+ json_limits.max_nesting = UINT_MAX;
+ json_limits.max_list_items = UINT_MAX;
+
+ jinput = json_istream_create(input, 0, &json_limits, 0);
+ joutput = json_ostream_create(output, 0);
+
+ rret = 0; wret = 1;
+ i_zero(&jnode);
+ for (;;) {
+ if (wret > 0 && rret == 0) {
+ rret = json_istream_walk(jinput, &jnode);
+ if (rret < 0)
+ break;
+ test_assert(!json_istream_failed(jinput));
+ }
+ if (json_node_is_none(&jnode))
+ wret = 1;
+ else
+ wret = json_ostream_write_node(joutput, &jnode, TRUE);
+ if (wret == 0)
+ continue;
+ if (wret < 0)
+ break;
+ i_zero(&jnode);
+ rret = 0;
+ }
+ wret = json_ostream_flush(joutput);
+
+ test_out_reason("read success",
+ !json_istream_failed(jinput),
+ (!json_istream_failed(jinput) ?
+ NULL : json_istream_get_error(jinput)));
+ test_out("write success", wret > 0);
+
+ json_ostream_destroy(&joutput);
+ json_istream_destroy(&jinput);
+}
+
+static void
+test_json_file_compare(struct istream *input1, struct istream *input2)
+{
+ const unsigned char *data1, *data2;
+ size_t size1, size2, pleft;
+ off_t ret;
+
+ i_stream_seek(input1, 0);
+ i_stream_seek(input2, 0);
+
+ /* read payload */
+ while ((ret = i_stream_read_more(input1, &data1, &size1)) > 0) {
+ /* compare with file on disk */
+ pleft = size1;
+ while ((ret = i_stream_read_more(input2,
+ &data2, &size2)) > 0 &&
+ pleft > 0) {
+ size2 = (size2 > pleft ? pleft : size2);
+ if (memcmp(data1, data2, size2) != 0) {
+ i_fatal("processed data does not match "
+ "(%"PRIuUOFF_T":%"PRIuUOFF_T")",
+ input1->v_offset, input2->v_offset);
+ }
+ i_stream_skip(input2, size2);
+ pleft -= size2;
+ data1 += size2;
+ }
+ if (ret < 0 && input2->stream_errno != 0) {
+ i_fatal("failed to read result stream: %s",
+ i_stream_get_error(input2));
+ }
+ i_stream_skip(input1, size1);
+ }
+
+ i_assert(ret != 0);
+
+ (void)i_stream_read(input2);
+ if (input2->stream_errno != 0) {
+ i_fatal("failed to read input stream: %s",
+ i_stream_get_error(input1));
+ } if (i_stream_have_bytes_left(input2)) {
+ if (i_stream_read_more(input2, &data2, &size2) <= 0)
+ size2 = 0;
+ i_fatal("result stream ended prematurely "
+ "(at least %zu bytes left)", size2);
+ }
+}
+
+static void test_json_file_io(const char *file)
+{
+ struct istream *input, *oinput;
+ struct ostream *output;
+ int fd;
+
+ test_begin(t_strdup_printf("json file io [%s]", file));
+
+ fd = open(file, O_RDONLY);
+ if (fd < 0)
+ i_fatal("Failed to open: %m");
+
+ input = i_stream_create_fd_autoclose(&fd, 1024);
+ output = iostream_temp_create("/tmp/.test-json-io-", 0);
+ o_stream_set_no_error_handling(output, TRUE);
+ test_json_file_io_run(input, output);
+ i_stream_unref(&input);
+
+ if (test_has_failed()) {
+ o_stream_unref(&output);
+ return;
+ }
+
+ input = iostream_temp_finish(&output, IO_BLOCK_SIZE);
+ output = iostream_temp_create("/tmp/.test-json-io-", 0);
+ test_json_file_io_run(input, output);
+
+ oinput = iostream_temp_finish(&output, IO_BLOCK_SIZE);
+ test_json_file_compare(input, oinput);
+
+ i_stream_unref(&input);
+ i_stream_unref(&oinput);
+
+ test_end();
+}
+
int main(int argc, char *argv[])
{
int ret, c;
static void (*test_functions[])(void) = {
test_json_io,
test_json_io_async,
+ test_json_stream_io,
+ test_json_stream_io_async,
NULL
};
debug = TRUE;
break;
default:
- i_fatal("Usage: %s [-D]", argv[0]);
+ i_fatal("Usage: %s [-D] [<json-file>]", argv[0]);
}
}
argc -= optind;
argv += optind;
- if (argc > 0)
- i_fatal("Usage: %s [-D]", argv[0]);
+ if (argc > 1 )
+ i_fatal("Usage: %s [-D] [<json-file>]", argv[0]);
+ if (argc == 1) {
+ test_json_file_io(argv[0]);
+ return 0;
+ }
ret = test_run(test_functions);
--- /dev/null
+/* Copyright (c) 2017-2023 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "ostream.h"
+#include "test-common.h"
+
+#include "json-ostream.h"
+
+#include <unistd.h>
+
+static bool debug = FALSE;
+
+static void test_json_ostream_write(void)
+{
+ string_t *buffer;
+ struct ostream *output;
+ struct json_ostream *joutput;
+ unsigned int state, pos;
+ int ret;
+
+ buffer = str_new(default_pool, 256);
+ output = o_stream_create_buffer(buffer);
+ o_stream_set_no_error_handling(output, TRUE);
+
+ /* number */
+ test_begin("json ostream write - number");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_write_number(joutput, NULL, 23423);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("23423", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* false */
+ test_begin("json ostream write - false");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_write_false(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("false", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* false */
+ test_begin("json ostream write - null");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_write_null(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("null", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* true */
+ test_begin("json ostream write - true");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_write_true(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("true", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* string */
+ test_begin("json ostream write - string");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_write_string(joutput, NULL, "frop!");
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("\"frop!\"", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* <JSON-text> */
+ test_begin("json ostream write - <JSON-text>");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_write_text(joutput, NULL, "\"frop!\"");
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("\"frop!\"", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* [ ] */
+ test_begin("json ostream write - array [ ]");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_descend_array(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("[]", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* [ string ] */
+ test_begin("json ostream write - array [ string ]");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_descend_array(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_write_string(joutput, NULL, "frop");
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("[\"frop\"]", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* [ <JSON-text> ] */
+ test_begin("json ostream write - array [ <JSON-text> ]");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_descend_array(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_write_text(joutput, NULL, "[1,\"frop\",2]");
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("[[1,\"frop\",2]]", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* { } */
+ test_begin("json ostream write - object { }");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("{}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* { "frop": "friep" } */
+ test_begin("json ostream write - object { \"frop\": \"friep\" }");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_write_string(joutput, "frop", "friep");
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("{\"frop\":\"friep\"}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* { "frop": 1234 } */
+ test_begin("json ostream write - object { \"frop\": 1234 }");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_write_object_member(joutput, "frop");
+ i_assert(ret > 0);
+ ret = json_ostream_write_number(joutput, NULL, 1234);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("{\"frop\":1234}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */
+ test_begin("json ostream write - "
+ "object { \"a\": [{\"d\": 1}], \"b\": [{\"e\": 2}], "
+ "\"c\": [{\"f\": 3}] }");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_descend_array(joutput, "a");
+ i_assert(ret > 0);
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_write_number(joutput, "d", 1);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_descend_array(joutput, "b");
+ i_assert(ret > 0);
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_write_number(joutput, "e", 2);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_descend_array(joutput, "c");
+ i_assert(ret > 0);
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_write_number(joutput, "f", 3);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp(
+ "{\"a\":[{\"d\":1}],\"b\":[{\"e\":2}],\"c\":[{\"f\":3}]}",
+ str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* { "frop": <JSON-text> } */
+ test_begin("json ostream write - object { \"frop\": <JSON-text> }");
+ joutput = json_ostream_create(output, 0);
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret > 0);
+ ret = json_ostream_write_text(joutput, "frop",
+ "[false,\"friep\",true]");
+ i_assert(ret > 0);
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret > 0);
+ ret = json_ostream_flush(joutput);
+ i_assert(ret > 0);
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("{\"frop\":[false,\"friep\",true]}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* trickle[1] */
+ test_begin("json ostream write - object, trickle[1]");
+ o_stream_set_max_buffer_size(output, 0);
+ joutput = json_ostream_create(output, 0);
+ state = 0;
+ for (pos = 0; pos < 400 && state <= 17; pos++) {
+ o_stream_set_max_buffer_size(output, pos);
+ switch (state) {
+ case 0:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 1:
+ ret = json_ostream_descend_array(joutput, "aaaaaa");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 2:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 3:
+ ret = json_ostream_write_number(joutput, "dddddd", 1);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 4:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 5:
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 6:
+ ret = json_ostream_descend_array(joutput, "bbbbbb");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 7:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 8:
+ ret = json_ostream_write_number(joutput, "eeeeee", 2);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 9:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 10:
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 11:
+ ret = json_ostream_descend_array(joutput, "cccccc");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 12:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 13:
+ ret = json_ostream_write_object_member(
+ joutput, "ffffff");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 14:
+ ret = json_ostream_write_number(joutput, NULL, 3);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 15:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 16:
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 17:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ }
+ }
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":1}],"
+ "\"bbbbbb\":[{\"eeeeee\":2}],"
+ "\"cccccc\":[{\"ffffff\":3}]}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* trickle[2] */
+ test_begin("json ostream write - object, trickle[2]");
+ o_stream_set_max_buffer_size(output, 0);
+ joutput = json_ostream_create(output, 0);
+ state = 0;
+ for (pos = 0; pos < 65535 && state <= 25; pos++) {
+ o_stream_set_max_buffer_size(output, pos);
+ switch (state) {
+ case 0:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 1:
+ ret = json_ostream_descend_array(joutput, "aaaaaa");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 2:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 3:
+ ret = json_ostream_write_number(joutput, "dddddd", 1);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 4:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 5:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 6:
+ ret = json_ostream_write_number(joutput, "gggggg", 4);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 7:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 8:
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 9:
+ ret = json_ostream_descend_array(joutput, "bbbbbb");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 10:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 11:
+ ret = json_ostream_write_number(joutput, "eeeeee", 2);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 12:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 13:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 14:
+ ret = json_ostream_write_number(joutput, "hhhhhh", 5);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 15:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 16:
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 17:
+ ret = json_ostream_descend_array(joutput, "cccccc");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 18:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 19:
+ ret = json_ostream_write_number(joutput, "ffffff", 3);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 20:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 21:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 22:
+ ret = json_ostream_write_number(joutput, "iiiiii", 6);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 23:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 24:
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 25:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ }
+ }
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":1},{\"gggggg\":4}],"
+ "\"bbbbbb\":[{\"eeeeee\":2},{\"hhhhhh\":5}],"
+ "\"cccccc\":[{\"ffffff\":3},{\"iiiiii\":6}]}",
+ str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* trickle[3] */
+ test_begin("json ostream write - object, trickle[3]");
+ o_stream_set_max_buffer_size(output, 0);
+ joutput = json_ostream_create(output, 0);
+ state = 0;
+ for (pos = 0; pos < 400 && state <= 16; pos++) {
+ o_stream_set_max_buffer_size(output, pos);
+ switch (state) {
+ case 0:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 1:
+ ret = json_ostream_descend_array(joutput, "aaaaaa");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 2:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 3:
+ ret = json_ostream_write_text(joutput, "dddddd",
+ "1234567");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 4:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 5:
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 6:
+ ret = json_ostream_descend_array(joutput, "bbbbbb");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 7:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 8:
+ ret = json_ostream_write_text(joutput, "eeeeee",
+ "[1,2,3,4,5,6,7]");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 9:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 10:
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 11:
+ ret = json_ostream_descend_array(joutput, "cccccc");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 12:
+ ret = json_ostream_descend_object(joutput, NULL);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 13:
+ ret = json_ostream_write_text(joutput, "ffffff",
+ "\"1234567\"");
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 14:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 15:
+ ret = json_ostream_ascend_array(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ case 16:
+ ret = json_ostream_ascend_object(joutput);
+ i_assert(ret >= 0);
+ if (ret == 0) break;
+ state++;
+ continue;
+ }
+ }
+ json_ostream_unref(&joutput);
+ test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":1234567}],"
+ "\"bbbbbb\":[{\"eeeeee\":[1,2,3,4,5,6,7]}],"
+ "\"cccccc\":[{\"ffffff\":\"1234567\"}]}",
+ str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ o_stream_destroy(&output);
+ str_free(&buffer);
+}
+
+static void test_json_ostream_nwrite(void)
+{
+ string_t *buffer;
+ struct ostream *output;
+ struct json_ostream *joutput;
+
+ buffer = str_new(default_pool, 256);
+ output = o_stream_create_buffer(buffer);
+ o_stream_set_no_error_handling(output, TRUE);
+
+ /* number */
+ test_begin("json ostream nwrite - number");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_nwrite_number(joutput, NULL, 23423);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("23423", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* false */
+ test_begin("json ostream nwrite - false");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_nwrite_false(joutput, NULL);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("false", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* false */
+ test_begin("json ostream nwrite - null");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_nwrite_null(joutput, NULL);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("null", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* true */
+ test_begin("json ostream nwrite - true");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_nwrite_true(joutput, NULL);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("true", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* string */
+ test_begin("json ostream nwrite - string");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_nwrite_string(joutput, NULL, "frop!");
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("\"frop!\"", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* formatted string */
+ test_begin("json ostream nwrite - formatted string");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_nwritef_string(joutput, NULL, "%u", 12345);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("\"12345\"", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* <JSON-text> */
+ test_begin("json ostream nwrite - <JSON-text>");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_nwrite_text(joutput, NULL, "\"frop!\"");
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("\"frop!\"", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* [ ] */
+ test_begin("json ostream nwrite - array [ ]");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_ndescend_array(joutput, NULL);
+ json_ostream_nascend_array(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("[]", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* [ string ] */
+ test_begin("json ostream nwrite - array [ string ]");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_ndescend_array(joutput, NULL);
+ json_ostream_nwrite_string(joutput, NULL, "frop");
+ json_ostream_nascend_array(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("[\"frop\"]", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* [ <JSON-text> ] */
+ test_begin("json ostream nwrite - array [ <JSON-text> ]");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_ndescend_array(joutput, NULL);
+ json_ostream_nwrite_text(joutput, NULL, "[1,\"frop\",2]");
+ json_ostream_nascend_array(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("[[1,\"frop\",2]]", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* { } */
+ test_begin("json ostream nwrite - object { }");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_ndescend_object(joutput, NULL);
+ json_ostream_nascend_object(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("{}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* { "frop": "friep" } */
+ test_begin("json ostream nwrite - object { \"frop\": \"friep\" }");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_ndescend_object(joutput, NULL);
+ json_ostream_nwrite_string(joutput, "frop", "friep");
+ json_ostream_nascend_object(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("{\"frop\":\"friep\"}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* complex example */
+ test_begin("json ostream nwrite - complex example");
+ joutput = json_ostream_create_str(buffer, 0);
+ json_ostream_ndescend_object(joutput, NULL);
+ json_ostream_nwrite_string(joutput, "user", "testuser3");
+ json_ostream_nwrite_string(joutput, "event", "messageNew");
+ json_ostream_nwrite_string(joutput, "folder", "INBOX");
+ json_ostream_nwrite_number(joutput, "imap-uidvalidity", 1588805816);
+ json_ostream_nwrite_number(joutput, "imap-uid", 1);
+ json_ostream_nwrite_string(
+ joutput, "from",
+ "Source =?utf8?q?p=C3=A4_=3Dutf8=3Fq=3Fencoding=3F=3D?= "
+ "<from@example.com>");
+ json_ostream_nwrite_string(
+ joutput, "subject",
+ "Stuff =?utf8?q?p=C3=A4_=3Dutf8=3Fq=3Fencoding=3F=3D?=");
+ json_ostream_nwrite_string(joutput, "snippet",
+ "P\xc3\xa4iv\xc3\xa4\xc3\xa4.");
+ json_ostream_nwrite_number(joutput, "unseen", 1);
+ json_ostream_nascend_object(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp(
+ "{\"user\":\"testuser3\","
+ "\"event\":\"messageNew\","
+ "\"folder\":\"INBOX\","
+ "\"imap-uidvalidity\":1588805816,"
+ "\"imap-uid\":1,"
+ "\"from\":\"Source =?utf8?q?p=C3=A4_=3Dutf8=3Fq=3Fencoding=3F=3D?= <from@example.com>\","
+ "\"subject\":\"Stuff =?utf8?q?p=C3=A4_=3Dutf8=3Fq=3Fencoding=3F=3D?=\","
+ "\"snippet\":\"P\xc3\xa4iv\xc3\xa4\xc3\xa4.\","
+ "\"unseen\":1}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* concatenated string */
+ test_begin("json ostream nwrite - concatenated string");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_nopen_string(joutput, NULL);
+ json_ostream_nwrite_string(joutput, NULL, "friep!");
+ json_ostream_nwrite_string(joutput, NULL, "frop!");
+ json_ostream_nwrite_string(joutput, NULL, "frml!");
+ json_ostream_nclose_string(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("\"friep!frop!frml!\"", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* array [ concatenated string ] */
+ test_begin("json ostream nwrite - array [ concatenated string ]");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_ndescend_array(joutput, NULL);
+ json_ostream_nopen_string(joutput, NULL);
+ json_ostream_nwrite_string(joutput, NULL, "friep!");
+ json_ostream_nwrite_string(joutput, NULL, "frop!");
+ json_ostream_nwrite_string(joutput, NULL, "frml!");
+ json_ostream_nclose_string(joutput);
+ json_ostream_nascend_array(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("[\"friep!frop!frml!\"]", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* object { concatenated string } */
+ test_begin("json ostream nwrite - object { concatenated string }");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_ndescend_object(joutput, NULL);
+ json_ostream_nopen_string(joutput, "foo");
+ json_ostream_nwrite_string(joutput, NULL, "friep!");
+ json_ostream_nwrite_string(joutput, NULL, "frop!");
+ json_ostream_nwrite_string(joutput, NULL, "frml!");
+ json_ostream_nclose_string(joutput);
+ json_ostream_nascend_object(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("{\"foo\":\"friep!frop!frml!\"}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* array [ complex concatenated string ] */
+ test_begin("json ostream nwrite - "
+ "array [ complex concatenated string ]");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_ndescend_array(joutput, NULL);
+ json_ostream_nopen_string(joutput, NULL);
+ json_ostream_nwrite_string(joutput, NULL, "friep!");
+ json_ostream_nwrite_string(joutput, NULL, "frop!");
+ json_ostream_nwrite_string(joutput, NULL, "frml!");
+ json_ostream_nclose_string(joutput);
+ json_ostream_nwrite_string(joutput, NULL, "friep!");
+ json_ostream_nwrite_string(joutput, NULL, "frop!");
+ json_ostream_nwrite_string(joutput, NULL, "frml!");
+ json_ostream_nopen_string(joutput, NULL);
+ json_ostream_nwrite_string(joutput, NULL, "frml!");
+ json_ostream_nwrite_string(joutput, NULL, "frop!");
+ json_ostream_nwrite_string(joutput, NULL, "friep!");
+ json_ostream_nclose_string(joutput);
+ json_ostream_nascend_array(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("[\"friep!frop!frml!\","
+ "\"friep!\",\"frop!\",\"frml!\","
+ "\"frml!frop!friep!\"]", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ /* object [ complex concatenated string ] */
+ test_begin("json ostream nwrite - "
+ "object [ complex concatenated string ]");
+ joutput = json_ostream_create(output, 0);
+ json_ostream_ndescend_object(joutput, NULL);
+ json_ostream_nopen_string(joutput, "a");
+ json_ostream_nwrite_string(joutput, NULL, "friep!");
+ json_ostream_nwrite_string(joutput, NULL, "frop!");
+ json_ostream_nwrite_string(joutput, NULL, "frml!");
+ json_ostream_nclose_string(joutput);
+ json_ostream_nwrite_string(joutput, "b", "friep!");
+ json_ostream_nwrite_string(joutput, "c", "frop!");
+ json_ostream_nwrite_string(joutput, "d", "frml!");
+ json_ostream_nopen_string(joutput, "e");
+ json_ostream_nwrite_string(joutput, NULL, "frml!");
+ json_ostream_nwrite_string(joutput, NULL, "frop!");
+ json_ostream_nwrite_string(joutput, NULL, "friep!");
+ json_ostream_nclose_string(joutput);
+ json_ostream_nascend_object(joutput);
+ json_ostream_nfinish_destroy(&joutput);
+ test_assert_strcmp("{\"a\":\"friep!frop!frml!\","
+ "\"b\":\"friep!\",\"c\":\"frop!\",\"d\":\"frml!\","
+ "\"e\":\"frml!frop!friep!\"}", str_c(buffer));
+ test_end();
+ str_truncate(buffer, 0);
+ output->offset = 0;
+
+ o_stream_destroy(&output);
+ str_free(&buffer);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+
+ static void (*test_functions[])(void) = {
+ test_json_ostream_write,
+ test_json_ostream_nwrite,
+ 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);
+}