Browse Source
Also taken from pettycoin. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>ppa-0.6.1
Rusty Russell
9 years ago
3 changed files with 472 additions and 0 deletions
@ -0,0 +1,383 @@ |
|||
/* JSON core and helpers */ |
|||
#include "json.h" |
|||
#include <assert.h> |
|||
#include <ccan/str/hex/hex.h> |
|||
#include <ccan/tal/str/str.h> |
|||
#include <ccan/tal/tal.h> |
|||
#include <errno.h> |
|||
#include <stdarg.h> |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
struct json_result { |
|||
unsigned int indent; |
|||
/* 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_number(const char *buffer, const jsmntok_t *tok, |
|||
unsigned int *num) |
|||
{ |
|||
char *end; |
|||
unsigned long l; |
|||
|
|||
l = strtoul(buffer + tok->start, &end, 0); |
|||
if (end != buffer + tok->end) |
|||
return false; |
|||
|
|||
*num = l; |
|||
|
|||
/* Check for overflow */ |
|||
if (l == ULONG_MAX && errno == ERANGE) |
|||
return false; |
|||
|
|||
if (*num != l) |
|||
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; |
|||
|
|||
assert(tok->type == JSMN_OBJECT); |
|||
|
|||
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 char *buffer, const jsmntok_t tok[], |
|||
size_t index) |
|||
{ |
|||
const jsmntok_t *t, *end; |
|||
|
|||
assert(tok->type == JSMN_ARRAY); |
|||
|
|||
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(buffer, 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; |
|||
} |
|||
|
|||
void json_get_params(const char *buffer, const jsmntok_t param[], ...) |
|||
{ |
|||
va_list ap; |
|||
const char *name; |
|||
const jsmntok_t **tokptr, *p, *end; |
|||
|
|||
if (param->type == JSMN_ARRAY) { |
|||
if (param->size == 0) |
|||
p = NULL; |
|||
else |
|||
p = param + 1; |
|||
end = json_next(param); |
|||
} else |
|||
assert(param->type == JSMN_OBJECT); |
|||
|
|||
va_start(ap, param); |
|||
while ((name = va_arg(ap, const char *)) != NULL) { |
|||
tokptr = va_arg(ap, const jsmntok_t **); |
|||
if (param->type == JSMN_ARRAY) { |
|||
*tokptr = p; |
|||
if (p) { |
|||
p = json_next(p); |
|||
if (p == end) |
|||
p = NULL; |
|||
} |
|||
} else { |
|||
*tokptr = json_get_member(buffer, param, name); |
|||
} |
|||
/* Convert 'null' to NULL */ |
|||
if (*tokptr |
|||
&& (*tokptr)->type == JSMN_PRIMITIVE |
|||
&& buffer[(*tokptr)->start] == 'n') { |
|||
*tokptr = NULL; |
|||
} |
|||
} |
|||
|
|||
va_end(ap); |
|||
} |
|||
|
|||
jsmntok_t *json_parse_input(const char *input, int len, bool *valid) |
|||
{ |
|||
jsmn_parser parser; |
|||
jsmntok_t *toks; |
|||
jsmnerr_t ret; |
|||
|
|||
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 referencable. */ |
|||
toks[ret].type = -1; |
|||
toks[ret].start = toks[ret].end = toks[ret].size = 0; |
|||
|
|||
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 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, ", "); |
|||
if (fieldname) |
|||
result_append_fmt(result, "\"%s\" : ", fieldname); |
|||
} |
|||
|
|||
void json_array_start(struct json_result *result, const char *fieldname) |
|||
{ |
|||
json_start_member(result, fieldname); |
|||
if (result->indent) { |
|||
unsigned int i; |
|||
result_append(result, "\n"); |
|||
for (i = 0; i < result->indent; i++) |
|||
result_append(result, "\t"); |
|||
} |
|||
result_append(result, "[ "); |
|||
result->indent++; |
|||
} |
|||
|
|||
void json_array_end(struct json_result *result) |
|||
{ |
|||
assert(result->indent); |
|||
result->indent--; |
|||
result_append(result, " ]"); |
|||
} |
|||
|
|||
void json_object_start(struct json_result *result, const char *fieldname) |
|||
{ |
|||
json_start_member(result, fieldname); |
|||
if (result->indent) { |
|||
unsigned int i; |
|||
result_append(result, "\n"); |
|||
for (i = 0; i < result->indent; i++) |
|||
result_append(result, "\t"); |
|||
} |
|||
result_append(result, "{ "); |
|||
result->indent++; |
|||
} |
|||
|
|||
void json_object_end(struct json_result *result) |
|||
{ |
|||
assert(result->indent); |
|||
result->indent--; |
|||
result_append(result, " }"); |
|||
} |
|||
|
|||
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_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) |
|||
{ |
|||
json_start_member(result, fieldname); |
|||
result_append_fmt(result, "\"%s\"", value); |
|||
} |
|||
|
|||
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[hex_str_size(len)]; |
|||
|
|||
hex_encode(data, len, hex, sizeof(hex)); |
|||
json_add_string(result, fieldname, 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->indent = 0; |
|||
return r; |
|||
} |
|||
|
|||
const char *json_result_string(const struct json_result *result) |
|||
{ |
|||
assert(!result->indent); |
|||
assert(tal_count(result->s) == strlen(result->s) + 1); |
|||
return result->s; |
|||
} |
@ -0,0 +1,87 @@ |
|||
#ifndef LIGHTNING_DAEMON_JSON_H |
|||
#define LIGHTNING_DAEMON_JSON_H |
|||
#include "config.h" |
|||
#include "stdbool.h" |
|||
#include "stdlib.h" |
|||
#include <ccan/tal/tal.h> |
|||
|
|||
#define JSMN_STRICT 1 |
|||
# include "jsmn/jsmn.h" |
|||
|
|||
struct json_result; |
|||
|
|||
/* Include " if it's a string. */ |
|||
const char *json_tok_contents(const char *buffer, const jsmntok_t *t); |
|||
|
|||
/* Include " if it's a string. */ |
|||
int json_tok_len(const jsmntok_t *t); |
|||
|
|||
/* Is this a string equal to str? */ |
|||
bool json_tok_streq(const char *buffer, const jsmntok_t *tok, const char *str); |
|||
|
|||
/* Extract number from this (may be a string, or a number literal) */ |
|||
bool json_tok_number(const char *buffer, const jsmntok_t *tok, |
|||
unsigned int *num); |
|||
|
|||
/* Is this the null primitive? */ |
|||
bool json_tok_is_null(const char *buffer, const jsmntok_t *tok); |
|||
|
|||
/* Returns next token with same parent. */ |
|||
const jsmntok_t *json_next(const jsmntok_t *tok); |
|||
|
|||
/* Get the parameters (by position or name). Followed by pairs
|
|||
* of const char *name, const jsmntok_t **ret_ptr, then NULL. |
|||
* *ret_ptr will be set to NULL if it's a literal 'null' or not present. |
|||
*/ |
|||
void json_get_params(const char *buffer, const jsmntok_t param[], ...); |
|||
|
|||
/* Get top-level member. */ |
|||
const jsmntok_t *json_get_member(const char *buffer, const jsmntok_t tok[], |
|||
const char *label); |
|||
|
|||
/* Get index'th array member. */ |
|||
const jsmntok_t *json_get_arr(const char *buffer, const jsmntok_t tok[], |
|||
size_t index); |
|||
|
|||
/* Guide is a string with . for members, [] around indexes. */ |
|||
const jsmntok_t *json_delve(const char *buffer, |
|||
const jsmntok_t *tok, |
|||
const char *guide); |
|||
|
|||
/* If input is complete and valid, return tokens. */ |
|||
jsmntok_t *json_parse_input(const char *input, int len, bool *valid); |
|||
|
|||
/* Creating JSON strings */ |
|||
|
|||
/* '"fieldname" : [ ' or '[ ' if fieldname is NULL */ |
|||
void json_array_start(struct json_result *ptr, const char *fieldname); |
|||
/* '"fieldname" : { ' or '{ ' if fieldname is NULL */ |
|||
void json_object_start(struct json_result *ptr, const char *fieldname); |
|||
/* ' ], ' */ |
|||
void json_array_end(struct json_result *ptr); |
|||
/* ' }, ' */ |
|||
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*/ |
|||
void json_add_string(struct json_result *result, const char *fieldname, const char *value); |
|||
/* '"fieldname" : literal' or 'literal' if fieldname is NULL*/ |
|||
void json_add_literal(struct json_result *result, const char *fieldname, |
|||
const char *literal, int len); |
|||
/* '"fieldname" : value' or 'value' if fieldname is NULL */ |
|||
void json_add_num(struct json_result *result, const char *fieldname, |
|||
unsigned int value); |
|||
/* '"fieldname" : true|false' or 'true|false' if fieldname is NULL */ |
|||
void json_add_bool(struct json_result *result, const char *fieldname, |
|||
bool value); |
|||
/* '"fieldname" : null' or 'null' if fieldname is NULL */ |
|||
void json_add_null(struct json_result *result, const char *fieldname); |
|||
/* '"fieldname" : "0189abcdef..."' or "0189abcdef..." if fieldname is NULL */ |
|||
void json_add_hex(struct json_result *result, const char *fieldname, |
|||
const void *data, size_t len); |
|||
|
|||
void json_add_object(struct json_result *result, ...); |
|||
|
|||
const char *json_result_string(const struct json_result *result); |
|||
#endif /* LIGHTNING_DAEMON_JSON_H */ |
Loading…
Reference in new issue