core-lightning/common/json.c
Rusty Russell 465d5d5649 json: add json_to_bool() helper.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2018-12-20 03:22:32 +00:00

357 lines
8.0 KiB
C

/* JSON core and helpers */
#include "json.h"
#include <assert.h>
#include <ccan/build_assert/build_assert.h>
#include <ccan/mem/mem.h>
#include <ccan/str/hex/hex.h>
#include <ccan/tal/str/str.h>
#include <common/utils.h>
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
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;
}
char *json_strdup(const tal_t *ctx, const char *buffer, const jsmntok_t *tok)
{
return tal_strndup(ctx, buffer + tok->start, tok->end - tok->start);
}
bool json_to_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_to_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_to_number(const char *buffer, const jsmntok_t *tok,
unsigned int *num)
{
uint64_t u64;
if (!json_to_u64(buffer, tok, &u64))
return false;
*num = u64;
/* Just in case it doesn't fit. */
if (*num != u64)
return false;
return true;
}
bool json_to_int(const char *buffer, const jsmntok_t *tok, int *num)
{
char *end;
long l;
l = strtol(buffer + tok->start, &end, 0);
if (end != buffer + tok->end)
return false;
BUILD_ASSERT(sizeof(l) >= sizeof(*num));
*num = l;
/* Check for overflow/underflow */
if ((l == LONG_MAX || l == LONG_MIN) && errno == ERANGE)
return false;
/* Check for truncation */
if (*num != l)
return false;
return true;
}
bool json_to_bool(const char *buffer, const jsmntok_t *tok, bool *b)
{
if (tok->type != JSMN_PRIMITIVE)
return false;
if (memeqstr(buffer + tok->start, tok->end - tok->start, "true")) {
*b = true;
return true;
}
if (memeqstr(buffer + tok->start, tok->end - tok->start, "false")) {
*b = false;
return true;
}
return false;
}
bool json_to_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_num(const char *buffer, const jsmntok_t *tok)
{
if (tok->type != JSMN_PRIMITIVE)
return false;
for (int i = tok->start; i < tok->end; i++)
if (!cisdigit(buffer[i]))
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';
}
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;
}
jsmntok_t *json_parse_input(const tal_t *ctx,
const char *input, int len, bool *valid)
{
jsmn_parser parser;
jsmntok_t *toks;
int ret;
toks = tal_arr(ctx, jsmntok_t, 10);
toks[0].type = JSMN_UNDEFINED;
jsmn_init(&parser);
again:
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_NOMEM:
tal_resize(&toks, tal_count(toks) * 2);
goto again;
}
/* Check whether we read at least one full root element, i.e., root
* element has its end set. */
if (toks[0].type == JSMN_UNDEFINED || toks[0].end == -1) {
*valid = true;
return tal_free(toks);
}
/* If we read a partial element at the end of the stream we'll get a
* ret=JSMN_ERROR_PART, but due to the previous check we know we read at
* least one full element, so count tokens that are part of this root
* element. */
ret = json_next(toks) - toks;
/* 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;
return toks;
}
const char *jsmntype_to_string(jsmntype_t t)
{
switch (t) {
case JSMN_UNDEFINED :
return "UNDEFINED";
case JSMN_OBJECT :
return "OBJECT";
case JSMN_ARRAY :
return "ARRAY";
case JSMN_STRING :
return "STRING";
case JSMN_PRIMITIVE :
return "PRIMITIVE";
}
return "INVALID";
}
void json_tok_print(const char *buffer, const jsmntok_t *tok)
{
const jsmntok_t *first = tok;
const jsmntok_t *last = json_next(tok);
printf("size: %d, count: %td\n", tok->size, last - first);
while (first != last) {
printf("%td. %.*s, %s\n", first - tok,
first->end - first->start, buffer + first->start,
jsmntype_to_string(first->type));
first++;
}
printf("\n");
}
jsmntok_t *json_tok_copy(const tal_t *ctx, const jsmntok_t *tok)
{
return tal_dup_arr(ctx, jsmntok_t, tok, json_next(tok) - tok, 0);
}
void json_tok_remove(jsmntok_t **tokens, jsmntok_t *tok, size_t num)
{
assert(*tokens);
assert((*tokens)->type == JSMN_ARRAY || (*tokens)->type == JSMN_OBJECT);
const jsmntok_t *src = tok;
const jsmntok_t *end = json_next(*tokens);
jsmntok_t *dest = tok;
int remove_count;
for (int i = 0; i < num; i++)
src = json_next(src);
remove_count = src - tok;
memmove(dest, src, sizeof(jsmntok_t) * (end - src));
tal_resize(tokens, tal_count(*tokens) - remove_count);
(*tokens)->size -= num;
}
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(tmpctx, guide+1, len);
switch (guide[0]) {
case '.':
if (tok->type != JSMN_OBJECT)
return NULL;
tok = json_get_member(buffer, tok, key);
if (!tok)
return NULL;
break;
case '[':
if (tok->type != JSMN_ARRAY)
return NULL;
tok = json_get_arr(tok, atol(key));
if (!tok)
return NULL;
/* Must be terminated */
assert(guide[1+strlen(key)] == ']');
len++;
break;
default:
abort();
}
guide += len + 1;
}
return tok;
}