Skip to content

Commit

Permalink
json: make json_add_string do partial escapes.
Browse files Browse the repository at this point in the history
This is useful when we log a JSON-escaped string, so we don't double-escape.

Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell committed Mar 26, 2018
1 parent ed9093f commit 7ae0132
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 17 deletions.
1 change: 1 addition & 0 deletions cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions cli/test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
13 changes: 3 additions & 10 deletions common/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion common/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
39 changes: 38 additions & 1 deletion common/json_escaped.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions common/json_escaped.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
44 changes: 39 additions & 5 deletions common/test/run-json.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}

0 comments on commit 7ae0132

Please sign in to comment.