From 7ae013202fc737a53a871c9945226d7472ff5eed Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 26 Mar 2018 10:38:47 +1030 Subject: [PATCH] json: make json_add_string do partial escapes. This is useful when we log a JSON-escaped string, so we don't double-escape. Signed-off-by: Rusty Russell --- cli/Makefile | 1 + cli/test/Makefile | 1 + common/json.c | 13 +++---------- common/json.h | 2 +- common/json_escaped.c | 39 ++++++++++++++++++++++++++++++++++++- common/json_escaped.h | 4 ++++ common/test/run-json.c | 44 +++++++++++++++++++++++++++++++++++++----- 7 files changed, 87 insertions(+), 17 deletions(-) diff --git a/cli/Makefile b/cli/Makefile index cad59b381..e752e6bfa 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -4,6 +4,7 @@ LIGHTNING_CLI_OBJS := $(LIGHTNING_CLI_SRC:.c=.o) LIGHTNING_CLI_COMMON_OBJS := \ common/configdir.o \ common/json.o \ + common/json_escaped.o \ common/version.o lightning-cli-all: cli/lightning-cli diff --git a/cli/test/Makefile b/cli/test/Makefile index 5846cdff6..abae776a8 100644 --- a/cli/test/Makefile +++ b/cli/test/Makefile @@ -12,6 +12,7 @@ CLI_TEST_COMMON_OBJS := \ common/daemon_conn.o \ common/htlc_state.o \ common/json.o \ + common/json_escaped.o \ common/pseudorand.o \ common/memleak.o \ common/msg_queue.o \ diff --git a/common/json.c b/common/json.c index 0ee16a3b7..19a2029e7 100644 --- a/common/json.c +++ b/common/json.c @@ -409,18 +409,11 @@ void json_add_literal(struct json_result *result, const char *fieldname, void json_add_string(struct json_result *result, const char *fieldname, const char *value) { - char *escaped = tal_strdup(result, value); - size_t i; + struct json_escaped *esc = json_partial_escape(NULL, value); json_start_member(result, fieldname); - for (i = 0; escaped[i]; i++) { - /* Replace any funny business. Better safe than accurate! */ - if (escaped[i] == '\\' - || escaped[i] == '"' - || !cisprint(escaped[i])) - escaped[i] = '?'; - } - result_append_fmt(result, "\"%s\"", escaped); + result_append_fmt(result, "\"%s\"", esc->s); + tal_free(esc); } void json_add_bool(struct json_result *result, const char *fieldname, bool value) diff --git a/common/json.h b/common/json.h index b8648bd01..81265d993 100644 --- a/common/json.h +++ b/common/json.h @@ -76,7 +76,7 @@ void json_object_end(struct json_result *ptr); struct json_result *new_json_result(const tal_t *ctx); /* '"fieldname" : "value"' or '"value"' if fieldname is NULL. Turns - * any unusual chars into ?. + * any non-printable chars into JSON escapes, but leaves existing escapes alone. */ void json_add_string(struct json_result *result, const char *fieldname, const char *value); diff --git a/common/json_escaped.c b/common/json_escaped.c index 7722f95ed..595a26c73 100644 --- a/common/json_escaped.c +++ b/common/json_escaped.c @@ -35,7 +35,9 @@ bool json_escaped_eq(const struct json_escaped *a, return streq(a->s, b->s); } -struct json_escaped *json_escape(const tal_t *ctx, const char *str TAKES) +static struct json_escaped *escape(const tal_t *ctx, + const char *str TAKES, + bool partial) { struct json_escaped *esc; size_t i, n; @@ -62,6 +64,31 @@ struct json_escaped *json_escape(const tal_t *ctx, const char *str TAKES) escape = 'r'; break; case '\\': + if (partial) { + /* Don't double-escape standard escapes. */ + if (str[i+1] == 'n' + || str[i+1] == 'b' + || str[i+1] == 'f' + || str[i+1] == 't' + || str[i+1] == 'r' + || str[i+1] == '/' + || str[i+1] == '\\' + || str[i+1] == '"') { + escape = str[i+1]; + i++; + break; + } + if (str[i+1] == 'u' + && cisxdigit(str[i+2]) + && cisxdigit(str[i+3]) + && cisxdigit(str[i+4]) + && cisxdigit(str[i+5])) { + memcpy(esc->s + n, str + i, 6); + n += 5; + i += 5; + continue; + } + } /* fall thru */ case '"': escape = str[i]; break; @@ -85,6 +112,16 @@ struct json_escaped *json_escape(const tal_t *ctx, const char *str TAKES) return esc; } +struct json_escaped *json_partial_escape(const tal_t *ctx, const char *str TAKES) +{ + return escape(ctx, str, true); +} + +struct json_escaped *json_escape(const tal_t *ctx, const char *str TAKES) +{ + return escape(ctx, str, false); +} + /* By policy, we don't handle \u. Use UTF-8. */ const char *json_escaped_unescape(const tal_t *ctx, const struct json_escaped *esc) diff --git a/common/json_escaped.h b/common/json_escaped.h index 62a2a0a82..fe5b29eeb 100644 --- a/common/json_escaped.h +++ b/common/json_escaped.h @@ -12,6 +12,10 @@ struct json_escaped { /* @str be a valid UTF-8 string */ struct json_escaped *json_escape(const tal_t *ctx, const char *str TAKES); +/* @str is a valid UTF-8 string which may already contain escapes. */ +struct json_escaped *json_partial_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, diff --git a/common/test/run-json.c b/common/test/run-json.c index 0f857b642..d63658f1d 100644 --- a/common/test/run-json.c +++ b/common/test/run-json.c @@ -67,12 +67,13 @@ static int test_json_filter(void) x = json_get_member(str, toks, "x"); assert(x); assert(x->type == JSMN_STRING); - assert((x->end - x->start) == 255); + + /* There are 7 one-letter escapes, and (32-5) \uXXXX escapes. */ + assert((x->end - x->start) == 255 + 7*1 + (32-5)*5); + /* No control characters. */ for (i = x->start; i < x->end; i++) { - assert(cisprint(str[i])); - assert(str[i] != '\\'); - assert(str[i] != '"'); - assert(str[i] == '?' || str[i] == badstr[i - x->start]); + assert((unsigned)str[i] >= ' '); + assert((unsigned)str[i] != 127); } tal_free(result); return 0; @@ -112,11 +113,44 @@ static void test_json_escape(void) } } +static void test_json_partial(void) +{ + const tal_t *ctx = tal(NULL, char); + + assert(streq(json_partial_escape(ctx, "\\")->s, "\\\\")); + assert(streq(json_partial_escape(ctx, "\\\\")->s, "\\\\")); + assert(streq(json_partial_escape(ctx, "\\\\\\")->s, "\\\\\\\\")); + assert(streq(json_partial_escape(ctx, "\\\\\\\\")->s, "\\\\\\\\")); + assert(streq(json_partial_escape(ctx, "\\n")->s, "\\n")); + assert(streq(json_partial_escape(ctx, "\n")->s, "\\n")); + assert(streq(json_partial_escape(ctx, "\\\"")->s, "\\\"")); + assert(streq(json_partial_escape(ctx, "\"")->s, "\\\"")); + assert(streq(json_partial_escape(ctx, "\\t")->s, "\\t")); + assert(streq(json_partial_escape(ctx, "\t")->s, "\\t")); + assert(streq(json_partial_escape(ctx, "\\b")->s, "\\b")); + assert(streq(json_partial_escape(ctx, "\b")->s, "\\b")); + assert(streq(json_partial_escape(ctx, "\\r")->s, "\\r")); + assert(streq(json_partial_escape(ctx, "\r")->s, "\\r")); + assert(streq(json_partial_escape(ctx, "\\f")->s, "\\f")); + assert(streq(json_partial_escape(ctx, "\f")->s, "\\f")); + /* You're allowed to escape / according to json.org. */ + assert(streq(json_partial_escape(ctx, "\\/")->s, "\\/")); + assert(streq(json_partial_escape(ctx, "/")->s, "/")); + + assert(streq(json_partial_escape(ctx, "\\u0FFF")->s, "\\u0FFF")); + assert(streq(json_partial_escape(ctx, "\\u0FFFx")->s, "\\u0FFFx")); + + /* Unknown escapes should be escaped. */ + assert(streq(json_partial_escape(ctx, "\\x")->s, "\\\\x")); + tal_free(ctx); +} + int main(void) { test_json_tok_bitcoin_amount(); test_json_filter(); test_json_escape(); + test_json_partial(); assert(!taken_any()); take_cleanup(); }