mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 21:35:11 +01:00
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:
parent
a077c3defb
commit
d92579f627
@ -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 \
|
||||
|
@ -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);
|
||||
|
@ -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
86
common/json_escaped.c
Normal 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
36
common/json_escaped.h
Normal 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 */
|
@ -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();
|
||||
}
|
||||
|
@ -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 \
|
||||
|
@ -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,
|
||||
|
@ -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)));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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, ...)
|
||||
|
Loading…
Reference in New Issue
Block a user