#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,
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;
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;
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
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);
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",
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",
/* 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;
}
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;
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",
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;
}
/* 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;
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",
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;
}
/* 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:
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",
}
/* 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",
}
/* 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;
}
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);
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 */
{
/* 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);
/* 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);
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)
{
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;
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);
}
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);
}
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);
}
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);
#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"
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";
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);
}
}
}
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) {
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) {
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)
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++) {
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);
}
}
}
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;
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);
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))
}
}
+ 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;
}
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:
}
}
- 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);
#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"
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;
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
}
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);
}
}
{
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();
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)
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);
}