Browse Source
These are generalized from our internal implementations. The main difference is that 'struct json_escaped' is now 'struct json_escape', so we replace that immediately. The difference between lightningd's json-writing ringbuffer and the more generic ccan/json_out is that the latter has a better API and handles escaping transparently if something slips through (though it does offer direct accessors so you can mess things up yourself!). Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>pull/2938/head
Rusty Russell
6 years ago
45 changed files with 1156 additions and 191 deletions
@ -0,0 +1 @@ |
|||
../../licenses/BSD-MIT |
@ -0,0 +1,39 @@ |
|||
#include "config.h" |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
/** |
|||
* json_escape - Escape sequences for JSON strings |
|||
* |
|||
* This code helps you format strings into forms useful for JSON. |
|||
* |
|||
* Author: Rusty Russell <rusty@rustcorp.com.au> |
|||
* License: BSD-MIT |
|||
* Example: |
|||
* // Print arguments as a JSON array. |
|||
* #include <ccan/json_escape/json_escape.h> |
|||
* |
|||
* int main(int argc, char *argv[]) |
|||
* { |
|||
* printf("["); |
|||
* for (int i = 1; i < argc; i++) { |
|||
* struct json_escape *e = json_escape(NULL, argv[i]); |
|||
* printf("%s\"%s\"", i == 1 ? "" : ",", e->s); |
|||
* } |
|||
* printf("]\n"); |
|||
* return 0; |
|||
* } |
|||
*/ |
|||
int main(int argc, char *argv[]) |
|||
{ |
|||
/* Expect exactly one argument */ |
|||
if (argc != 2) |
|||
return 1; |
|||
|
|||
if (strcmp(argv[1], "depends") == 0) { |
|||
printf("ccan/tal\n"); |
|||
return 0; |
|||
} |
|||
|
|||
return 1; |
|||
} |
@ -0,0 +1,44 @@ |
|||
/* MIT (BSD) license - see LICENSE file for details */ |
|||
#ifndef CCAN_JSON_ESCAPE_H |
|||
#define CCAN_JSON_ESCAPE_H |
|||
#include "config.h" |
|||
#include <ccan/tal/tal.h> |
|||
|
|||
/* Type differentiation for a correctly-escaped JSON string */ |
|||
struct json_escape { |
|||
/* NUL terminated string. */ |
|||
char s[1]; |
|||
}; |
|||
|
|||
/**
|
|||
* json_escape - escape a valid UTF-8 string. |
|||
* @ctx: tal context to allocate from. |
|||
* @str: the string to escape. |
|||
* |
|||
* Allocates and returns a valid JSON string (without surrounding quotes). |
|||
*/ |
|||
struct json_escape *json_escape(const tal_t *ctx, const char *str TAKES); |
|||
|
|||
/* Version with @len */ |
|||
struct json_escape *json_escape_len(const tal_t *ctx, |
|||
const char *str TAKES, size_t len); |
|||
|
|||
/* @str is a valid UTF-8 string which may already contain escapes. */ |
|||
struct json_escape *json_partial_escape(const tal_t *ctx, |
|||
const char *str TAKES); |
|||
|
|||
/* Do we need to escape this str? */ |
|||
bool json_escape_needed(const char *str, size_t len); |
|||
|
|||
/* Are two escape json strings identical? */ |
|||
bool json_escape_eq(const struct json_escape *a, |
|||
const struct json_escape *b); |
|||
|
|||
/* Internal routine for creating json_escape from bytes. */ |
|||
struct json_escape *json_escape_string_(const tal_t *ctx, |
|||
const void *bytes, size_t len); |
|||
|
|||
/* Be very careful here! Can fail! Doesn't handle \u: use UTF-8 please. */ |
|||
const char *json_escape_unescape(const tal_t *ctx, |
|||
const struct json_escape *esc); |
|||
#endif /* CCAN_JSON_ESCAPE_H */ |
@ -0,0 +1,41 @@ |
|||
#include <ccan/json_escape/json_escape.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/json_escape/json_escape.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
const tal_t *ctx = tal(NULL, char); |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(21); |
|||
|
|||
ok1(!strcmp(json_partial_escape(ctx, "\\")->s, "\\\\")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\\\")->s, "\\\\")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\\\\\")->s, "\\\\\\\\")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\\\\\\\")->s, "\\\\\\\\")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\n")->s, "\\n")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\n")->s, "\\n")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\\"")->s, "\\\"")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\"")->s, "\\\"")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\t")->s, "\\t")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\t")->s, "\\t")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\b")->s, "\\b")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\b")->s, "\\b")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\r")->s, "\\r")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\r")->s, "\\r")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\f")->s, "\\f")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\f")->s, "\\f")); |
|||
/* You're allowed to escape / according to json.org. */ |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\/")->s, "\\/")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "/")->s, "/")); |
|||
|
|||
ok1(!strcmp(json_partial_escape(ctx, "\\u0FFF")->s, "\\u0FFF")); |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\u0FFFx")->s, "\\u0FFFx")); |
|||
|
|||
/* Unknown escapes should be escaped. */ |
|||
ok1(!strcmp(json_partial_escape(ctx, "\\x")->s, "\\\\x")); |
|||
tal_free(ctx); |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,35 @@ |
|||
#include <ccan/json_escape/json_escape.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/json_escape/json_escape.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
const tal_t *ctx = tal(NULL, char); |
|||
struct json_escape *e; |
|||
char *p; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(5); |
|||
|
|||
/* This should simply be tal_steal */ |
|||
p = tal_dup_arr(NULL, char, "Hello", 6, 0); |
|||
e = json_escape(ctx, take(p)); |
|||
ok1(!strcmp(e->s, "Hello")); |
|||
ok1((void *)e == (void *)p); |
|||
ok1(tal_parent(e) == ctx); |
|||
|
|||
/* This can't be tal_steal, but still should be freed. */ |
|||
p = tal_dup_arr(NULL, char, |
|||
"\\\b\f\n\r\t\"" |
|||
"\\\\\\b\\f\\n\\r\\t\\\"", 22, 0); |
|||
e = json_escape(ctx, take(p)); |
|||
ok1(tal_parent(e) == ctx); |
|||
ok1(!strcmp(e->s, |
|||
"\\\\\\b\\f\\n\\r\\t\\\"" |
|||
"\\\\\\\\\\\\b\\\\f\\\\n\\\\r\\\\t\\\\\\\"")); |
|||
tal_free(ctx); |
|||
|
|||
/* This exits depending on whether all tests passed */ |
|||
return exit_status(); |
|||
} |
@ -0,0 +1,44 @@ |
|||
#include <ccan/json_escape/json_escape.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/json_escape/json_escape.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
const tal_t *ctx = tal(NULL, char); |
|||
struct json_escape *e; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(6); |
|||
|
|||
e = json_escape(ctx, "Hello"); |
|||
ok1(!strcmp(e->s, "Hello")); |
|||
ok1(!strcmp(json_escape_unescape(ctx, e), |
|||
"Hello")); |
|||
|
|||
e = json_escape(ctx, |
|||
"\\\b\f\n\r\t\"" |
|||
"\\\\\\b\\f\\n\\r\\t\\\""); |
|||
ok1(!strcmp(e->s, |
|||
"\\\\\\b\\f\\n\\r\\t\\\"" |
|||
"\\\\\\\\\\\\b\\\\f\\\\n\\\\r\\\\t\\\\\\\"")); |
|||
ok1(!strcmp(json_escape_unescape(ctx, e), |
|||
"\\\b\f\n\r\t\"" |
|||
"\\\\\\b\\f\\n\\r\\t\\\"")); |
|||
|
|||
/* This one doesn't escape the already-escaped chars */ |
|||
e = json_partial_escape(ctx, |
|||
"\\\b\f\n\r\t\"" |
|||
"\\\\\\b\\f\\n\\r\\t\\\""); |
|||
ok1(!strcmp(e->s, |
|||
"\\\\\\b\\f\\n\\r\\t\\\"" |
|||
"\\\\\\b\\f\\n\\r\\t\\\"")); |
|||
ok1(!strcmp(json_escape_unescape(ctx, e), |
|||
"\\\b\f\n\r\t\"" |
|||
"\\\b\f\n\r\t\"")); |
|||
|
|||
tal_free(ctx); |
|||
|
|||
/* This exits depending on whether all tests passed */ |
|||
return exit_status(); |
|||
} |
@ -0,0 +1 @@ |
|||
../../licenses/BSD-MIT |
@ -0,0 +1,82 @@ |
|||
#include "config.h" |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
/** |
|||
* json_out - Code for creating simple JSON output. |
|||
* |
|||
* This code helps you create well-formed JSON strings. |
|||
* |
|||
* Author: Rusty Russell <rusty@rustcorp.com.au> |
|||
* License: BSD-MIT |
|||
* |
|||
* Example: |
|||
* // Given "a 1 true" outputs {"argv1":"a","argv2":1,"argv3":true} |
|||
* // Print arguments as a JSON array. |
|||
* #include <ccan/json_out/json_out.h> |
|||
* #include <stdio.h> |
|||
* #include <string.h> |
|||
* #include <unistd.h> |
|||
* |
|||
* // Simplistic test to see if str needs quotes. |
|||
* static bool can_be_json_literal(const char *str) |
|||
* { |
|||
* char *endp; |
|||
* if (strtol(str, &endp, 10) != LONG_MIN |
|||
* && endp != str |
|||
* && *endp == '\0') |
|||
* return true; |
|||
* return !strcmp(str, "true") |
|||
* || !strcmp(str, "false") |
|||
* || !strcmp(str, "null"); |
|||
* } |
|||
* |
|||
* int main(int argc, char *argv[]) |
|||
* { |
|||
* struct json_out *jout = json_out_new(NULL); |
|||
* size_t len; |
|||
* const char *p; |
|||
* |
|||
* json_out_start(jout, NULL, '{'); |
|||
* for (int i = 1; i < argc; i++) { |
|||
* char fieldname[80]; |
|||
* sprintf(fieldname, "argv%i", i); |
|||
* json_out_add(jout, fieldname, |
|||
* !can_be_json_literal(argv[i]), |
|||
* "%s", argv[i]); |
|||
* } |
|||
* json_out_end(jout, '}'); |
|||
* // Force appending of \n |
|||
* json_out_direct(jout, 1)[0] = '\n'; |
|||
* json_out_finished(jout); |
|||
* |
|||
* // Now write it out. |
|||
* while ((p = json_out_contents(jout, &len)) != NULL) { |
|||
* int i = write(STDOUT_FILENO, p, len); |
|||
* if (i <= 0) |
|||
* exit(1); |
|||
* json_out_consume(jout, i); |
|||
* } |
|||
* |
|||
* tal_free(jout); |
|||
* return 0; |
|||
* } |
|||
* |
|||
*/ |
|||
int main(int argc, char *argv[]) |
|||
{ |
|||
/* Expect exactly one argument */ |
|||
if (argc != 2) |
|||
return 1; |
|||
|
|||
if (strcmp(argv[1], "depends") == 0) { |
|||
printf("ccan/compiler\n"); |
|||
printf("ccan/json_escape\n"); |
|||
printf("ccan/membuf\n"); |
|||
printf("ccan/tal\n"); |
|||
printf("ccan/typesafe_cb\n"); |
|||
return 0; |
|||
} |
|||
|
|||
return 1; |
|||
} |
@ -0,0 +1,356 @@ |
|||
/* MIT (BSD) license - see LICENSE file for details */ |
|||
#include <ccan/json_escape/json_escape.h> |
|||
#include <ccan/json_out/json_out.h> |
|||
#include <ccan/membuf/membuf.h> |
|||
#include <stdarg.h> |
|||
#include <stdio.h> |
|||
|
|||
struct json_out { |
|||
/* Callback if we reallocate. */ |
|||
void (*move_cb)(struct json_out *jout, ptrdiff_t delta, void *arg); |
|||
void *cb_arg; |
|||
|
|||
#ifdef CCAN_JSON_OUT_DEBUG |
|||
/* tal_arr of types ( or [ we're enclosed in. NULL if oom. */ |
|||
char *wrapping; |
|||
#endif |
|||
/* True if we haven't yet put an element in current wrapping */ |
|||
bool empty; |
|||
|
|||
/* Output. */ |
|||
MEMBUF(char) outbuf; |
|||
}; |
|||
|
|||
/* Realloc helper for tal membufs */ |
|||
static void *membuf_tal_realloc(struct membuf *mb, |
|||
void *rawelems, size_t newsize) |
|||
{ |
|||
char *p = rawelems; |
|||
|
|||
if (!tal_resize(&p, newsize)) |
|||
return NULL; |
|||
return p; |
|||
} |
|||
|
|||
struct json_out *json_out_new(const tal_t *ctx) |
|||
{ |
|||
struct json_out *jout = tal(ctx, struct json_out); |
|||
char *pool; |
|||
|
|||
if (!jout) |
|||
return NULL; |
|||
pool = tal_arr(jout, char, 64); |
|||
if (!pool) |
|||
return tal_free(jout); |
|||
|
|||
membuf_init(&jout->outbuf, pool, tal_count(pool), membuf_tal_realloc); |
|||
#ifdef CCAN_JSON_OUT_DEBUG |
|||
jout->wrapping = tal_arr(jout, char, 0); |
|||
#endif |
|||
jout->empty = true; |
|||
jout->move_cb = NULL; |
|||
return jout; |
|||
} |
|||
|
|||
void json_out_call_on_move_(struct json_out *jout, |
|||
void (*cb)(struct json_out *jout, ptrdiff_t delta, |
|||
void *arg), |
|||
void *arg) |
|||
{ |
|||
if (cb) |
|||
assert(!jout->move_cb); |
|||
jout->move_cb = cb; |
|||
jout->cb_arg = arg; |
|||
} |
|||
|
|||
struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src) |
|||
{ |
|||
size_t num_elems = membuf_num_elems(&src->outbuf); |
|||
char *elems = membuf_elems(&src->outbuf); |
|||
struct json_out *jout = tal_dup(ctx, struct json_out, src); |
|||
char *pool; |
|||
|
|||
if (!jout) |
|||
return NULL; |
|||
pool = tal_dup_arr(jout, char, elems, num_elems, 0); |
|||
if (!pool) |
|||
return tal_free(jout); |
|||
membuf_init(&jout->outbuf, pool, num_elems, membuf_tal_realloc); |
|||
membuf_added(&jout->outbuf, num_elems); |
|||
#ifdef CCAN_JSON_OUT_DEBUG |
|||
jout->wrapping = tal_dup_arr(jout, char, |
|||
jout->wrapping, tal_count(jout->wrapping), |
|||
0); |
|||
#endif |
|||
return jout; |
|||
} |
|||
|
|||
static void indent(struct json_out *jout, char type) |
|||
{ |
|||
#ifdef CCAN_JSON_OUT_DEBUG |
|||
/* Can't check if we ran out of memory. */ |
|||
if (jout->wrapping) { |
|||
size_t n = tal_count(jout->wrapping); |
|||
if (!tal_resize(&jout->wrapping, n+1)) |
|||
jout->wrapping = tal_free(jout->wrapping); |
|||
else |
|||
jout->wrapping[n] = type; |
|||
} |
|||
#endif |
|||
jout->empty = true; |
|||
} |
|||
|
|||
static void unindent(struct json_out *jout, char type) |
|||
{ |
|||
#ifdef CCAN_JSON_OUT_DEBUG |
|||
/* Can't check if we ran out of memory. */ |
|||
if (jout->wrapping) { |
|||
size_t indent = tal_count(jout->wrapping); |
|||
assert(indent > 0); |
|||
/* Both [ and ] and { and } are two apart in ASCII */ |
|||
assert(jout->wrapping[indent-1] == type - 2); |
|||
tal_resize(&jout->wrapping, indent-1); |
|||
} |
|||
#endif |
|||
jout->empty = false; |
|||
} |
|||
|
|||
/* Make sure jout->outbuf has room for len: return pointer */ |
|||
static char *mkroom(struct json_out *jout, size_t len) |
|||
{ |
|||
ptrdiff_t delta = membuf_prepare_space(&jout->outbuf, len); |
|||
|
|||
if (delta && jout->move_cb) |
|||
jout->move_cb(jout, delta, jout->cb_arg); |
|||
|
|||
return membuf_space(&jout->outbuf); |
|||
} |
|||
|
|||
static void check_fieldname(const struct json_out *jout, |
|||
const char *fieldname) |
|||
{ |
|||
#ifdef CCAN_JSON_OUT_DEBUG |
|||
/* We don't escape this for you */ |
|||
assert(!fieldname || !json_escape_needed(fieldname, strlen(fieldname))); |
|||
|
|||
/* Can't check anything else if we ran out of memory. */ |
|||
if (jout->wrapping) { |
|||
size_t n = tal_count(jout->wrapping); |
|||
if (n == 0) |
|||
/* Can't have a fieldname if not in anything! */ |
|||
assert(!fieldname); |
|||
else if (jout->wrapping[n-1] == '[') |
|||
/* No fieldnames in arrays. */ |
|||
assert(!fieldname); |
|||
else { |
|||
/* Must have fieldnames in objects. */ |
|||
assert(fieldname); |
|||
} |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
char *json_out_member_direct(struct json_out *jout, |
|||
const char *fieldname, size_t extra) |
|||
{ |
|||
char *dest; |
|||
|
|||
/* Prepend comma if required. */ |
|||
if (!jout->empty) |
|||
extra++; |
|||
|
|||
check_fieldname(jout, fieldname); |
|||
if (fieldname) |
|||
extra += 1 + strlen(fieldname) + 2; |
|||
|
|||
dest = mkroom(jout, extra); |
|||
if (!dest) |
|||
goto out; |
|||
|
|||
if (!jout->empty) |
|||
*(dest++) = ','; |
|||
if (fieldname) { |
|||
*(dest++) = '"'; |
|||
memcpy(dest, fieldname, strlen(fieldname)); |
|||
dest += strlen(fieldname); |
|||
*(dest++) = '"'; |
|||
*(dest++) = ':'; |
|||
} |
|||
membuf_added(&jout->outbuf, extra); |
|||
|
|||
out: |
|||
jout->empty = false; |
|||
return dest; |
|||
} |
|||
|
|||
bool json_out_start(struct json_out *jout, const char *fieldname, char type) |
|||
{ |
|||
char *p; |
|||
|
|||
assert(type == '[' || type == '{'); |
|||
p = json_out_member_direct(jout, fieldname, 1); |
|||
if (p) |
|||
p[0] = type; |
|||
indent(jout, type); |
|||
|
|||
return p != NULL; |
|||
} |
|||
|
|||
bool json_out_end(struct json_out *jout, char type) |
|||
{ |
|||
char *p; |
|||
|
|||
assert(type == '}' || type == ']'); |
|||
p = json_out_direct(jout, 1); |
|||
if (p) |
|||
p[0] = type; |
|||
unindent(jout, type); |
|||
|
|||
return p != NULL; |
|||
} |
|||
|
|||
bool json_out_addv(struct json_out *jout, |
|||
const char *fieldname, |
|||
bool quote, |
|||
const char *fmt, |
|||
va_list ap) |
|||
{ |
|||
size_t fmtlen, avail; |
|||
va_list ap2; |
|||
char *dst; |
|||
|
|||
if (!json_out_member_direct(jout, fieldname, 0)) |
|||
return false; |
|||
|
|||
/* Make a copy in case we need it below. */ |
|||
va_copy(ap2, ap); |
|||
|
|||
/* We can use any additional space, but need room for ". */ |
|||
avail = membuf_num_space(&jout->outbuf); |
|||
if (quote) { |
|||
if (avail < 2) |
|||
avail = 0; |
|||
else |
|||
avail -= 2; |
|||
} |
|||
|
|||
/* Try printing in place first. */ |
|||
dst = membuf_space(&jout->outbuf); |
|||
fmtlen = vsnprintf(dst + quote, avail, fmt, ap); |
|||
|
|||
/* Horrible subtlety: vsnprintf *will* NUL terminate, even if it means
|
|||
* chopping off the last character. So if fmtlen == |
|||
* membuf_num_space(&jout->outbuf), the result was truncated! */ |
|||
if (fmtlen + (int)quote*2 >= membuf_num_space(&jout->outbuf)) { |
|||
/* Make room for NUL terminator, even though we don't want it */ |
|||
dst = mkroom(jout, fmtlen + 1 + (int)quote*2); |
|||
if (!dst) |
|||
goto out; |
|||
vsprintf(dst + quote, fmt, ap2); |
|||
} |
|||
|
|||
#ifdef CCAN_JSON_OUT_DEBUG |
|||
/* You're not inserting junk here, are you? */ |
|||
assert(quote || !json_escape_needed(dst, fmtlen)); |
|||
#endif |
|||
|
|||
/* Of course, if we need to escape it, we have to redo it all. */ |
|||
if (quote) { |
|||
if (json_escape_needed(dst + quote, fmtlen)) { |
|||
struct json_escape *e; |
|||
e = json_escape_len(NULL, dst + quote, fmtlen); |
|||
fmtlen = strlen(e->s); |
|||
dst = mkroom(jout, fmtlen + (int)quote*2); |
|||
if (!dst) |
|||
goto out; |
|||
memcpy(dst + quote, e, fmtlen); |
|||
tal_free(e); |
|||
} |
|||
dst[0] = '"'; |
|||
dst[fmtlen+1] = '"'; |
|||
} |
|||
membuf_added(&jout->outbuf, fmtlen + (int)quote*2); |
|||
|
|||
out: |
|||
va_end(ap2); |
|||
return dst != NULL; |
|||
} |
|||
|
|||
bool json_out_add(struct json_out *jout, |
|||
const char *fieldname, |
|||
bool quote, |
|||
const char *fmt, ...) |
|||
{ |
|||
va_list ap; |
|||
bool ret; |
|||
|
|||
va_start(ap, fmt); |
|||
ret = json_out_addv(jout, fieldname, quote, fmt, ap); |
|||
va_end(ap); |
|||
return ret; |
|||
} |
|||
|
|||
bool json_out_addstr(struct json_out *jout, |
|||
const char *fieldname, |
|||
const char *str) |
|||
{ |
|||
size_t len = strlen(str); |
|||
char *p; |
|||
struct json_escape *e; |
|||
|
|||
if (json_escape_needed(str, len)) { |
|||
e = json_escape(NULL, str); |
|||
str = e->s; |
|||
len = strlen(str); |
|||
} else |
|||
e = NULL; |
|||
|
|||
p = json_out_member_direct(jout, fieldname, len + 2); |
|||
if (p) { |
|||
p[0] = p[1+len] = '"'; |
|||
memcpy(p+1, str, len); |
|||
} |
|||
tal_free(e); |
|||
|
|||
return p != NULL; |
|||
} |
|||
|
|||
bool json_out_add_splice(struct json_out *jout, |
|||
const char *fieldname, |
|||
const struct json_out *src) |
|||
{ |
|||
const char *p; |
|||
size_t len; |
|||
|
|||
p = json_out_contents(src, &len); |
|||
if (!p) |
|||
return false; |
|||
memcpy(json_out_member_direct(jout, fieldname, len), p, len); |
|||
return true; |
|||
} |
|||
|
|||
char *json_out_direct(struct json_out *jout, size_t len) |
|||
{ |
|||
char *p = mkroom(jout, len); |
|||
if (p) |
|||
membuf_added(&jout->outbuf, len); |
|||
return p; |
|||
} |
|||
|
|||
void json_out_finished(const struct json_out *jout) |
|||
{ |
|||
#ifdef CCAN_JSON_OUT_DEBUG |
|||
assert(tal_count(jout->wrapping) == 0); |
|||
#endif |
|||
} |
|||
|
|||
const char *json_out_contents(const struct json_out *jout, size_t *len) |
|||
{ |
|||
*len = membuf_num_elems(&jout->outbuf); |
|||
return *len ? membuf_elems(&jout->outbuf) : NULL; |
|||
} |
|||
|
|||
void json_out_consume(struct json_out *jout, size_t len) |
|||
{ |
|||
membuf_consume(&jout->outbuf, len); |
|||
} |
@ -0,0 +1,200 @@ |
|||
/* MIT (BSD) license - see LICENSE file for details */ |
|||
#ifndef CCAN_JSON_OUT_H |
|||
#define CCAN_JSON_OUT_H |
|||
#include <ccan/compiler/compiler.h> |
|||
#include <ccan/tal/tal.h> |
|||
#include <ccan/typesafe_cb/typesafe_cb.h> |
|||
#include <stddef.h> |
|||
|
|||
struct json_out; |
|||
|
|||
/**
|
|||
* json_out_new - allocate a json_out stream. |
|||
* @ctx: the tal_context to allocate from, or NULL |
|||
* |
|||
* Returns NULL if tal allocation fails. |
|||
*/ |
|||
struct json_out *json_out_new(const tal_t *ctx); |
|||
|
|||
/**
|
|||
* json_out_call_on_move - callback for when buffer is reallocated. |
|||
* @jout: the json_out object to attach to. |
|||
* @cb: the callback to call. |
|||
* @arg: the argument to @cb (must match type). |
|||
* |
|||
* A NULL @cb disables. You can't currently have more than one callback. |
|||
* The @delta argument to @cb is the difference between the old location |
|||
* and the new one, and is never zero. |
|||
*/ |
|||
#define json_out_call_on_move(jout, cb, arg) \ |
|||
json_out_call_on_move_((jout), \ |
|||
typesafe_cb_preargs(void, void *, \ |
|||
(cb), (arg), \ |
|||
struct json_out *, \ |
|||
ptrdiff_t), \ |
|||
(arg)) |
|||
|
|||
void json_out_call_on_move_(struct json_out *jout, |
|||
void (*cb)(struct json_out *jout, ptrdiff_t delta, |
|||
void *arg), |
|||
void *arg); |
|||
|
|||
/**
|
|||
* json_out_dup - duplicate a json_out stream. |
|||
* @ctx: the tal_context to allocate from, or NULL |
|||
* @src: the json_out to copy. |
|||
*/ |
|||
struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src); |
|||
|
|||
/**
|
|||
* json_out_start - start an array or object. |
|||
* @jout: the json_out object to write into. |
|||
* @fieldname: the fieldname, if inside an object, or NULL if inside an array. |
|||
* @type: '[' or '{' to start an array or object, respectively. |
|||
* |
|||
* Returns true unless tal_resize() fails. |
|||
* Literally writes '"@fieldname": @type' or '@type ' if fieldname is NULL. |
|||
* @fieldname must not need JSON escaping. |
|||
*/ |
|||
bool json_out_start(struct json_out *jout, const char *fieldname, char type); |
|||
|
|||
/**
|
|||
* json_out_end - end an array or object. |
|||
* @jout: the json_out object to write into. |
|||
* @type: '}' or ']' to end an array or object, respectively. |
|||
* |
|||
* Returns true unless tal_resize() fails. |
|||
* |
|||
* Literally writes ']' or '}', keeping track of whether we need to append |
|||
* a comma. |
|||
*/ |
|||
bool json_out_end(struct json_out *jout, char type); |
|||
|
|||
/**
|
|||
* json_out_add - add a formatted member. |
|||
* @jout: the json_out object to write into. |
|||
* @fieldname: optional fieldname to prepend (must not need escaping). |
|||
* @quote: if true, surround fmt by " and ". |
|||
* @fmt...: the printf-style format |
|||
* |
|||
* Returns true unless tal_resize() fails. |
|||
* |
|||
* If you're in an array, @fieldname must be NULL. If you're in an |
|||
* object, @fieldname must be non-NULL. This is checked if |
|||
* CCAN_JSON_OUT_DEBUG is defined. |
|||
* @fieldname must not need JSON escaping. |
|||
* |
|||
* If the resulting string requires escaping, and @quote is true, we |
|||
* call json_escape(). |
|||
*/ |
|||
PRINTF_FMT(4,5) |
|||
bool json_out_add(struct json_out *jout, |
|||
const char *fieldname, |
|||
bool quote, |
|||
const char *fmt, ...); |
|||
|
|||
/**
|
|||
* json_out_addv - add a formatted member (vararg variant) |
|||
* @jout: the json_out object to write into. |
|||
* @fieldname: optional fieldname to prepend. |
|||
* @quote: if true, surround fmt by " and ". |
|||
* @fmt: the printf-style format |
|||
* @ap: the argument list. |
|||
* |
|||
* See json_out_add() above. |
|||
*/ |
|||
bool json_out_addv(struct json_out *jout, |
|||
const char *fieldname, |
|||
bool quote, |
|||
const char *fmt, |
|||
va_list ap); |
|||
|
|||
/**
|
|||
* json_out_addstr - convenience helper to add a string field. |
|||
* @jout: the json_out object to write into. |
|||
* @fieldname: optional fieldname to prepend. |
|||
* @str: the string to add (must not be NULL). |
|||
* |
|||
* Equivalent to json_out_add(@jout, @fieldname, true, "%s", @str); |
|||
*/ |
|||
bool json_out_addstr(struct json_out *jout, |
|||
const char *fieldname, |
|||
const char *str); |
|||
|
|||
/**
|
|||
* json_out_member_direct - add a field, with direct access. |
|||
* @jout: the json_out object to write into. |
|||
* @fieldname: optional fieldname to prepend. |
|||
* @extra: how many bytes to allocate. |
|||
* |
|||
* @fieldname must not need JSON escaping. Returns a direct pointer into |
|||
* the @extra bytes, or NULL if tal_resize() fails. |
|||
* |
|||
* This allows you to write your own efficient type-specific helpers. |
|||
*/ |
|||
char *json_out_member_direct(struct json_out *jout, |
|||
const char *fieldname, size_t extra); |
|||
|
|||
/**
|
|||
* json_out_direct - make room in output and access directly. |
|||
* @jout: the json_out object to write into. |
|||
* @len: the length to allocate. |
|||
* |
|||
* This lets you access the json_out stream directly, to save a copy, |
|||
* if you know exactly how much you will write. |
|||
* |
|||
* Returns a pointer to @len bytes at the end of @jout, or NULL if |
|||
* tal_resize() fails. |
|||
* |
|||
* This is dangerous, since it doesn't automatically prepend a "," |
|||
* like the internal logic does, but can be used (carefully) to add |
|||
* entire objects, or whitespace. |
|||
*/ |
|||
char *json_out_direct(struct json_out *jout, size_t extra); |
|||
|
|||
/**
|
|||
* json_out_add_splice - copy a field from another json_out. |
|||
* @jout: the json_out object to write into. |
|||
* @fieldname: optional fieldname to prepend. |
|||
* @src: the json_out object to copy from. |
|||
* |
|||
* This asserts that @src is well-formed (as per json_out_finished()), |
|||
* then places it into @jout with optional @fieldname prepended. This |
|||
* can be used to assemble sub-objects for your JSON and then copy |
|||
* them in. |
|||
* |
|||
* Note that it will call json_out_contents(@src), so it expects that |
|||
* object to be unconsumed. |
|||
* |
|||
* Returns false if tal_resize() fails. |
|||
*/ |
|||
bool json_out_add_splice(struct json_out *jout, |
|||
const char *fieldname, |
|||
const struct json_out *src); |
|||
|
|||
/**
|
|||
* json_out_finished - assert that the json buffer is finished. |
|||
* @jout: the json_out object written to. |
|||
* |
|||
* This simply causes internal assertions that all arrays and objects are |
|||
* finished. It needs CCAN_JSON_OUT_DEBUG defined to have any effect. |
|||
*/ |
|||
void json_out_finished(const struct json_out *jout); |
|||
|
|||
/**
|
|||
* json_out_contents - read contents from json_out stream. |
|||
* @jout: the json_out object we want to read from. |
|||
* @len: set to the length of the buffer returned. |
|||
* |
|||
* This returns a pointer into the JSON written so far. Returns NULL |
|||
* and sets @len to 0 if there's nothing left in the buffer. |
|||
*/ |
|||
const char *json_out_contents(const struct json_out *jout, size_t *len); |
|||
|
|||
/**
|
|||
* json_out_consume - discard contents from json_out stream. |
|||
* @jout: the json_out object we read from. |
|||
* @len: the length to consume (must be <= @len from json_out_contents) |
|||
*/ |
|||
void json_out_consume(struct json_out *jout, size_t len); |
|||
#endif /* CCAN_JSON_OUT_H */ |
@ -0,0 +1,2 @@ |
|||
#define CCAN_JSON_OUT_DEBUG 1 |
|||
#include "run.c" |
@ -0,0 +1,48 @@ |
|||
#include <ccan/json_out/json_out.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/json_out/json_out.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
static const char *ptr; |
|||
static bool called = false; |
|||
|
|||
static void move_cb(struct json_out *jout, ptrdiff_t delta, |
|||
struct json_out *arg) |
|||
{ |
|||
ptr += delta; |
|||
called = true; |
|||
ok1(arg == jout); |
|||
} |
|||
|
|||
int main(void) |
|||
{ |
|||
const tal_t *ctx = tal(NULL, char); |
|||
struct json_out *jout; |
|||
char *p; |
|||
size_t len; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(3); |
|||
|
|||
/* Test nested arrays. */ |
|||
jout = json_out_new(ctx); |
|||
json_out_call_on_move(jout, move_cb, jout); |
|||
|
|||
json_out_start(jout, NULL, '{'); |
|||
ptr = json_out_contents(jout, &len); |
|||
|
|||
p = json_out_member_direct(jout, "fieldname", 102); |
|||
p[0] = '"'; |
|||
p[101] = '"'; |
|||
memset(p+1, 'p', 100); |
|||
|
|||
json_out_finished(jout); |
|||
ok1(called); |
|||
/* Contents should have moved correctly. */ |
|||
ok1(json_out_contents(jout, &len) == ptr); |
|||
|
|||
tal_free(ctx); |
|||
|
|||
/* This exits depending on whether all tests passed */ |
|||
return exit_status(); |
|||
} |
@ -0,0 +1,141 @@ |
|||
#include <ccan/json_out/json_out.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/json_out/json_out.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
static void test_json_out_add(const tal_t *ctx, |
|||
char c, bool quote, const char *escaped) |
|||
{ |
|||
/* 64 is the size of the initial buf, so we test that. */ |
|||
for (size_t i = 1; i < 64; i++) { |
|||
struct json_out *jout; |
|||
char str[64 + 1]; |
|||
const char *r; |
|||
size_t len; |
|||
char fieldname[64 + 1]; |
|||
|
|||
jout = json_out_new(ctx); |
|||
json_out_start(jout, NULL, '{'); |
|||
memset(str, c, i); |
|||
str[i] = '\0'; |
|||
memset(fieldname, 'f', i); |
|||
fieldname[i] = '\0'; |
|||
json_out_add(jout, fieldname, quote, "%s", str); |
|||
json_out_end(jout, '}'); |
|||
json_out_finished(jout); |
|||
|
|||
r = json_out_contents(jout, &len); |
|||
ok1(len == strlen("{\"") + i + strlen("\":") |
|||
+ quote * 2 + strlen(escaped) * i + strlen("}")); |
|||
|
|||
ok1(len > strlen("{\"")); |
|||
ok1(memcmp(r, "{\"", strlen("{\"")) == 0); |
|||
json_out_consume(jout, strlen("{\"")); |
|||
|
|||
r = json_out_contents(jout, &len); |
|||
ok1(len > strlen(fieldname)); |
|||
ok1(memcmp(r, fieldname, strlen(fieldname)) == 0); |
|||
json_out_consume(jout, strlen(fieldname)); |
|||
|
|||
r = json_out_contents(jout, &len); |
|||
ok1(len > strlen("\":")); |
|||
ok1(memcmp(r, "\":", strlen("\":")) == 0); |
|||
json_out_consume(jout, strlen("\":")); |
|||
|
|||
r = json_out_contents(jout, &len); |
|||
if (quote) { |
|||
ok1(len > 0); |
|||
ok1(r[0] == '"'); |
|||
json_out_consume(jout, 1); |
|||
} |
|||
for (size_t n = 0; n < i; n++) { |
|||
r = json_out_contents(jout, &len); |
|||
ok1(len > strlen(escaped)); |
|||
ok1(memcmp(r, escaped, strlen(escaped)) == 0); |
|||
json_out_consume(jout, strlen(escaped)); |
|||
} |
|||
r = json_out_contents(jout, &len); |
|||
if (quote) { |
|||
ok1(len > 0); |
|||
ok1(r[0] == '"'); |
|||
json_out_consume(jout, 1); |
|||
} |
|||
r = json_out_contents(jout, &len); |
|||
ok1(len == 1); |
|||
ok1(memcmp(r, "}", 1) == 0); |
|||
json_out_consume(jout, 1); |
|||
ok1(!json_out_contents(jout, &len)); |
|||
ok1(len == 0); |
|||
} |
|||
} |
|||
|
|||
static void json_eq(const struct json_out *jout, const char *expect) |
|||
{ |
|||
size_t len; |
|||
const char *p; |
|||
|
|||
json_out_finished(jout); |
|||
p = json_out_contents(jout, &len); |
|||
ok1(len == strlen(expect)); |
|||
ok1(memcmp(expect, p, len) == 0); |
|||
} |
|||
|
|||
int main(void) |
|||
{ |
|||
const tal_t *ctx = tal(NULL, char); |
|||
struct json_out *jout; |
|||
char *p; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(14689); |
|||
|
|||
/* Simple tests */ |
|||
test_json_out_add(ctx, '1', false, "1"); |
|||
test_json_out_add(ctx, 'x', true, "x"); |
|||
test_json_out_add(ctx, '\n', true, "\\n"); |
|||
|
|||
/* Test nested arrays. */ |
|||
jout = json_out_new(ctx); |
|||
for (size_t i = 0; i < 64; i++) |
|||
json_out_start(jout, NULL, '['); |
|||
for (size_t i = 0; i < 64; i++) |
|||
json_out_end(jout, ']'); |
|||
json_eq(jout, "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"); |
|||
|
|||
/* Test nested objects. */ |
|||
jout = json_out_new(ctx); |
|||
json_out_start(jout, NULL, '{'); |
|||
for (size_t i = 0; i < 63; i++) |
|||
json_out_start(jout, "x", '{'); |
|||
for (size_t i = 0; i < 64; i++) |
|||
json_out_end(jout, '}'); |
|||
json_eq(jout, "{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"); |
|||
|
|||
jout = json_out_new(ctx); |
|||
json_out_start(jout, NULL, '{'); |
|||
p = json_out_member_direct(jout, "x", 7); |
|||
memcpy(p, "\"hello\"", 7); |
|||
json_out_end(jout, '}'); |
|||
json_eq(jout, "{\"x\":\"hello\"}"); |
|||
|
|||
jout = json_out_new(ctx); |
|||
p = json_out_direct(jout, strlen("{\"x\":\"hello\"}\n")); |
|||
memcpy(p, "{\"x\":\"hello\"}\n", strlen("{\"x\":\"hello\"}\n")); |
|||
json_eq(jout, "{\"x\":\"hello\"}\n"); |
|||
|
|||
jout = json_out_new(ctx); |
|||
json_out_start(jout, NULL, '{'); |
|||
struct json_out *jout2 = json_out_new(ctx); |
|||
json_out_start(jout2, NULL, '{'); |
|||
json_out_addstr(jout2, "x", "hello"); |
|||
json_out_end(jout2, '}'); |
|||
json_out_finished(jout2); |
|||
json_out_add_splice(jout, "inner", jout2); |
|||
json_out_end(jout, '}'); |
|||
json_eq(jout, "{\"inner\":{\"x\":\"hello\"}}"); |
|||
|
|||
tal_free(ctx); |
|||
|
|||
/* This exits depending on whether all tests passed */ |
|||
return exit_status(); |
|||
} |
@ -1,35 +0,0 @@ |
|||
#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); |
|||
|
|||
/* @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_to_escaped_string(const tal_t *ctx, |
|||
const char *buffer, |
|||
const jsmntok_t *tok); |
|||
|
|||
/* Are two escaped json strings identical? */ |
|||
bool json_escaped_eq(const struct json_escaped *a, |
|||
const struct json_escaped *b); |
|||
|
|||
/* Internal routine for creating json_escaped from bytes. */ |
|||
struct json_escaped *json_escaped_string_(const tal_t *ctx, |
|||
const void *bytes, size_t len); |
|||
|
|||
/* Be very careful here! Can fail! Doesn't handle \u: use UTF-8 please. */ |
|||
const char *json_escaped_unescape(const tal_t *ctx, |
|||
const struct json_escaped *esc); |
|||
#endif /* LIGHTNING_COMMON_JSON_ESCAPED_H */ |
@ -1,46 +0,0 @@ |
|||
#include "../json_escaped.c" |
|||
#include <assert.h> |
|||
#include <common/utils.h> |
|||
#include <stdio.h> |
|||
|
|||
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) |
|||
{ |
|||
setup_locale(); |
|||
|
|||
test_json_partial(); |
|||
assert(!taken_any()); |
|||
take_cleanup(); |
|||
} |
Loading…
Reference in new issue