]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
doveadm: Use the new lib-json
authorStephan Bosch <stephan.bosch@open-xchange.com>
Sun, 28 Jul 2019 09:20:38 +0000 (11:20 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Sat, 18 Nov 2023 18:58:04 +0000 (18:58 +0000)
src/doveadm/Makefile.am
src/doveadm/client-connection-http.c
src/doveadm/doveadm-auth-server.c
src/doveadm/doveadm-print-json.c

index eafecc5f37aa97ee325cc209b6428ca991af844d..294b8e539a3c2f5c234ada062ab8c9e32c78831c 100644 (file)
@@ -10,6 +10,7 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib \
        -I$(top_srcdir)/src/lib-test \
        -I$(top_srcdir)/src/lib-settings \
+       -I$(top_srcdir)/src/lib-json \
        -I$(top_srcdir)/src/lib-auth \
        -I$(top_srcdir)/src/lib-auth-client \
        -I$(top_srcdir)/src/lib-compression \
index 5bd914643eaa9713943fd24ff69385d74778b565..4f89a577f87518fd3dfebb8a9da6769bea58a5de 100644 (file)
 #include "doveadm-print.h"
 #include "doveadm-settings.h"
 #include "client-connection-private.h"
-#include "json-parser.h"
+#include "json-istream.h"
+#include "json-ostream.h"
 
 #include <unistd.h>
 #include <ctype.h>
 
 enum client_request_parse_state {
-       CLIENT_REQUEST_PARSE_INIT,
-       CLIENT_REQUEST_PARSE_CMD,
+       CLIENT_REQUEST_PARSE_CMD = 0,
        CLIENT_REQUEST_PARSE_CMD_NAME,
        CLIENT_REQUEST_PARSE_CMD_PARAMS,
        CLIENT_REQUEST_PARSE_CMD_PARAM_KEY,
@@ -56,7 +56,8 @@ struct client_request_http {
        struct istream *input;
        struct ostream *output;
 
-       struct json_parser *json_parser;
+       struct json_istream *json_input;
+       struct json_ostream *json_output;
 
        const struct doveadm_cmd_ver2 *cmd;
        struct doveadm_cmd_param *cmd_param;
@@ -64,7 +65,6 @@ struct client_request_http {
        ARRAY_TYPE(doveadm_cmd_param_arr_t) pargv;
        int method_err;
        char *method_id;
-       bool first_row;
        bool value_is_array;
 
        enum client_request_parse_state parse_state;
@@ -127,44 +127,39 @@ static struct doveadm_http_server_mount doveadm_http_server_mounts[] = {
 static void doveadm_http_server_json_error(void *context, const char *error)
 {
        struct client_request_http *req = context;
-       struct ostream *output = req->output;
-       string_t *escaped;
-
-       escaped = str_new(req->pool, 10);
-
-       o_stream_nsend_str(output, "[\"error\",{\"type\":\"");
-       json_append_escaped(escaped, error);
-       o_stream_nsend_str(output, str_c(escaped));
-       o_stream_nsend_str(output, "\", \"exitCode\":");
-        str_truncate(escaped,0);
-       str_printfa(escaped, "%d", doveadm_exit_code);
-       o_stream_nsend_str(output, str_c(escaped));
-       o_stream_nsend_str(output, "},\"");
-       str_truncate(escaped,0);
-       if (req->method_id != NULL) {
-               json_append_escaped(escaped, req->method_id);
-               o_stream_nsend_str(output, str_c(escaped));
-       }
-       o_stream_nsend_str(output, "\"]");
+       struct json_ostream *json_output = req->json_output;
+
+       json_ostream_ndescend_array(json_output, NULL);
+
+       json_ostream_nwrite_string(json_output, NULL, "error");
+
+       json_ostream_ndescend_object(json_output, NULL);
+       json_ostream_nwrite_string(json_output, "type", error);
+       json_ostream_nwrite_number(json_output, "exitCode",
+                                  doveadm_exit_code);
+       json_ostream_nascend_object(json_output);
+
+       if (req->method_id != NULL)
+               json_ostream_nwrite_string(json_output, NULL, req->method_id);
+
+       json_ostream_nascend_array(json_output);
 }
 
 static void
 doveadm_http_server_json_success(void *context, struct istream *result)
 {
        struct client_request_http *req = context;
-       struct ostream *output = req->output;
-       string_t *escaped;
+       struct json_ostream *json_output = req->json_output;
 
-       escaped = str_new(req->pool, 10);
+       json_ostream_ndescend_array(json_output, NULL);
+       json_ostream_nwrite_string(json_output, NULL, "doveadmResponse");
 
-       o_stream_nsend_str(output, "[\"doveadmResponse\",");
-       o_stream_nsend_istream(output, result);
-       o_stream_nsend_str(output, ",\"");
-       if (req->method_id != NULL) {
-               json_append_escaped(escaped, req->method_id);
-               o_stream_nsend_str(output, str_c(escaped));
-       }
-       o_stream_nsend_str(output, "\"]");
+       json_ostream_nwrite_text_stream(json_output, NULL, result);
+
+       if (req->method_id != NULL)
+               json_ostream_nwrite_string(json_output, NULL, req->method_id);
+
+       json_ostream_nascend_array(json_output);
 }
 
 static void
@@ -239,17 +234,13 @@ doveadm_http_server_command_execute(struct client_request_http *req)
                doveadm_print_deinit();
        if (o_stream_finish(doveadm_print_ostream) < 0) {
                e_info(cctx->event, "Error writing output in command %s: %s",
-                      req->cmd->name, o_stream_get_error(req->output));
+                      req->cmd->name,
+                      o_stream_get_error(doveadm_print_ostream));
                doveadm_exit_code = EX_TEMPFAIL;
        }
 
        is = iostream_temp_finish(&doveadm_print_ostream, 4096);
 
-       if (req->first_row == TRUE)
-               req->first_row = FALSE;
-       else
-               o_stream_nsend_str(req->output,",");
-
        if (cctx->referral != NULL) {
                e_error(cctx->event,
                        "Command requested referral: %s", cctx->referral);
@@ -267,47 +258,16 @@ doveadm_http_server_command_execute(struct client_request_http *req)
        doveadm_cmd_context_unref(&cctx);
 }
 
-static int request_json_parse_init(struct client_request_http *req)
-{
-       struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
-       const char *value;
-       int ret;
-
-       ret = json_parse_next(req->json_parser, &type, &value);
-       if (ret <= 0)
-               return ret;
-       if (type != JSON_TYPE_ARRAY) {
-               /* request must be a JSON array */
-               http_server_request_fail_text(http_sreq,
-                       400, "Bad Request",
-                       "Request must be a JSON array");
-               return -1;
-       }
-       req->first_row = TRUE;
-       o_stream_nsend_str(req->output,"[");
-
-       /* next: parse the next command */
-       req->parse_state = CLIENT_REQUEST_PARSE_CMD;
-       return 1;
-}
-
 static int request_json_parse_cmd(struct client_request_http *req)
 {
        struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
-       const char *value;
+       struct json_node jnode;
        int ret;
 
-       ret = json_parse_next(req->json_parser, &type, &value);
+       ret = json_istream_descend(req->json_input, &jnode);
        if (ret <= 0)
                return ret;
-       if (type == JSON_TYPE_ARRAY_END) {
-               /* end of command list */
-               req->parse_state = CLIENT_REQUEST_PARSE_DONE;
-               return 1;
-       }
-       if (type != JSON_TYPE_ARRAY) {
+       if (!json_node_is_array(&jnode)) {
                /* command must be an array */
                http_server_request_fail_text(http_sreq,
                        400, "Bad Request",
@@ -327,17 +287,17 @@ static int request_json_parse_cmd(struct client_request_http *req)
 static int request_json_parse_cmd_name(struct client_request_http *req)
 {
        struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
-       const char *value;
+       struct json_node jnode;
        const struct doveadm_cmd_ver2 *ccmd;
        struct doveadm_cmd_param *param;
+       const char *cmd_name;
        bool found;
        int pargc, ret;
 
-       ret = json_parse_next(req->json_parser, &type, &value);
+       ret = json_istream_read_next(req->json_input, &jnode);
        if (ret <= 0)
                return ret;
-       if (type != JSON_TYPE_STRING) {
+       if (!json_node_is_string(&jnode)) {
                /* command name must be a string */
                http_server_request_fail_text(http_sreq,
                        400, "Bad Request",
@@ -347,8 +307,9 @@ static int request_json_parse_cmd_name(struct client_request_http *req)
 
        /* see if we can find it */
        found = FALSE;
+       cmd_name = json_node_get_str(&jnode);
        array_foreach(&doveadm_cmds_ver2, ccmd) {
-               if (i_strccdascmp(ccmd->name, value) == 0) {
+               if (i_strccdascmp(ccmd->name, cmd_name) == 0) {
                        req->cmd = ccmd;
                        found = TRUE;
                        break;
@@ -356,7 +317,7 @@ static int request_json_parse_cmd_name(struct client_request_http *req)
        }
        if (!found) {
                /* command not found; skip to the command ID */
-               json_parse_skip_next(req->json_parser);
+               json_istream_ignore(req->json_input, 1);
                req->method_err = 404;
                req->parse_state = CLIENT_REQUEST_PARSE_CMD_ID;
                return 1;
@@ -377,19 +338,13 @@ static int request_json_parse_cmd_name(struct client_request_http *req)
 static int request_json_parse_cmd_params(struct client_request_http *req)
 {
        struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
-       const char *value;
+       struct json_node jnode;
        int ret;
 
-       ret = json_parse_next(req->json_parser, &type, &value);
+       ret = json_istream_descend(req->json_input, &jnode);
        if (ret <= 0)
                return ret;
-       if (type == JSON_TYPE_OBJECT_END) {
-               /* empty command parameters object; parse command ID next */
-               req->parse_state = CLIENT_REQUEST_PARSE_CMD_ID;
-               return 1;
-       }
-       if (type != JSON_TYPE_OBJECT) {
+       if (!json_node_is_object(&jnode)) {
                /* parameters must be contained in an object */
                http_server_request_fail_text(http_sreq,
                        400, "Bad Request",
@@ -405,25 +360,24 @@ static int request_json_parse_cmd_params(struct client_request_http *req)
 static int request_json_parse_param_key(struct client_request_http *req)
 {
        struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
-       const char *value;
        struct doveadm_cmd_param *par;
+       const char *name;
        bool found;
        int ret;
 
-       ret = json_parse_next(req->json_parser, &type, &value);
+       ret = json_istream_read_object_member(req->json_input, &name);
        if (ret <= 0)
                return ret;
-       if (type == JSON_TYPE_OBJECT_END) {
+       if (name == NULL) {
                /* end of parameters; parse command ID next */
+               json_istream_ascend(req->json_input);
                req->parse_state = CLIENT_REQUEST_PARSE_CMD_ID;
                return 1;
        }
-       i_assert(type == JSON_TYPE_OBJECT_KEY);
        /* find the parameter */
        found = FALSE;
        array_foreach_modifiable(&req->pargv, par) {
-               if (i_strccdascmp(par->name, value) == 0) {
+               if (i_strccdascmp(par->name, name) == 0) {
                        req->cmd_param = par;
                        found = TRUE;
                        break;
@@ -439,13 +393,13 @@ static int request_json_parse_param_key(struct client_request_http *req)
        }
        /* skip remaining parameters if error has already occurred */
        if (!found || req->method_err != 0) {
-               json_parse_skip_next(req->json_parser);
+               json_istream_ascend(req->json_input);
                req->method_err = 400;
-               req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY;
+               req->parse_state = CLIENT_REQUEST_PARSE_CMD_ID;
                return 1;
        }
 
-       /* next: parse parameter value */
+       /* next: continue with the value */
        req->value_is_array = FALSE;
        req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_VALUE;
        return 1;
@@ -454,47 +408,51 @@ static int request_json_parse_param_key(struct client_request_http *req)
 static int request_json_parse_param_value(struct client_request_http *req)
 {
        struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
+       struct json_node jnode;
        const char *value;
        int ret;
 
        if (req->cmd_param->type == CMD_PARAM_ISTREAM) {
-               struct istream* is[2] = {0};
-
                /* read the value as a stream */
-               ret = json_parse_next_stream(req->json_parser, &is[0]);
+               ret = json_istream_read_stream(req->json_input, 0,
+                                              IO_BLOCK_SIZE, "/tmp/doveadm.",
+                                              &jnode);
                if (ret <= 0)
                        return ret;
+               if (!json_node_is_string(&jnode)) {
+                       http_server_request_fail_text(http_sreq,
+                               400, "Bad Request",
+                               "Parameter `%s' must be a string",
+                               req->cmd_param->name);
+                       return -1;
+               }
 
-               req->cmd_param->value.v_istream =
-                       i_stream_create_seekable_path(is,
-                               IO_BLOCK_SIZE, "/tmp/doveadm.");
-               i_stream_unref(&is[0]);
+               i_assert(jnode.value.content_type == JSON_CONTENT_TYPE_STREAM);
+               req->cmd_param->value.v_istream = jnode.value.content.stream;
+               i_stream_ref(req->cmd_param->value.v_istream);
                req->cmd_param->value_set = TRUE;
 
-               /* read the seekable stream to its end so that the underlying
-                  json istream is read to its conclusion. */
-               req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_ISTREAM;
+               /* next: continue with the next parameter */
+               json_istream_skip(req->json_input);
+               req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY;
                return 1;
        }
 
-       ret = json_parse_next(req->json_parser, &type, &value);
+       ret = json_istream_descend(req->json_input, &jnode);
        if (ret <= 0)
                return ret;
        if (req->cmd_param->type == CMD_PARAM_ARRAY) {
-               const char *tmp;
-
                /* expects either a singular value or an array of values */
                p_array_init(&req->cmd_param->value.v_array, req->pool, 1);
                req->cmd_param->value_set = TRUE;
-               if (type == JSON_TYPE_ARRAY) {
+               if (json_node_is_array(&jnode)) {
                        /* start of array */
                        req->value_is_array = TRUE;
                        req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_ARRAY;
                        return 1;
                }
                /* singular value */
-               if (type != JSON_TYPE_STRING) {
+               if (!json_node_is_string(&jnode)) {
                        /* FIXME: should handle other than string too */
                        http_server_request_fail_text(http_sreq,
                                400, "Bad Request",
@@ -502,8 +460,8 @@ static int request_json_parse_param_value(struct client_request_http *req)
                                req->cmd_param->name);
                        return -1;
                }
-               tmp = p_strdup(req->pool, value);
-               array_push_back(&req->cmd_param->value.v_array, &tmp);
+               value = p_strdup(req->pool, json_node_get_str(&jnode));
+               array_push_back(&req->cmd_param->value.v_array, &value);
 
                /* next: continue with the next parameter */
                req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY;
@@ -511,6 +469,7 @@ static int request_json_parse_param_value(struct client_request_http *req)
        }
 
        /* expects just a value */
+       value = json_node_get_str(&jnode);
        req->cmd_param->value_set = TRUE;
        switch(req->cmd_param->type) {
        case CMD_PARAM_BOOL:
@@ -549,19 +508,20 @@ static int request_json_parse_param_value(struct client_request_http *req)
 static int request_json_parse_param_array(struct client_request_http *req)
 {
        struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
-       const char *value;
+       struct json_node jnode;
+       const char *tmp;
        int ret;
 
-       ret = json_parse_next(req->json_parser, &type, &value);
+       ret = json_istream_read_next(req->json_input, &jnode);
        if (ret <= 0)
                return ret;
-       if (type == JSON_TYPE_ARRAY_END) {
+       if (json_node_is_array_end(&jnode)) {
                /* end of array: continue with next parameter */
+               json_istream_ascend(req->json_input);
                req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY;
                return 1;
        }
-       if (type != JSON_TYPE_STRING) {
+       if (!json_node_is_string(&jnode)) {
                /* array items must be string */
                http_server_request_fail_text(http_sreq,
                        400, "Bad Request",
@@ -571,57 +531,23 @@ static int request_json_parse_param_array(struct client_request_http *req)
        }
 
        /* record entry */
-       value = p_strdup(req->pool, value);
-       array_push_back(&req->cmd_param->value.v_array, &value);
+       tmp = p_strdup(req->pool, json_node_get_str(&jnode));
+       array_push_back(&req->cmd_param->value.v_array, &tmp);
 
        /* next: continue with the next array item */
        return 1;
 }
 
-static int request_json_parse_param_istream(struct client_request_http *req)
-{
-       struct http_server_request *http_sreq = req->http_request;
-       struct istream *v_input = req->cmd_param->value.v_istream;
-       const unsigned char *data;
-       size_t size;
-
-       while (i_stream_read_more(v_input, &data, &size) > 0)
-               i_stream_skip(v_input, size);
-       if (!v_input->eof) {
-               /* more to read */
-               return 0;
-       }
-
-       if (v_input->stream_errno != 0) {
-               e_error(req->conn->conn.event,
-                       "read(%s) failed: %s",
-                       i_stream_get_name(v_input),
-                       i_stream_get_error(v_input));
-               req->method_err = 400;
-               if (req->input->stream_errno == 0) {
-                       http_server_request_fail_text(http_sreq,
-                               400, "Bad Request",
-                               "Failed to read command parameter data");
-               }
-               return -1;
-       }
-
-       /* next: continue with the next parameter */
-       req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY;
-       return 1;
-}
-
 static int request_json_parse_cmd_id(struct client_request_http *req)
 {
        struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
-       const char *value;
+       struct json_node jnode;
        int ret;
 
-       ret = json_parse_next(req->json_parser, &type, &value);
+       ret = json_istream_read_next(req->json_input, &jnode);
        if (ret <= 0)
                return ret;
-       if (type != JSON_TYPE_STRING) {
+       if (!json_node_is_string(&jnode)) {
                /* command ID must be a string */
                http_server_request_fail_text(http_sreq,
                        400, "Bad Request",
@@ -630,7 +556,7 @@ static int request_json_parse_cmd_id(struct client_request_http *req)
        }
 
        /* next: parse end of command */
-       req->method_id = p_strdup(req->pool, value);
+       req->method_id = p_strdup(req->pool, json_node_get_str(&jnode));
        req->parse_state = CLIENT_REQUEST_PARSE_CMD_DONE;
        return 1;
 }
@@ -638,20 +564,20 @@ static int request_json_parse_cmd_id(struct client_request_http *req)
 static int request_json_parse_cmd_done(struct client_request_http *req)
 {
        struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
-       const char *value;
+       struct json_node jnode;
        int ret;
 
-       ret = json_parse_next(req->json_parser, &type, &value);
+       ret = json_istream_read_next(req->json_input, &jnode);
        if (ret <= 0)
                return ret;
-       if (type != JSON_TYPE_ARRAY_END) {
+       if (!json_node_is_array_end(&jnode)) {
                /* command array must end here */
                http_server_request_fail_text(http_sreq,
                        400, "Bad Request",
                        "Unexpected JSON element at end of command");
                return -1;
        }
+       json_istream_ascend(req->json_input);
 
        /* execute command */
        doveadm_http_server_command_execute(req);
@@ -664,11 +590,10 @@ static int request_json_parse_cmd_done(struct client_request_http *req)
 static int request_json_parse_done(struct client_request_http *req)
 {
        struct http_server_request *http_sreq = req->http_request;
-       enum json_type type;
-       const char *value;
+       struct json_node jnode;
        int ret;
 
-       ret = json_parse_next(req->json_parser, &type, &value);
+       ret = json_istream_read_next(req->json_input, &jnode);
        if (ret <= 0)
                return ret;
        /* only gets here when there is spurious additional JSON */
@@ -682,9 +607,6 @@ static int doveadm_http_server_json_parse_v1(struct client_request_http *req)
 {
        /* parser state machine */
        switch (req->parse_state) {
-       /* command list: '[' */
-       case CLIENT_REQUEST_PARSE_INIT:
-               return request_json_parse_init(req);
        /* command begin: '[' */
        case CLIENT_REQUEST_PARSE_CMD:
                return request_json_parse_cmd(req);
@@ -694,18 +616,15 @@ static int doveadm_http_server_json_parse_v1(struct client_request_http *req)
        /* command parameters: '{' */
        case CLIENT_REQUEST_PARSE_CMD_PARAMS:
                return request_json_parse_cmd_params(req);
-       /* parameter key: string */
+       /* parameter key */
        case CLIENT_REQUEST_PARSE_CMD_PARAM_KEY:
                return request_json_parse_param_key(req);
-       /* parameter value */
+       /* parameter value: string */
        case CLIENT_REQUEST_PARSE_CMD_PARAM_VALUE:
                return request_json_parse_param_value(req);
        /* parameter array value */
        case CLIENT_REQUEST_PARSE_CMD_PARAM_ARRAY:
                return request_json_parse_param_array(req);
-       /* parameter istream value */
-       case CLIENT_REQUEST_PARSE_CMD_PARAM_ISTREAM:
-               return request_json_parse_param_istream(req);
        /* command ID: string */
        case CLIENT_REQUEST_PARSE_CMD_ID:
                return request_json_parse_cmd_id(req);
@@ -721,6 +640,27 @@ static int doveadm_http_server_json_parse_v1(struct client_request_http *req)
        i_unreached();
 }
 
+static bool
+doveadm_http_server_finish_json_output(struct client_request_http *req,
+                                      struct json_ostream **_json_output)
+{
+       struct http_server_request *http_sreq = req->http_request;
+       struct json_ostream *json_output = *_json_output;
+       bool result = TRUE;
+
+       if (json_ostream_nfinish(json_output) < 0) {
+               e_error(req->conn->conn.event,
+                       "error writing JSON output: %s",
+                       json_ostream_get_error(json_output));
+               http_server_request_fail(http_sreq,
+                                        500, "Internal server error");
+               result = FALSE;
+       }
+       json_ostream_destroy(_json_output);
+
+       return result;
+}
+
 static void
 doveadm_http_server_read_request_v1(struct client_request_http *req)
 {
@@ -728,15 +668,22 @@ doveadm_http_server_read_request_v1(struct client_request_http *req)
        const char *error;
        int ret;
 
-       if (req->json_parser == NULL) {
-               req->json_parser = json_parser_init_flags(
-                       req->input, JSON_PARSER_NO_ROOT_OBJECT);
+       if (req->json_input == NULL) {
+               req->json_input = json_istream_create_array(
+                       req->input, NULL, JSON_PARSER_FLAG_NUMBERS_AS_STRING);
+       }
+
+       if (req->json_output == NULL) {
+               req->json_output = json_ostream_create(req->output, 0);
+               json_ostream_set_no_error_handling(req->json_output, TRUE);
+               json_ostream_ndescend_array(req->json_output, NULL);
        }
 
        while ((ret = doveadm_http_server_json_parse_v1(req)) > 0);
 
        if (http_server_request_get_response(http_sreq) != NULL) {
                /* already responded */
+               json_istream_destroy(&req->json_input);
                io_remove(&req->io);
                i_stream_destroy(&req->input);
                return;
@@ -757,15 +704,18 @@ doveadm_http_server_read_request_v1(struct client_request_http *req)
                return;
        }
 
-       if (json_parser_deinit(&req->json_parser, &error) != 0) {
+       ret = json_istream_finish(&req->json_input, &error);
+       i_assert(ret != 0);
+       if (ret < 0) {
                http_server_request_fail_text(http_sreq,
                        400, "Bad Request",
                        "JSON parse error: %s", error);
                return;
        }
 
-       i_stream_destroy(&req->input);
-       o_stream_nsend_str(req->output,"]");
+       json_ostream_nascend_array(req->json_output);
+       if (!doveadm_http_server_finish_json_output(req, &req->json_output))
+               return;
 
        doveadm_http_server_send_response(req);
 }
@@ -801,71 +751,68 @@ doveadm_http_server_camelcase_value(string_t *tmp, const char *value)
 
 static void doveadm_http_server_send_api_v1(struct client_request_http *req)
 {
-       struct ostream *output = req->output;
+       struct json_ostream *json_output;
        const struct doveadm_cmd_ver2 *cmd;
        const struct doveadm_cmd_param *par;
        unsigned int i, k;
-       string_t *tmp, *cctmp;
-       bool sent, first_cmd = TRUE;
+       string_t *cctmp;
 
-       tmp = str_new(req->pool, 8);
        cctmp = t_str_new(64);
 
-       o_stream_nsend_str(output,"[\n");
+       json_output = json_ostream_create(req->output, 0);
+       json_ostream_set_no_error_handling(json_output, TRUE);
+       json_ostream_ndescend_array(json_output, NULL);
+
        for (i = 0; i < array_count(&doveadm_cmds_ver2); i++) {
                cmd = array_idx(&doveadm_cmds_ver2, i);
                if ((cmd->flags & CMD_FLAG_HIDDEN) != 0)
                        continue;
-               if (first_cmd)
-                       first_cmd = FALSE;
-               else
-                       o_stream_nsend_str(output, ",\n");
-               o_stream_nsend_str(output, "\t{\"command\":\"");
-               json_append_escaped(tmp,
+
+               json_ostream_ndescend_object(json_output, NULL);
+               json_ostream_nwrite_string(
+                       json_output, "command",
                        doveadm_http_server_camelcase_value(cctmp, cmd->name));
-               o_stream_nsend_str(output, str_c(tmp));
-               o_stream_nsend_str(output, "\", \"parameters\":[");
 
-               sent = FALSE;
+               json_ostream_ndescend_array(json_output, "parameters");
+
                for (k = 0; cmd->parameters[k].name != NULL; k++) {
-                       str_truncate(tmp, 0);
                        par = &(cmd->parameters[k]);
                        if ((par->flags & CMD_PARAM_FLAG_DO_NOT_EXPOSE) != 0)
                                continue;
-                       if (sent)
-                               o_stream_nsend_str(output, ",\n");
-                       else
-                               o_stream_nsend_str(output, "\n");
-                       sent = TRUE;
-                       o_stream_nsend_str(output, "\t\t{\"name\":\"");
-                       json_append_escaped(tmp,
+                       json_ostream_ndescend_object(json_output, NULL);
+                       json_ostream_nwrite_string(
+                               json_output, "name",
                                doveadm_http_server_camelcase_value(cctmp,
                                                                    par->name));
-                       o_stream_nsend_str(output, str_c(tmp));
-                       o_stream_nsend_str(output, "\",\"type\":\"");
                        switch(par->type) {
                        case CMD_PARAM_BOOL:
-                               o_stream_nsend_str(output, "boolean");
+                               json_ostream_nwrite_string(json_output,
+                                                          "type", "boolean");
                                break;
                        case CMD_PARAM_INT64:
-                               o_stream_nsend_str(output, "integer");
+                               json_ostream_nwrite_string(json_output,
+                                                          "type", "integer");
                                break;
                        case CMD_PARAM_ARRAY:
-                               o_stream_nsend_str(output, "array");
+                               json_ostream_nwrite_string(json_output,
+                                                          "type", "array");
                                break;
                        case CMD_PARAM_IP:
                        case CMD_PARAM_ISTREAM:
                        case CMD_PARAM_STR:
-                               o_stream_nsend_str(output, "string");
+                               json_ostream_nwrite_string(json_output,
+                                                          "type", "string");
                        }
-                       o_stream_nsend_str(output, "\"}");
+                       json_ostream_nascend_object(json_output);
                }
-               if (k > 0)
-                       o_stream_nsend_str(output,"\n\t");
-               o_stream_nsend_str(output,"]}");
-               str_truncate(tmp, 0);
+               json_ostream_nascend_array(json_output);
+               json_ostream_nascend_object(json_output);
        }
-       o_stream_nsend_str(output,"\n]");
+
+       json_ostream_nascend_array(json_output);
+       if (!doveadm_http_server_finish_json_output(req, &json_output))
+               return;
+
        doveadm_http_server_send_response(req);
 }
 
@@ -891,30 +838,38 @@ doveadm_http_server_options_handler(struct client_request_http *req)
 
 static void doveadm_http_server_print_mounts(struct client_request_http *req)
 {
-       struct ostream *output = req->output;
+       struct json_ostream *json_output;
        unsigned int i;
 
-       o_stream_nsend_str(output, "[\n");
+       json_output = json_ostream_create(req->output, 0);
+       json_ostream_set_no_error_handling(json_output, TRUE);
+       json_ostream_ndescend_array(json_output, NULL);
+
        for (i = 0; i < N_ELEMENTS(doveadm_http_server_mounts); i++) {
-               if (i > 0)
-                       o_stream_nsend_str(output, ",\n");
-               o_stream_nsend_str(output, "{\"method\":\"");
-               if (doveadm_http_server_mounts[i].verb == NULL)
-                       o_stream_nsend_str(output, "*");
-               else {
-                       o_stream_nsend_str(output,
+               json_ostream_ndescend_object(json_output, NULL);
+
+               if (doveadm_http_server_mounts[i].verb == NULL) {
+                       json_ostream_nwrite_string(json_output, "method", "*");
+               } else {
+                       json_ostream_nwrite_string(
+                               json_output, "method",
                                doveadm_http_server_mounts[i].verb);
                }
-               o_stream_nsend_str(output, "\",\"path\":\"");
-               if (doveadm_http_server_mounts[i].path == NULL)
-                       o_stream_nsend_str(output, "*");
-               else {
-                       o_stream_nsend_str(output,
+               if (doveadm_http_server_mounts[i].path == NULL) {
+                       json_ostream_nwrite_string(json_output, "path", "*");
+               } else {
+                       json_ostream_nwrite_string(
+                               json_output, "path",
                                doveadm_http_server_mounts[i].path);
                }
-               o_stream_nsend_str(output, "\"}");
+
+               json_ostream_nascend_object(json_output);
        }
-       o_stream_nsend_str(output, "\n]");
+
+       json_ostream_nascend_array(json_output);
+       if (!doveadm_http_server_finish_json_output(req, &json_output))
+               return;
+
        doveadm_http_server_send_response(req);
 }
 
@@ -985,11 +940,8 @@ doveadm_http_server_request_destroy(struct client_request_http *req)
                       http_req->version_major, http_req->version_minor,
                       status, size, url, agent);
        }
-       if (req->json_parser != NULL) {
-               const char *error ATTR_UNUSED;
-               (void)json_parser_deinit(&req->json_parser, &error);
-               // we've already failed, ignore error
-       }
+       json_istream_destroy(&req->json_input);
+       json_ostream_destroy(&req->json_output);
        if (req->output != NULL)
                o_stream_set_no_error_handling(req->output, TRUE);
        io_remove(&req->io);
index 9ccc94b996839008f37583d99a14f56fd458c868..3c15df7151663e9e776169f27aa7092456b631ee 100644 (file)
@@ -15,7 +15,7 @@
 #include "mail-storage-service.h"
 #include "mail-user.h"
 #include "ostream.h"
-#include "json-parser.h"
+#include "json-ostream.h"
 #include "doveadm.h"
 #include "doveadm-print.h"
 
@@ -51,6 +51,7 @@ doveadm_get_auth_master_conn(const char *auth_socket_path)
 static int
 cmd_user_input(struct auth_master_connection *conn,
               const struct authtest_input *input,
+              struct json_ostream *json_output,
               const char *show_field, bool userdb)
 {
        const char *lookup_name = userdb ? "userdb lookup" : "passdb lookup";
@@ -68,69 +69,46 @@ cmd_user_input(struct auth_master_connection *conn,
                                              pool, &fields);
        }
        if (ret < 0) {
-               const char *msg;
                if (fields[0] == NULL) {
-                       msg = t_strdup_printf("\"error\":\"%s failed\"",
-                                             lookup_name);
+                       json_ostream_nwritef_string(json_output,
+                               "error", "%s failed", lookup_name);
                } else {
-                       msg = t_strdup_printf("\"error\":\"%s failed: %s\"",
-                                             lookup_name,
-                                             fields[0]);
+                       json_ostream_nwritef_string(json_output,
+                               "error", "%s failed: %s",
+                               lookup_name, fields[0]);
                }
-               o_stream_nsend_str(doveadm_print_ostream, msg);
                ret = -1;
        } else if (ret == 0) {
-               o_stream_nsend_str(doveadm_print_ostream,
-                       t_strdup_printf("\"error\":\"%s: user doesn't exist\"",
-                               lookup_name));
+               json_ostream_nwritef_string(json_output,
+                       "error", "%s: user doesn't exist", lookup_name);
        } else if (show_field != NULL) {
                size_t show_field_len = strlen(show_field);
-               string_t *json_field = t_str_new(show_field_len+1);
-               json_append_escaped(json_field, show_field);
-               o_stream_nsend_str(doveadm_print_ostream, t_strdup_printf("\"%s\":", str_c(json_field)));
                for (; *fields != NULL; fields++) {
                        if (strncmp(*fields, show_field, show_field_len) == 0 &&
                            (*fields)[show_field_len] == '=') {
-                               string_t *jsonval = t_str_new(32);
-                               json_append_escaped(jsonval, *fields + show_field_len + 1);
-                               o_stream_nsend_str(doveadm_print_ostream, "\"");
-                               o_stream_nsend_str(doveadm_print_ostream, str_c(jsonval));
-                               o_stream_nsend_str(doveadm_print_ostream, "\"");
+                               json_ostream_nwrite_string(
+                                       json_output, show_field,
+                                       *fields + show_field_len + 1);
                        }
                }
        } else {
-               string_t *jsonval = t_str_new(64);
-               o_stream_nsend_str(doveadm_print_ostream, "\"source\":\"");
-               o_stream_nsend_str(doveadm_print_ostream, userdb ? "userdb\"" : "passdb\"");
+               json_ostream_nwrite_string(json_output,
+                       "source", (userdb ? "userdb" : "passdb"));
 
                if (updated_username != NULL) {
-                       o_stream_nsend_str(doveadm_print_ostream, ",\"updated_username\":\"");
-                       str_truncate(jsonval, 0);
-                       json_append_escaped(jsonval, updated_username);
-                       o_stream_nsend_str(doveadm_print_ostream, str_c(jsonval));
-                       o_stream_nsend_str(doveadm_print_ostream, "\"");
+                       json_ostream_nwrite_string(json_output,
+                               "updated_username", updated_username);
                }
                for (; *fields != NULL; fields++) {
                        const char *field = *fields;
                        if (*field == '\0') continue;
                        p = strchr(*fields, '=');
-                       str_truncate(jsonval, 0);
                        if (p != NULL) {
-                               field = t_strcut(*fields, '=');
-                       }
-                       str_truncate(jsonval, 0);
-                       json_append_escaped(jsonval, field);
-                       o_stream_nsend_str(doveadm_print_ostream, ",\"");
-                       o_stream_nsend_str(doveadm_print_ostream, str_c(jsonval));
-                       o_stream_nsend_str(doveadm_print_ostream, "\":");
-                       if (p != NULL) {
-                               str_truncate(jsonval, 0);
-                               json_append_escaped(jsonval, p+1);
-                               o_stream_nsend_str(doveadm_print_ostream, "\"");
-                               o_stream_nsend_str(doveadm_print_ostream, str_c(jsonval));
-                               o_stream_nsend_str(doveadm_print_ostream, "\"");
+                               field = t_strdup_until(*fields, p);
+                               json_ostream_nwrite_string(json_output,
+                                                          field, p+1);
                        } else {
-                               o_stream_nsend_str(doveadm_print_ostream, "true");
+                               json_ostream_nwrite_true(json_output, field);
                        }
                }
        }
@@ -164,18 +142,17 @@ static void
 cmd_user_list(struct doveadm_cmd_context *cctx,
              struct auth_master_connection *conn,
              const struct authtest_input *input,
+             struct json_ostream *json_output,
              char *const *users)
 {
        struct auth_master_user_list_ctx *ctx;
        const char *username, *user_mask = "*";
-       string_t *escaped = t_str_new(256);
-       bool first = TRUE;
        unsigned int i;
 
        if (users[0] != NULL && users[1] == NULL)
                user_mask = users[0];
 
-       o_stream_nsend_str(doveadm_print_ostream, "{\"userList\":[");
+       json_ostream_ndescend_array(json_output, "userList");
 
        ctx = auth_master_user_list_init(conn, user_mask, &input->info);
        while ((username = auth_master_user_list_next(ctx)) != NULL) {
@@ -184,15 +161,8 @@ cmd_user_list(struct doveadm_cmd_context *cctx,
                                break;
                }
                if (users[i] != NULL) {
-                       if (first)
-                               first = FALSE;
-                       else
-                               o_stream_nsend_str(doveadm_print_ostream, ",");
-                       str_truncate(escaped, 0);
-                       str_append_c(escaped, '"');
-                       json_append_escaped(escaped, username);
-                       str_append_c(escaped, '"');
-                       o_stream_nsend(doveadm_print_ostream, escaped->data, escaped->used);
+                       json_ostream_nwrite_string(json_output, NULL,
+                                                  username);
                }
        }
        if (auth_master_user_list_deinit(&ctx) < 0) {
@@ -200,7 +170,7 @@ cmd_user_list(struct doveadm_cmd_context *cctx,
                doveadm_exit_code = EX_DATAERR;
        }
 
-       o_stream_nsend_str(doveadm_print_ostream, "]}");
+       json_ostream_nascend_array(json_output);
 }
 
 static void cmd_auth_cache_flush(struct doveadm_cmd_context *cctx)
@@ -229,46 +199,40 @@ static void cmd_auth_cache_flush(struct doveadm_cmd_context *cctx)
        auth_master_deinit(&conn);
 }
 
-static void cmd_user_mail_input_field(const char *key, const char *value,
-                                     const char *show_field, bool *first)
+static void cmd_user_mail_input_field(struct json_ostream *json_output,
+                                     const char *key, const char *value,
+                                     const char *show_field)
 {
-       string_t *jvalue = t_str_new(128);
        if (show_field != NULL && strcmp(show_field, key) != 0) return;
-       /* do not emit comma on first field. we need to keep track
-          of when the first field actually gets printed as it
-          might change due to show_field */
-       if (!*first)
-               o_stream_nsend_str(doveadm_print_ostream, ",");
-       *first = FALSE;
-       json_append_escaped(jvalue, key);
-       o_stream_nsend_str(doveadm_print_ostream, "\"");
-       o_stream_nsend_str(doveadm_print_ostream, str_c(jvalue));
-       o_stream_nsend_str(doveadm_print_ostream, "\":\"");
-       str_truncate(jvalue, 0);
-       json_append_escaped(jvalue, value);
-       o_stream_nsend_str(doveadm_print_ostream, str_c(jvalue));
-       o_stream_nsend_str(doveadm_print_ostream, "\"");
+
+       json_ostream_nwrite_string(json_output, key, value);
 }
 
 static void
 cmd_user_mail_print_fields(const struct authtest_input *input,
                           struct mail_user *user,
+                          struct json_ostream *json_output,
                           const char *const *userdb_fields,
                           const char *show_field)
 {
        const struct mail_storage_settings *mail_set;
        const char *key, *value;
        unsigned int i;
-       bool first = TRUE;
 
-       if (strcmp(input->username, user->username) != 0)
-               cmd_user_mail_input_field("user", user->username, show_field, &first);
-       cmd_user_mail_input_field("uid", user->set->mail_uid, show_field, &first);
-       cmd_user_mail_input_field("gid", user->set->mail_gid, show_field, &first);
-       cmd_user_mail_input_field("home", user->set->mail_home, show_field, &first);
+       if (strcmp(input->username, user->username) != 0) {
+               cmd_user_mail_input_field(json_output, "user",
+                       user->username, show_field);
+       }
+       cmd_user_mail_input_field(json_output, "uid",
+                                 user->set->mail_uid, show_field);
+       cmd_user_mail_input_field(json_output, "gid",
+                                 user->set->mail_gid, show_field);
+       cmd_user_mail_input_field(json_output, "home",
+                                 user->set->mail_home, show_field);
 
        mail_set = mail_user_set_get_storage_set(user);
-       cmd_user_mail_input_field("mail", mail_set->mail_location, show_field, &first);
+       cmd_user_mail_input_field(json_output, "mail",
+                                 mail_set->mail_location, show_field);
 
        if (userdb_fields != NULL) {
                for (i = 0; userdb_fields[i] != NULL; i++) {
@@ -284,7 +248,8 @@ cmd_user_mail_print_fields(const struct authtest_input *input,
                            strcmp(key, "home") != 0 &&
                            strcmp(key, "mail") != 0 &&
                            *key != '\0') {
-                               cmd_user_mail_input_field(key, value, show_field, &first);
+                               cmd_user_mail_input_field(json_output,
+                                       key, value, show_field);
                        }
                }
        }
@@ -293,6 +258,7 @@ cmd_user_mail_print_fields(const struct authtest_input *input,
 static int
 cmd_user_mail_input(struct mail_storage_service_ctx *storage_service,
                    const struct authtest_input *input,
+                   struct json_ostream *json_output,
                    const char *show_field, const char *expand_field)
 {
        struct mail_storage_service_input service_input;
@@ -319,39 +285,27 @@ cmd_user_mail_input(struct mail_storage_service_ctx *storage_service,
                pool_unref(&pool);
                if (ret < 0)
                        return -1;
-               string_t *username = t_str_new(32);
-               json_append_escaped(username, input->username);
-               o_stream_nsend_str(doveadm_print_ostream,
-                       t_strdup_printf("\"error\":\"userdb lookup: user %s doesn't exist\"", str_c(username))
-               );
+               json_ostream_nwritef_string(json_output, "error",
+                       "userdb lookup: user %s doesn't exist",
+                       input->username);
                return 0;
        }
 
-       if (expand_field == NULL)
-               cmd_user_mail_print_fields(input, user, userdb_fields, show_field);
-       else {
+       if (expand_field == NULL) {
+               cmd_user_mail_print_fields(input, user,
+                       json_output, userdb_fields, show_field);
+       } else {
                string_t *str = t_str_new(128);
                if (var_expand_with_funcs(str, expand_field,
                                          mail_user_var_expand_table(user),
                                          mail_user_var_expand_func_table, user,
                                          &error) <= 0) {
-                       string_t *str = t_str_new(128);
-                       str_printfa(str, "\"error\":\"Failed to expand field: ");
-                       json_append_escaped(str, error);
-                       str_append_c(str, '"');
-                       o_stream_nsend(doveadm_print_ostream, str_data(str), str_len(str));
+                       json_ostream_nwritef_string(json_output,
+                               "error", "Failed to expand field: %s", error);
                } else {
-                       string_t *value = t_str_new(128);
-                       json_append_escaped(value, expand_field);
-                       o_stream_nsend_str(doveadm_print_ostream, "\"");
-                       o_stream_nsend_str(doveadm_print_ostream, str_c(value));
-                       o_stream_nsend_str(doveadm_print_ostream, "\":\"");
-                       str_truncate(value, 0);
-                       json_append_escaped(value, str_c(str));
-                       o_stream_nsend_str(doveadm_print_ostream, str_c(value));
-                       o_stream_nsend_str(doveadm_print_ostream, "\"");
+                       json_ostream_nwrite_string(json_output,
+                               expand_field, str_c(str));
                }
-
        }
 
        mail_user_deinit(&user);
@@ -368,7 +322,8 @@ static void cmd_user_ver2(struct doveadm_cmd_context *cctx)
        struct authtest_input input;
        const char *show_field = NULL, *expand_field = NULL;
        struct mail_storage_service_ctx *storage_service = NULL;
-       bool have_wildcards, userdb_only = FALSE, first = TRUE;
+       bool have_wildcards, userdb_only = FALSE;
+       struct json_ostream *json_output;
        int ret;
 
        if (!doveadm_cmd_param_str(cctx, "socket-path", &auth_socket_path))
@@ -412,8 +367,16 @@ static void cmd_user_ver2(struct doveadm_cmd_context *cctx)
                }
        }
 
+       json_output = json_ostream_create(doveadm_print_ostream, 0);
+       json_ostream_set_no_error_handling(json_output, TRUE);
+       json_ostream_ndescend_object(json_output, NULL);
+
        if (have_wildcards) {
-               cmd_user_list(cctx, conn, &input, (char*const*)optval);
+               cmd_user_list(cctx, conn, &input, json_output,
+                             (char*const*)optval);
+
+               json_ostream_nascend_object(json_output);
+               json_ostream_destroy(&json_output);
                auth_master_deinit(&conn);
                return;
        }
@@ -430,34 +393,25 @@ static void cmd_user_ver2(struct doveadm_cmd_context *cctx)
                conn = NULL;
        }
 
-       string_t *json = t_str_new(64);
-       o_stream_nsend_str(doveadm_print_ostream, "{");
-
        input.info.local_ip = cctx->local_ip;
        input.info.local_port = cctx->local_port;
        input.info.remote_ip = cctx->remote_ip;
        input.info.remote_port = cctx->remote_port;
 
        for(const char *const *val = optval; *val != NULL; val++) {
-               str_truncate(json, 0);
-               json_append_escaped(json, *val);
-
                input.username = *val;
-               if (first)
-                       first = FALSE;
-               else
-                       o_stream_nsend_str(doveadm_print_ostream, ",");
 
-               o_stream_nsend_str(doveadm_print_ostream, "\"");
-               o_stream_nsend_str(doveadm_print_ostream, str_c(json));
-               o_stream_nsend_str(doveadm_print_ostream, "\"");
-               o_stream_nsend_str(doveadm_print_ostream, ":{");
+               json_ostream_ndescend_object(json_output, *val);
 
-               ret = !userdb_only ?
-                       cmd_user_mail_input(storage_service, &input, show_field, expand_field) :
-                       cmd_user_input(conn, &input, show_field, TRUE);
+               if (!userdb_only) {
+                       ret = cmd_user_mail_input(storage_service, &input,
+                               json_output, show_field, expand_field);
+               } else {
+                       ret = cmd_user_input(conn, &input,
+                               json_output, show_field, TRUE);
+               }
 
-               o_stream_nsend_str(doveadm_print_ostream, "}");
+               json_ostream_nascend_object(json_output);
 
                switch (ret) {
                case -1:
@@ -469,7 +423,8 @@ static void cmd_user_ver2(struct doveadm_cmd_context *cctx)
                }
        }
 
-       o_stream_nsend_str(doveadm_print_ostream,"}");
+       json_ostream_nascend_object(json_output);
+       json_ostream_destroy(&json_output);
 
        if (storage_service != NULL)
                mail_storage_service_deinit(&storage_service);
index 13481912b5154b1a51bd02e2ec4e55dd43abebf0..b8f059fdc495929846809adecc03501c2144b91a 100644 (file)
@@ -5,7 +5,7 @@
 #include "str.h"
 #include "strescape.h"
 #include "ostream.h"
-#include "json-parser.h"
+#include "json-ostream.h"
 #include "doveadm.h"
 #include "doveadm-print.h"
 #include "doveadm-print-private.h"
@@ -13,9 +13,9 @@
 
 struct doveadm_print_json_context {
        unsigned int header_idx, header_count;
-       bool first_row;
-       bool in_stream;
        bool flushed;
+       struct json_ostream *json_output;
+       struct ostream *str_stream;
        ARRAY(struct doveadm_print_header) headers;
        pool_t pool;
        string_t *str;
@@ -23,16 +23,22 @@ struct doveadm_print_json_context {
 
 static struct doveadm_print_json_context ctx;
 
-static void doveadm_print_json_flush_internal(void);
-
 static void doveadm_print_json_init(void)
 {
        i_zero(&ctx);
        ctx.pool = pool_alloconly_create("doveadm json print", 1024);
-       ctx.str = str_new(ctx.pool, 256);
+
        p_array_init(&ctx.headers, ctx.pool, 1);
-       ctx.first_row = TRUE;
-       ctx.in_stream = FALSE;
+}
+
+static void doveadm_print_json_init_output(void)
+{
+       if (ctx.json_output != NULL)
+               return;
+
+       ctx.json_output = json_ostream_create(doveadm_print_ostream, 0);
+       json_ostream_set_no_error_handling(ctx.json_output, TRUE);
+       json_ostream_ndescend_array(ctx.json_output, NULL);
 }
 
 static void
@@ -46,33 +52,19 @@ doveadm_print_json_header(const struct doveadm_print_header *hdr)
 }
 
 static void
-doveadm_print_json_value_header(const struct doveadm_print_header *hdr)
+doveadm_print_json_value_header(void)
 {
-       // get header name
-       if (ctx.header_idx == 0) {
-               if (ctx.first_row == TRUE) {
-                       ctx.first_row = FALSE;
-                       str_append_c(ctx.str, '[');
-               } else {
-                       str_append_c(ctx.str, ',');
-               }
-               str_append_c(ctx.str, '{');
-       } else {
-               str_append_c(ctx.str, ',');
-       }
+       doveadm_print_json_init_output();
 
-       str_append_c(ctx.str, '"');
-       json_append_escaped(ctx.str, hdr->key);
-       str_append_c(ctx.str, '"');
-       str_append_c(ctx.str, ':');
+       if (ctx.header_idx == 0)
+               json_ostream_ndescend_object(ctx.json_output, NULL);
 }
 
 static void
 doveadm_print_json_value_footer(void) {
        if (++ctx.header_idx == ctx.header_count) {
                ctx.header_idx = 0;
-               str_append_c(ctx.str, '}');
-               doveadm_print_json_flush_internal();
+               json_ostream_nascend_object(ctx.json_output);
        }
 }
 
@@ -80,17 +72,16 @@ static void doveadm_print_json_print(const char *value)
 {
        const struct doveadm_print_header *hdr = array_idx(&ctx.headers, ctx.header_idx);
 
-       doveadm_print_json_value_header(hdr);
+       doveadm_print_json_value_header();
 
        if (value == NULL) {
-               str_append(ctx.str, "null");
+               json_ostream_nwrite_null(ctx.json_output, hdr->key);
        } else if ((hdr->flags & DOVEADM_PRINT_HEADER_FLAG_NUMBER) != 0) {
                i_assert(str_is_float(value, '\0'));
-               str_append(ctx.str, value);
+               json_ostream_nwrite_number_raw(ctx.json_output,
+                                              hdr->key, value);
        } else {
-               str_append_c(ctx.str, '"');
-               json_append_escaped(ctx.str, value);
-               str_append_c(ctx.str, '"');
+               json_ostream_nwrite_string(ctx.json_output, hdr->key, value);
        }
 
        doveadm_print_json_value_footer();
@@ -99,32 +90,23 @@ static void doveadm_print_json_print(const char *value)
 static void
 doveadm_print_json_print_stream(const unsigned char *value, size_t size)
 {
-       if (!ctx.in_stream) {
+       if (ctx.str_stream == NULL) {
                const struct doveadm_print_header *hdr =
                        array_idx(&ctx.headers, ctx.header_idx);
-               doveadm_print_json_value_header(hdr);
+               doveadm_print_json_value_header();
                i_assert((hdr->flags & DOVEADM_PRINT_HEADER_FLAG_NUMBER) == 0);
-               str_append_c(ctx.str, '"');
-               ctx.in_stream = TRUE;
+               ctx.str_stream = json_ostream_nopen_string_stream(
+                       ctx.json_output, hdr->key);
+               o_stream_set_no_error_handling(ctx.str_stream, TRUE);
        }
 
        if (size == 0) {
-               str_append_c(ctx.str, '"');
+               o_stream_destroy(&ctx.str_stream);
                doveadm_print_json_value_footer();
-               ctx.in_stream = FALSE;
                return;
        }
 
-       json_append_escaped_data(ctx.str, value, size);
-
-       if (str_len(ctx.str) >= IO_BLOCK_SIZE)
-               doveadm_print_json_flush_internal();
-}
-
-static void doveadm_print_json_flush_internal(void)
-{
-       o_stream_nsend(doveadm_print_ostream, str_data(ctx.str), str_len(ctx.str));
-       str_truncate(ctx.str, 0);
+       o_stream_nsend(ctx.str_stream, value, size);
 }
 
 static void doveadm_print_json_flush(void)
@@ -133,17 +115,15 @@ static void doveadm_print_json_flush(void)
                return;
        ctx.flushed = TRUE;
 
-       if (ctx.first_row == FALSE)
-               str_append_c(ctx.str,']');
-       else {
-               str_append_c(ctx.str,'[');
-               str_append_c(ctx.str,']');
-       }
-       doveadm_print_json_flush_internal();
+       if (ctx.json_output == NULL)
+               doveadm_print_json_init_output();
+       json_ostream_nascend_array(ctx.json_output);
+       json_ostream_nflush(ctx.json_output);
 }
 
 static void doveadm_print_json_deinit(void)
 {
+       json_ostream_destroy(&ctx.json_output);
        pool_unref(&ctx.pool);
 }