common/json_escaped: new type which explicitly notes a string is already JSON.

Trivial to use as a string, but it still means you should be careful
around it.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2018-03-26 10:38:15 +10:30
parent a077c3defb
commit d92579f627
12 changed files with 207 additions and 96 deletions

View File

@ -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 \

View File

@ -1,5 +1,6 @@
/* JSON core and helpers */
#include "json.h"
#include "json_escaped.h"
#include <assert.h>
#include <ccan/build_assert/build_assert.h>
#include <ccan/str/hex/hex.h>
@ -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);

View File

@ -2,6 +2,7 @@
#define LIGHTNING_COMMON_JSON_H
#include "config.h"
#include <bitcoin/pubkey.h>
#include <ccan/take/take.h>
#include <ccan/tal/tal.h>
#include <stdbool.h>
#include <stdint.h>
@ -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);

86
common/json_escaped.c Normal file
View File

@ -0,0 +1,86 @@
#include <common/json_escaped.h>
#include <stdio.h>
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;
}

36
common/json_escaped.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef LIGHTNING_COMMON_JSON_ESCAPED_H
#define LIGHTNING_COMMON_JSON_ESCAPED_H
#include "config.h"
#include <common/json.h>
/* 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 */

View File

@ -1,4 +1,5 @@
#include "../json.c"
#include "../json_escaped.c"
#include <stdio.h>
/* 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();
}

View File

@ -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 \

View File

@ -10,6 +10,7 @@
#include <ccan/tal/str/str.h>
#include <common/bech32.h>
#include <common/bolt11.h>
#include <common/json_escaped.h>
#include <common/utils.h>
#include <errno.h>
#include <hsmd/gen_hsm_client_wire.h>
@ -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,

View File

@ -10,6 +10,7 @@
#include <ccan/str/hex/hex.h>
#include <ccan/tal/str/str.h>
#include <common/bech32.h>
#include <common/json_escaped.h>
#include <common/memleak.h>
#include <common/version.h>
#include <common/wireaddr.h>
@ -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)));
}

View File

@ -10,6 +10,7 @@
#include <ccan/tal/path/path.h>
#include <ccan/tal/str/str.h>
#include <common/configdir.h>
#include <common/json_escaped.h>
#include <common/memleak.h>
#include <common/version.h>
#include <common/wireaddr.h>
@ -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);
}

View File

@ -14,6 +14,7 @@
#include <common/dev_disconnect.h>
#include <common/features.h>
#include <common/initial_commit_tx.h>
#include <common/json_escaped.h>
#include <common/key_derive.h>
#include <common/status.h>
#include <common/timeout.h>
@ -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,

View File

@ -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, ...)