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 <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2018-03-26 10:38:47 +10:30
parent ed9093fcbd
commit 7ae013202f
7 changed files with 87 additions and 17 deletions

View File

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

View File

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

View File

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

View File

@ -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);

View File

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

View File

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

View File

@ -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();
}