From: Stephan Bosch Date: Sun, 28 Jul 2019 09:20:38 +0000 (+0200) Subject: doveadm: Use the new lib-json X-Git-Tag: 2.4.0~2367 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2ac44f7acfc9dc652135ee1847a2db43c24e2c56;p=thirdparty%2Fdovecot%2Fcore.git doveadm: Use the new lib-json --- diff --git a/src/doveadm/Makefile.am b/src/doveadm/Makefile.am index eafecc5f37..294b8e539a 100644 --- a/src/doveadm/Makefile.am +++ b/src/doveadm/Makefile.am @@ -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 \ diff --git a/src/doveadm/client-connection-http.c b/src/doveadm/client-connection-http.c index 5bd914643e..4f89a577f8 100644 --- a/src/doveadm/client-connection-http.c +++ b/src/doveadm/client-connection-http.c @@ -27,14 +27,14 @@ #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 #include 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); diff --git a/src/doveadm/doveadm-auth-server.c b/src/doveadm/doveadm-auth-server.c index 9ccc94b996..3c15df7151 100644 --- a/src/doveadm/doveadm-auth-server.c +++ b/src/doveadm/doveadm-auth-server.c @@ -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); diff --git a/src/doveadm/doveadm-print-json.c b/src/doveadm/doveadm-print-json.c index 13481912b5..b8f059fdc4 100644 --- a/src/doveadm/doveadm-print-json.c +++ b/src/doveadm/doveadm-print-json.c @@ -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); }