diff --git a/common/Makefile b/common/Makefile index 368d55622..ff46b38d9 100644 --- a/common/Makefile +++ b/common/Makefile @@ -21,6 +21,7 @@ COMMON_SRC_NOGEN := \ common/initial_commit_tx.c \ common/io_debug.c \ common/json.c \ + common/json_escaped.c \ common/key_derive.c \ common/keyset.c \ common/memleak.c \ diff --git a/common/json.c b/common/json.c index 70120b6e2..79d1492be 100644 --- a/common/json.c +++ b/common/json.c @@ -1,5 +1,6 @@ /* JSON core and helpers */ #include "json.h" +#include "json_escaped.h" #include #include #include @@ -446,56 +447,6 @@ void json_add_string(struct json_result *result, const char *fieldname, const ch result_append_fmt(result, "\"%s\"", escaped); } -void json_add_string_escape(struct json_result *result, const char *fieldname, - const char *value) -{ - /* Worst case: all \uXXXX */ - char *escaped = tal_arr(result, char, strlen(value) * 6 + 1); - size_t i, n; - - json_start_member(result, fieldname); - for (i = n = 0; value[i]; i++, n++) { - char esc = 0; - switch (value[i]) { - case '\n': - esc = 'n'; - break; - case '\b': - esc = 'b'; - break; - case '\f': - esc = 'f'; - break; - case '\t': - esc = 't'; - break; - case '\r': - esc = 'r'; - break; - case '\\': - case '"': - esc = value[i]; - break; - default: - if ((unsigned)value[i] < ' ' - || value[i] == 127) { - sprintf(escaped + n, "\\u%04X", value[i]); - n += 5; - continue; - } - } - if (esc) { - escaped[n++] = '\\'; - escaped[n] = esc; - } else - escaped[n] = value[i]; - } - - escaped[n] = '\0'; - result_append_fmt(result, "\"%s\"", escaped); - tal_free(escaped); -} - void json_add_bool(struct json_result *result, const char *fieldname, bool value) { json_start_member(result, fieldname); @@ -537,6 +488,15 @@ void json_add_object(struct json_result *result, ...) va_end(ap); } +void json_add_escaped_string(struct json_result *result, const char *fieldname, + const struct json_escaped *esc TAKES) +{ + json_start_member(result, fieldname); + result_append_fmt(result, "\"%s\"", esc->s); + if (taken(esc)) + tal_free(esc); +} + struct json_result *new_json_result(const tal_t *ctx) { struct json_result *r = tal(ctx, struct json_result); diff --git a/common/json.h b/common/json.h index 805a270a5..b8648bd01 100644 --- a/common/json.h +++ b/common/json.h @@ -2,6 +2,7 @@ #define LIGHTNING_COMMON_JSON_H #include "config.h" #include +#include #include #include #include @@ -79,10 +80,6 @@ struct json_result *new_json_result(const tal_t *ctx); */ void json_add_string(struct json_result *result, const char *fieldname, const char *value); -/* Properly escapes any characters in @value */ -void json_add_string_escape(struct json_result *result, const char *fieldname, - const char *value); - /* '"fieldname" : literal' or 'literal' if fieldname is NULL*/ void json_add_literal(struct json_result *result, const char *fieldname, const char *literal, int len); diff --git a/common/json_escaped.c b/common/json_escaped.c new file mode 100644 index 000000000..5f7bbb53e --- /dev/null +++ b/common/json_escaped.c @@ -0,0 +1,86 @@ +#include +#include + +struct json_escaped *json_escaped_string_(const tal_t *ctx, + const void *bytes, size_t len) +{ + struct json_escaped *esc; + + esc = tal_alloc_arr_(ctx, 1, len + 1, false, true, + TAL_LABEL(struct json_escaped, "")); + memcpy(esc->s, bytes, len); + esc->s[len] = '\0'; + return esc; +} + +struct json_escaped *json_tok_escaped_string(const tal_t *ctx, + const char *buffer, + const jsmntok_t *tok) +{ + if (tok->type != JSMN_STRING) + return NULL; + /* jsmn always gives us ~ well-formed strings. */ + return json_escaped_string_(ctx, buffer + tok->start, + tok->end - tok->start); +} + +bool json_escaped_streq(const struct json_escaped *esc, const char *str) +{ + return streq(esc->s, str); +} + +bool json_escaped_eq(const struct json_escaped *a, + const struct json_escaped *b) +{ + return streq(a->s, b->s); +} + +struct json_escaped *json_escape(const tal_t *ctx, const char *str TAKES) +{ + struct json_escaped *esc; + size_t i, n; + + /* Worst case: all \uXXXX */ + esc = (struct json_escaped *)tal_arr(ctx, char, strlen(str) * 6 + 1); + + for (i = n = 0; str[i]; i++, n++) { + char escape = 0; + switch (str[i]) { + case '\n': + escape = 'n'; + break; + case '\b': + escape = 'b'; + break; + case '\f': + escape = 'f'; + break; + case '\t': + escape = 't'; + break; + case '\r': + escape = 'r'; + break; + case '\\': + case '"': + escape = str[i]; + break; + default: + if ((unsigned)str[i] < ' ' || str[i] == 127) { + sprintf(esc->s + n, "\\u%04X", str[i]); + n += 5; + continue; + } + } + if (escape) { + esc->s[n++] = '\\'; + esc->s[n] = escape; + } else + esc->s[n] = str[i]; + } + + esc->s[n] = '\0'; + if (taken(str)) + tal_free(str); + return esc; +} diff --git a/common/json_escaped.h b/common/json_escaped.h new file mode 100644 index 000000000..31ec389db --- /dev/null +++ b/common/json_escaped.h @@ -0,0 +1,36 @@ +#ifndef LIGHTNING_COMMON_JSON_ESCAPED_H +#define LIGHTNING_COMMON_JSON_ESCAPED_H +#include "config.h" +#include + +/* Type differentiation for a correctly-escaped JSON string */ +struct json_escaped { + /* NUL terminated string. */ + char s[1]; +}; + +/* @str be a valid UTF-8 string */ +struct json_escaped *json_escape(const tal_t *ctx, const char *str TAKES); + +/* Extract a JSON-escaped string. */ +struct json_escaped *json_tok_escaped_string(const tal_t *ctx, + const char *buffer, + const jsmntok_t *tok); + +/* Is @esc equal to @str */ +bool json_escaped_streq(const struct json_escaped *esc, const char *str); + + +/* Are two escaped json strings identical? */ +bool json_escaped_eq(const struct json_escaped *a, + const struct json_escaped *b); + +void json_add_escaped_string(struct json_result *result, + const char *fieldname, + const struct json_escaped *esc TAKES); + +/* Internal routine for creating json_escaped from bytes. */ +struct json_escaped *json_escaped_string_(const tal_t *ctx, + const void *bytes, size_t len); + +#endif /* LIGHTNING_COMMON_JSON_ESCAPED_H */ diff --git a/common/test/run-json.c b/common/test/run-json.c index c9bb663e5..0f857b642 100644 --- a/common/test/run-json.c +++ b/common/test/run-json.c @@ -1,4 +1,5 @@ #include "../json.c" +#include "../json_escaped.c" #include /* AUTOGENERATED MOCKS START */ @@ -85,12 +86,14 @@ static void test_json_escape(void) for (i = 1; i < 256; i++) { char badstr[2]; struct json_result *result = new_json_result(NULL); + struct json_escaped *esc; badstr[0] = i; badstr[1] = 0; json_object_start(result, NULL); - json_add_string_escape(result, "x", badstr); + esc = json_escape(NULL, badstr); + json_add_escaped_string(result, "x", take(esc)); json_object_end(result); str = json_result_string(result); @@ -114,4 +117,6 @@ int main(void) test_json_tok_bitcoin_amount(); test_json_filter(); test_json_escape(); + assert(!taken_any()); + take_cleanup(); } diff --git a/lightningd/Makefile b/lightningd/Makefile index 02b509f9d..68bd7527a 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -31,6 +31,7 @@ LIGHTNINGD_COMMON_OBJS := \ common/io_debug.o \ common/key_derive.o \ common/json.o \ + common/json_escaped.o \ common/memleak.o \ common/msg_queue.o \ common/permute_tx.o \ diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 90fb6775d..2ba723a1e 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -110,7 +111,7 @@ static void json_invoice(struct command *cmd, struct invoice_details details; jsmntok_t *msatoshi, *label, *desc, *exp, *fallback; u64 *msatoshi_val; - const char *label_val; + const struct json_escaped *label_val; const char *desc_val; enum address_parse_result fallback_parse; struct json_result *response = new_json_result(cmd); @@ -147,14 +148,18 @@ static void json_invoice(struct command *cmd, } } /* label */ - label_val = tal_strndup(cmd, buffer + label->start, - label->end - label->start); - if (wallet_invoice_find_by_label(wallet, &invoice, label_val)) { - command_fail(cmd, "Duplicate label '%s'", label_val); + label_val = json_tok_escaped_string(cmd, buffer, label); + if (!label_val) { + command_fail(cmd, "label '%.*s' not a string", + label->end - label->start, buffer + label->start); return; } - if (strlen(label_val) > INVOICE_MAX_LABEL_LEN) { - command_fail(cmd, "Label '%s' over %u bytes", label_val, + if (wallet_invoice_find_by_label(wallet, &invoice, label_val->s)) { + command_fail(cmd, "Duplicate label '%s'", label_val->s); + return; + } + if (strlen(label_val->s) > INVOICE_MAX_LABEL_LEN) { + command_fail(cmd, "Label '%s' over %u bytes", label_val->s, INVOICE_MAX_LABEL_LEN); return; } @@ -221,7 +226,7 @@ static void json_invoice(struct command *cmd, result = wallet_invoice_create(cmd->ld->wallet, &invoice, take(msatoshi_val), - take(label_val), + take((char *)label_val->s), expiry, b11enc, &r, diff --git a/lightningd/jsonrpc.c b/lightningd/jsonrpc.c index 591f0fb1d..51ebdda5f 100644 --- a/lightningd/jsonrpc.c +++ b/lightningd/jsonrpc.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -201,10 +202,15 @@ static void json_help(struct command *cmd, "HELP! Please contribute" " a description for this" " command!"); - else - json_add_string_escape(response, - "verbose", - cmdlist[i]->verbose); + else { + struct json_escaped *esc; + + esc = json_escape(NULL, + cmdlist[i]->verbose); + json_add_escaped_string(response, + "verbose", + take(esc)); + } goto done; } } @@ -281,11 +287,10 @@ static void connection_complete_error(struct json_connection *jcon, int code, const struct json_result *data) { - /* Use this to escape errmsg. */ - struct json_result *errorres = new_json_result(tmpctx); + struct json_escaped *esc; const char *data_str; - json_add_string_escape(errorres, NULL, errmsg); + esc = json_escape(tmpctx, errmsg); if (data) data_str = tal_fmt(tmpctx, ", \"data\" : %s", json_result_string(data)); @@ -298,10 +303,10 @@ static void connection_complete_error(struct json_connection *jcon, "{ \"jsonrpc\": \"2.0\", " " \"error\" : " "{ \"code\" : %d," - " \"message\" : %s%s }," + " \"message\" : \"%s\"%s }," " \"id\" : %s }\n", code, - json_result_string(errorres), + esc->s, data_str, id))); } diff --git a/lightningd/options.c b/lightningd/options.c index 20f041fa7..ec13b20dc 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -876,8 +877,10 @@ static void add_config(struct lightningd *ld, } } - if (answer) - json_add_string_escape(response, name0, answer); + if (answer) { + struct json_escaped *esc = json_escape(NULL, answer); + json_add_escaped_string(response, name0, take(esc)); + } tal_free(name0); } diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 41aba8d45..65216242a 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -578,6 +579,30 @@ struct getpeers_args { struct pubkey *specific_id; }; +static void json_add_node_decoration(struct json_result *response, + struct gossip_getnodes_entry **nodes, + const struct pubkey *id) +{ + for (size_t i = 0; i < tal_count(nodes); i++) { + struct json_escaped *esc; + + /* If no addresses, then this node announcement hasn't been + * received yet So no alias information either. + */ + if (nodes[i]->addresses == NULL) + continue; + + if (!pubkey_eq(&nodes[i]->nodeid, id)) + continue; + + esc = json_escape(NULL, (const char *)nodes[i]->alias); + json_add_escaped_string(response, "alias", take(esc)); + json_add_hex(response, "color", + nodes[i]->color, ARRAY_SIZE(nodes[i]->color)); + break; + } +} + static void gossipd_getpeers_complete(struct subd *gossip, const u8 *msg, const int *fds UNUSED, struct getpeers_args *gpa) @@ -620,17 +645,7 @@ static void gossipd_getpeers_complete(struct subd *gossip, const u8 *msg, json_array_end(response); } - for (size_t i = 0; i < tal_count(nodes); i++) { - /* If no addresses, then this node announcement hasn't been received yet - * So no alias information either. - */ - if (nodes[i]->addresses != NULL && pubkey_eq(&nodes[i]->nodeid, &p->id)) { - json_add_string_escape(response, "alias", (char*)nodes[i]->alias); - json_add_hex(response, "color", nodes[i]->color, ARRAY_SIZE(nodes[i]->color)); - break; - } - } - + json_add_node_decoration(response, nodes, &p->id); json_array_start(response, "channels"); json_add_uncommitted_channel(response, p->uncommitted_channel); @@ -716,13 +731,7 @@ static void gossipd_getpeers_complete(struct subd *gossip, const u8 *msg, /* Fake state. */ json_add_string(response, "state", "GOSSIPING"); json_add_pubkey(response, "id", ids+i); - for (size_t j = 0; j < tal_count(nodes); j++) { - if (nodes[j]->addresses != NULL && pubkey_eq(&nodes[j]->nodeid, ids+i)) { - json_add_string_escape(response, "alias", (char*)nodes[j]->alias); - json_add_hex(response, "color", nodes[j]->color, ARRAY_SIZE(nodes[j]->color)); - break; - } - } + json_add_node_decoration(response, nodes, ids+i); json_array_start(response, "netaddr"); if (addrs[i].type != ADDR_TYPE_PADDING) json_add_string(response, NULL, diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 31f7010bf..b829bf628 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -180,6 +180,10 @@ void invoices_waitone(const tal_t *ctx UNNEEDED, void json_add_bool(struct json_result *result UNNEEDED, const char *fieldname UNNEEDED, bool value UNNEEDED) { fprintf(stderr, "json_add_bool called!\n"); abort(); } +/* Generated stub for json_add_escaped_string */ +void json_add_escaped_string(struct json_result *result UNNEEDED, const char *fieldname UNNEEDED, + const struct json_escaped *esc TAKES UNNEEDED) +{ fprintf(stderr, "json_add_escaped_string called!\n"); abort(); } /* Generated stub for json_add_hex */ void json_add_hex(struct json_result *result UNNEEDED, const char *fieldname UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) @@ -205,10 +209,6 @@ void json_add_short_channel_id(struct json_result *response UNNEEDED, /* Generated stub for json_add_string */ void json_add_string(struct json_result *result UNNEEDED, const char *fieldname UNNEEDED, const char *value UNNEEDED) { fprintf(stderr, "json_add_string called!\n"); abort(); } -/* Generated stub for json_add_string_escape */ -void json_add_string_escape(struct json_result *result UNNEEDED, const char *fieldname UNNEEDED, - const char *value UNNEEDED) -{ fprintf(stderr, "json_add_string_escape called!\n"); abort(); } /* Generated stub for json_add_txid */ void json_add_txid(struct json_result *result UNNEEDED, const char *fieldname UNNEEDED, const struct bitcoin_txid *txid UNNEEDED) @@ -227,6 +227,9 @@ void json_array_end(struct json_result *ptr UNNEEDED) /* Generated stub for json_array_start */ void json_array_start(struct json_result *ptr UNNEEDED, const char *fieldname UNNEEDED) { fprintf(stderr, "json_array_start called!\n"); abort(); } +/* Generated stub for json_escape */ +struct json_escaped *json_escape(const tal_t *ctx UNNEEDED, const char *str TAKES UNNEEDED) +{ fprintf(stderr, "json_escape called!\n"); abort(); } /* Generated stub for json_get_params */ bool json_get_params(struct command *cmd UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t param[] UNNEEDED, ...)