core-lightning/common/json.c
Rusty Russell 91a22dc496 jsonprc: make json_get_params() fail the command, for better error reporting.
We move it into jsonrpc where it belongs, and make it fail the command.
This means it can tell us exactly what was wrong.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2018-02-02 00:05:00 +01:00

551 lines
12 KiB
C

/* JSON core and helpers */
#include "json.h"
#include <assert.h>
#include <ccan/build_assert/build_assert.h>
#include <ccan/str/hex/hex.h>
#include <ccan/tal/str/str.h>
#include <ccan/tal/tal.h>
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
struct json_result {
/* tal_arr of types we're enclosed in. */
jsmntype_t *wrapping;
/* tal_count() of this is strlen() + 1 */
char *s;
};
const char *json_tok_contents(const char *buffer, const jsmntok_t *t)
{
if (t->type == JSMN_STRING)
return buffer + t->start - 1;
return buffer + t->start;
}
/* Include " if it's a string. */
int json_tok_len(const jsmntok_t *t)
{
if (t->type == JSMN_STRING)
return t->end - t->start + 2;
return t->end - t->start;
}
bool json_tok_streq(const char *buffer, const jsmntok_t *tok, const char *str)
{
if (tok->type != JSMN_STRING)
return false;
if (tok->end - tok->start != strlen(str))
return false;
return strncmp(buffer + tok->start, str, tok->end - tok->start) == 0;
}
bool json_tok_u64(const char *buffer, const jsmntok_t *tok,
uint64_t *num)
{
char *end;
unsigned long long l;
l = strtoull(buffer + tok->start, &end, 0);
if (end != buffer + tok->end)
return false;
BUILD_ASSERT(sizeof(l) >= sizeof(*num));
*num = l;
/* Check for overflow */
if (l == ULLONG_MAX && errno == ERANGE)
return false;
if (*num != l)
return false;
return true;
}
bool json_tok_double(const char *buffer, const jsmntok_t *tok, double *num)
{
char *end;
*num = strtod(buffer + tok->start, &end);
if (end != buffer + tok->end)
return false;
return true;
}
bool json_tok_number(const char *buffer, const jsmntok_t *tok,
unsigned int *num)
{
uint64_t u64;
if (!json_tok_u64(buffer, tok, &u64))
return false;
*num = u64;
/* Just in case it doesn't fit. */
if (*num != u64)
return false;
return true;
}
bool json_tok_bitcoin_amount(const char *buffer, const jsmntok_t *tok,
uint64_t *satoshi)
{
char *end;
unsigned long btc, sat;
btc = strtoul(buffer + tok->start, &end, 10);
if (btc == ULONG_MAX && errno == ERANGE)
return false;
if (end != buffer + tok->end) {
/* Expect always 8 decimal places. */
if (*end != '.' || buffer + tok->end - end != 9)
return false;
sat = strtoul(end+1, &end, 10);
if (sat == ULONG_MAX && errno == ERANGE)
return false;
if (end != buffer + tok->end)
return false;
} else
sat = 0;
*satoshi = btc * (uint64_t)100000000 + sat;
if (*satoshi != btc * (uint64_t)100000000 + sat)
return false;
return true;
}
bool json_tok_is_null(const char *buffer, const jsmntok_t *tok)
{
if (tok->type != JSMN_PRIMITIVE)
return false;
return buffer[tok->start] == 'n';
}
bool json_tok_bool(const char *buffer, const jsmntok_t *tok, bool *b)
{
if (tok->type != JSMN_PRIMITIVE)
return false;
if (tok->end - tok->start == strlen("true")
&& memcmp(buffer + tok->start, "true", strlen("true")) == 0) {
*b = true;
return true;
}
if (tok->end - tok->start == strlen("false")
&& memcmp(buffer + tok->start, "false", strlen("false")) == 0) {
*b = false;
return true;
}
return false;
}
const jsmntok_t *json_next(const jsmntok_t *tok)
{
const jsmntok_t *t;
size_t i;
for (t = tok + 1, i = 0; i < tok->size; i++)
t = json_next(t);
return t;
}
const jsmntok_t *json_get_member(const char *buffer, const jsmntok_t tok[],
const char *label)
{
const jsmntok_t *t, *end;
if (tok->type != JSMN_OBJECT)
return NULL;
end = json_next(tok);
for (t = tok + 1; t < end; t = json_next(t+1))
if (json_tok_streq(buffer, t, label))
return t + 1;
return NULL;
}
const jsmntok_t *json_get_arr(const jsmntok_t tok[], size_t index)
{
const jsmntok_t *t, *end;
if (tok->type != JSMN_ARRAY)
return NULL;
end = json_next(tok);
for (t = tok + 1; t < end; t = json_next(t)) {
if (index == 0)
return t;
index--;
}
return NULL;
}
/* Guide is a string with . for members, [] around indexes. */
const jsmntok_t *json_delve(const char *buffer,
const jsmntok_t *tok,
const char *guide)
{
while (*guide) {
const char *key;
size_t len = strcspn(guide+1, ".[]");
key = tal_strndup(NULL, guide+1, len);
switch (guide[0]) {
case '.':
if (tok->type != JSMN_OBJECT)
return tal_free(key);
tok = json_get_member(buffer, tok, key);
if (!tok)
return tal_free(key);
break;
case '[':
if (tok->type != JSMN_ARRAY)
return tal_free(key);
tok = json_get_arr(tok, atol(key));
if (!tok)
return tal_free(key);
/* Must be terminated */
assert(guide[1+strlen(key)] == ']');
len++;
break;
default:
abort();
}
tal_free(key);
guide += len + 1;
}
return tok;
}
static bool strange_chars(const char *str, size_t len)
{
for (size_t i = 0; i < len; i++) {
if (!cisprint(str[i]) || str[i] == '"' || str[i] == '\\')
return true;
}
return false;
}
jsmntok_t *json_parse_input(const char *input, int len, bool *valid)
{
jsmn_parser parser;
jsmntok_t *toks;
jsmnerr_t ret;
size_t i;
toks = tal_arr(input, jsmntok_t, 10);
again:
jsmn_init(&parser);
ret = jsmn_parse(&parser, input, len, toks, tal_count(toks) - 1);
switch (ret) {
case JSMN_ERROR_INVAL:
*valid = false;
return tal_free(toks);
case JSMN_ERROR_PART:
*valid = true;
return tal_free(toks);
case JSMN_ERROR_NOMEM:
tal_resize(&toks, tal_count(toks) * 2);
goto again;
}
/* Cut to length and return. */
*valid = true;
tal_resize(&toks, ret + 1);
/* Make sure last one is always referenceable. */
toks[ret].type = -1;
toks[ret].start = toks[ret].end = toks[ret].size = 0;
/* Don't allow tokens to contain weird characters (outside toks ok). */
for (i = 0; i < ret; i++) {
if (toks[i].type != JSMN_STRING
&& toks[i].type != JSMN_PRIMITIVE)
continue;
if (strange_chars(input + toks[i].start,
toks[i].end - toks[i].start)) {
*valid = false;
return tal_free(toks);
}
}
return toks;
}
static void result_append(struct json_result *res, const char *str)
{
size_t len = tal_count(res->s) - 1;
tal_resize(&res->s, len + strlen(str) + 1);
strcpy(res->s + len, str);
}
static void PRINTF_FMT(2,3)
result_append_fmt(struct json_result *res, const char *fmt, ...)
{
size_t len = tal_count(res->s) - 1, fmtlen;
va_list ap;
va_start(ap, fmt);
fmtlen = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
tal_resize(&res->s, len + fmtlen + 1);
va_start(ap, fmt);
vsprintf(res->s + len, fmt, ap);
va_end(ap);
}
static bool result_ends_with(struct json_result *res, const char *str)
{
size_t len = tal_count(res->s) - 1;
if (strlen(str) > len)
return false;
return streq(res->s + len - strlen(str), str);
}
static void check_fieldname(const struct json_result *result,
const char *fieldname)
{
size_t n = tal_count(result->wrapping);
if (n == 0)
/* Can't have a fieldname if not in anything! */
assert(!fieldname);
else if (result->wrapping[n-1] == JSMN_ARRAY)
/* No fieldnames in arrays. */
assert(!fieldname);
else
/* Must have fieldnames in objects. */
assert(fieldname);
}
static void json_start_member(struct json_result *result, const char *fieldname)
{
/* Prepend comma if required. */
if (result->s[0]
&& !result_ends_with(result, "{ ")
&& !result_ends_with(result, "[ "))
result_append(result, ", ");
check_fieldname(result, fieldname);
if (fieldname)
result_append_fmt(result, "\"%s\" : ", fieldname);
}
static void result_add_indent(struct json_result *result)
{
size_t i, indent = tal_count(result->wrapping);
if (!indent)
return;
result_append(result, "\n");
for (i = 0; i < indent; i++)
result_append(result, "\t");
}
static void result_add_wrap(struct json_result *result, jsmntype_t type)
{
size_t indent = tal_count(result->wrapping);
tal_resize(&result->wrapping, indent+1);
result->wrapping[indent] = type;
}
static void result_pop_wrap(struct json_result *result, jsmntype_t type)
{
size_t indent = tal_count(result->wrapping);
assert(indent);
assert(result->wrapping[indent-1] == type);
tal_resize(&result->wrapping, indent-1);
}
void json_array_start(struct json_result *result, const char *fieldname)
{
json_start_member(result, fieldname);
result_add_indent(result);
result_append(result, "[ ");
result_add_wrap(result, JSMN_ARRAY);
}
void json_array_end(struct json_result *result)
{
result_append(result, " ]");
result_pop_wrap(result, JSMN_ARRAY);
}
void json_object_start(struct json_result *result, const char *fieldname)
{
json_start_member(result, fieldname);
result_add_indent(result);
result_append(result, "{ ");
result_add_wrap(result, JSMN_OBJECT);
}
void json_object_end(struct json_result *result)
{
result_append(result, " }");
result_pop_wrap(result, JSMN_OBJECT);
}
void json_add_num(struct json_result *result, const char *fieldname, unsigned int value)
{
json_start_member(result, fieldname);
result_append_fmt(result, "%u", value);
}
void json_add_snum(struct json_result *result, const char *fieldname, int value)
{
json_start_member(result, fieldname);
result_append_fmt(result, "%d", value);
}
void json_add_u64(struct json_result *result, const char *fieldname,
uint64_t value)
{
json_start_member(result, fieldname);
result_append_fmt(result, "%"PRIu64, value);
}
void json_add_literal(struct json_result *result, const char *fieldname,
const char *literal, int len)
{
json_start_member(result, fieldname);
result_append_fmt(result, "%.*s", len, literal);
}
void json_add_string(struct json_result *result, const char *fieldname, const char *value)
{
char *escaped = tal_strdup(result, value);
size_t i;
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);
}
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);
result_append(result, value ? "true" : "false");
}
void json_add_null(struct json_result *result, const char *fieldname)
{
json_start_member(result, fieldname);
result_append(result, "null");
}
void json_add_hex(struct json_result *result, const char *fieldname,
const void *data, size_t len)
{
char *hex = tal_arr(NULL, char, hex_str_size(len));
hex_encode(data, len, hex, hex_str_size(len));
json_add_string(result, fieldname, hex);
tal_free(hex);
}
void json_add_object(struct json_result *result, ...)
{
va_list ap;
const char *field;
va_start(ap, result);
json_object_start(result, NULL);
while ((field = va_arg(ap, const char *)) != NULL) {
jsmntype_t type = va_arg(ap, jsmntype_t);
const char *value = va_arg(ap, const char *);
if (type == JSMN_STRING)
json_add_string(result, field, value);
else
json_add_literal(result, field, value, strlen(value));
}
json_object_end(result);
va_end(ap);
}
struct json_result *new_json_result(const tal_t *ctx)
{
struct json_result *r = tal(ctx, struct json_result);
/* Using tal_arr means that it has a valid count. */
r->s = tal_arrz(r, char, 1);
r->wrapping = tal_arr(r, jsmntype_t, 0);
return r;
}
const char *json_result_string(const struct json_result *result)
{
assert(tal_count(result->wrapping) == 0);
assert(tal_count(result->s) == strlen(result->s) + 1);
return result->s;
}