From c77e29c4032aae2e62d0ad04bbcf77bcd1a3ce75 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 12 Jun 2019 10:08:54 +0930 Subject: [PATCH] libplugin: use json_out internally. Since this handles escaping for us, this automatically fixes our previous escaping issued. Fixes: #2612 Signed-off-by: Rusty Russell --- plugins/libplugin.c | 234 +++++++++++++++++++++++++------------------- 1 file changed, 133 insertions(+), 101 deletions(-) diff --git a/plugins/libplugin.c b/plugins/libplugin.c index 68fb3d1fe..85bf21c67 100644 --- a/plugins/libplugin.c +++ b/plugins/libplugin.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -153,32 +154,32 @@ static struct command *read_json_request(const tal_t *ctx, return cmd; } -/* I stole this trick from @wythe (Mark Beckwith); its ugliness is beautiful */ -static void vprintf_json(int fd, const char *fmt_single_ticks, va_list ap) +/* This starts a JSON RPC message with boilerplate */ +static struct json_out *start_json_rpc(const tal_t *ctx, u64 id) { - char *json, *p; - size_t n; + struct json_out *jout = json_out_new(ctx); - json = tal_vfmt(NULL, fmt_single_ticks, ap); + json_out_start(jout, NULL, '{'); + json_out_addstr(jout, "jsonrpc", "2.0"); + json_out_add(jout, "id", false, "%"PRIu64, id); - for (n = 0, p = strchr(json, '\''); p; p = strchr(json, '\'')) { - *p = '"'; - n++; - } - /* Don't put stray single-ticks in like this comment does! */ - assert(n % 2 == 0); - write_all(fd, json, strlen(json)); - tal_free(json); + return jout; } -static PRINTF_FMT(2,3) void printf_json(int fd, - const char *fmt_single_ticks, ...) +/* This closes a JSON response and writes it out. */ +static void finish_and_send_json(int fd, struct json_out *jout) { - va_list ap; + size_t len; + const char *p; - va_start(ap, fmt_single_ticks); - vprintf_json(fd, fmt_single_ticks, ap); - va_end(ap); + json_out_end(jout, '}'); + /* We double-\n terminate. Don't need to, but it's more readable. */ + memcpy(json_out_direct(jout, 2), "\n\n", 2); + json_out_finished(jout); + + p = json_out_contents(jout, &len); + write_all(fd, p, len); + json_out_consume(jout, len); } /* param.c is insistant on functions returning 'struct command_result'; we @@ -189,46 +190,56 @@ static struct command_result *WARN_UNUSED_RESULT end_cmd(struct command *cmd) return &complete; } +/* FIXME: We promised callers we'd turn ' into ". */ +static void copy_with_quote_sub(char *dst, const char *src, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (src[i] == '\'') + dst[i] = '"'; + else + dst[i] = src[i]; + } +} + static struct command_result *WARN_UNUSED_RESULT -command_done_ok(struct command *cmd, const char *result) +command_done_raw(struct command *cmd, + const char *label, + const char *str, int size) { - printf_json(STDOUT_FILENO, - "{ 'jsonrpc': '2.0', " - "'id': %"PRIu64", " - "'result': { %s } }\n\n", - cmd->id, result); + struct json_out *jout = start_json_rpc(cmd, cmd->id); + + copy_with_quote_sub(json_out_member_direct(jout, label, size), + str, size); + + finish_and_send_json(STDOUT_FILENO, jout); return end_cmd(cmd); } +static struct command_result *WARN_UNUSED_RESULT +command_done_ok(struct command *cmd, const char *result) +{ + return command_done_raw(cmd, "result", result, strlen(result)); +} + struct command_result *command_done_err(struct command *cmd, int code, const char *errmsg, const char *data) { - printf_json(STDOUT_FILENO, - "{ 'jsonrpc': '2.0', " - "'id': %"PRIu64", " - " 'error' : " - " { 'code' : %d," - " 'message' : '%s'", - cmd->id, code, errmsg); - if (data) - printf_json(STDOUT_FILENO, - ", 'data': %s", data); - printf_json(STDOUT_FILENO, " } }\n\n"); - return end_cmd(cmd); -} + struct json_out *jout = start_json_rpc(cmd, cmd->id); -static struct command_result *WARN_UNUSED_RESULT -command_done_raw(struct command *cmd, - const char *label, - const char *str, int size) -{ - printf_json(STDOUT_FILENO, - "{ 'jsonrpc': '2.0', " - "'id': %"PRIu64", " - " '%s' : %.*s }\n\n", - cmd->id, label, size, str); + json_out_start(jout, "error", '{'); + json_out_add(jout, "code", false, "%d", code); + json_out_addstr(jout, "message", errmsg); + + if (data) { + char *p; + p = json_out_member_direct(jout, "data", strlen(data)); + copy_with_quote_sub(p, data, strlen(data)); + } + json_out_end(jout, '}'); + + finish_and_send_json(STDOUT_FILENO, jout); return end_cmd(cmd); } @@ -328,6 +339,23 @@ static const jsmntok_t *read_rpc_reply(const tal_t *ctx, return toks; } +static struct json_out *start_json_request(const tal_t *ctx, + u64 id, + const char *method, + const char *params) +{ + struct json_out *jout; + + jout = start_json_rpc(tmpctx, id); + json_out_addstr(jout, "method", method); + json_out_start(jout, "params", '{'); + copy_with_quote_sub(json_out_direct(jout, strlen(params)), + params, strlen(params)); + json_out_end(jout, '}'); + + return jout; +} + /* Synchronous routine to send command and extract single field from response */ const char *rpc_delve(const tal_t *ctx, const char *method, const char *params, @@ -337,10 +365,10 @@ const char *rpc_delve(const tal_t *ctx, const jsmntok_t *contents, *t; int reqlen; const char *ret; + struct json_out *jout; - printf_json(rpc->fd, - "{ 'method': '%s', 'id': 0, 'params': { %s } }", - method, params); + jout = start_json_request(tmpctx, 0, method, params); + finish_and_send_json(rpc->fd, jout); read_rpc_reply(tmpctx, rpc, &contents, &error, &reqlen); if (error) @@ -410,7 +438,11 @@ send_outreq_(struct command *cmd, const char *paramfmt_single_ticks, ...) { va_list ap; - struct out_req *out = tal(cmd, struct out_req); + struct json_out *jout; + struct out_req *out; + char *params; + + out = tal(cmd, struct out_req); out->id = next_outreq_id++; out->cmd = cmd; out->cb = cb; @@ -418,13 +450,13 @@ send_outreq_(struct command *cmd, out->arg = arg; uintmap_add(&out_reqs, out->id, out); - printf_json(rpc_conn.fd, - "{ 'method': '%s', 'id': %"PRIu64", 'params': {", - method, out->id); va_start(ap, paramfmt_single_ticks); - vprintf_json(rpc_conn.fd, paramfmt_single_ticks, ap); + params = tal_vfmt(tmpctx, paramfmt_single_ticks, ap); va_end(ap); - printf_json(rpc_conn.fd, "} }"); + + jout = start_json_request(tmpctx, out->id, method, params); + finish_and_send_json(rpc_conn.fd, jout); + return &pending; } @@ -434,41 +466,39 @@ handle_getmanifest(struct command *getmanifest_cmd, size_t num_commands, const struct plugin_option *opts) { - char *params = tal_strdup(getmanifest_cmd, - "'options': ["); + struct json_out *params = json_out_new(tmpctx); + size_t len; + const char *p; + json_out_start(params, NULL, '{'); + json_out_start(params, "options", '['); for (size_t i = 0; i < tal_count(opts); i++) { - tal_append_fmt(¶ms, "{ 'name': '%s'," - " 'type': '%s'," - " 'description': '%s' }%s", - opts[i].name, - opts[i].type, - opts[i].description, - i == tal_count(opts) - 1 ? "" : ",\n"); + json_out_start(params, NULL, '{'); + json_out_addstr(params, "name", opts[i].name); + json_out_addstr(params, "type", opts[i].type); + json_out_addstr(params, "description", opts[i].description); + json_out_end(params, '}'); } + json_out_end(params, ']'); - tal_append_fmt(¶ms, - "],\n" - "'rpcmethods': [ "); - + json_out_start(params, "rpcmethods", '['); for (size_t i = 0; i < num_commands; i++) { - tal_append_fmt(¶ms, "{ 'name': '%s'," - " 'category': '%s'," - " 'usage': '%s'," - " 'description': '%s'", - commands[i].name, - commands[i].category, - strmap_get(&usagemap, commands[i].name), - commands[i].description); + json_out_start(params, NULL, '{'); + json_out_addstr(params, "name", commands[i].name); + json_out_addstr(params, "usage", + strmap_get(&usagemap, commands[i].name)); + json_out_addstr(params, "description", commands[i].description); if (commands[i].long_description) - tal_append_fmt(¶ms, - " 'long_description': '%s'", - commands[i].long_description); - tal_append_fmt(¶ms, - "}%s", i == num_commands - 1 ? "" : ",\n"); + json_out_addstr(params, "long_description", + commands[i].long_description); + json_out_end(params, '}'); } - tal_append_fmt(¶ms, " ]"); - return command_done_ok(getmanifest_cmd, params); + json_out_end(params, ']'); + json_out_end(params, '}'); + json_out_finished(params); + + p = json_out_contents(params, &len); + return command_done_raw(getmanifest_cmd, "result", p, len); } static struct command_result *handle_init(struct command *init_cmd, @@ -528,7 +558,7 @@ static struct command_result *handle_init(struct command *init_cmd, if (init) init(&rpc_conn); - return command_done_ok(init_cmd, ""); + return command_done_ok(init_cmd, "{}"); } char *u64_option(const char *arg, u64 *i) @@ -619,21 +649,23 @@ struct plugin_timer *plugin_timer(struct plugin_conn *rpc, struct timerel t, static void plugin_logv(enum log_level l, const char *fmt, va_list ap) { - char *message; - - printf_json(STDOUT_FILENO, - "{ 'jsonrpc': '2.0', " - "'method': 'log', " - "'params': { 'level': '%s', 'message': \"", - l == LOG_DBG ? "debug" - : l == LOG_INFORM ? "info" - : l == LOG_UNUSUAL ? "warn" - : "error"); - - message = tal_vfmt(NULL, fmt, ap); - write_all(STDOUT_FILENO, message, strlen(message)); - printf_json(STDOUT_FILENO, "\" } }\n\n"); - tal_free(message); + struct json_out *jout = json_out_new(tmpctx); + + json_out_start(jout, NULL, '{'); + json_out_addstr(jout, "jsonrpc", "2.0"); + json_out_addstr(jout, "method", "log"); + + json_out_start(jout, "params", '{'); + json_out_addstr(jout, "level", + l == LOG_DBG ? "debug" + : l == LOG_INFORM ? "info" + : l == LOG_UNUSUAL ? "warn" + : "error"); + json_out_addv(jout, "message", true, fmt, ap); + json_out_end(jout, '}'); + + /* Last '}' is done by finish_and_send_json */ + finish_and_send_json(STDOUT_FILENO, jout); } void NORETURN plugin_err(const char *fmt, ...)