/* JSON core and helpers */ #include "json.h" #include #include #include #include #include #include #include #include #include #include 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; int 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_double(struct json_result *result, const char *fieldname, double value) { json_start_member(result, fieldname); result_append_fmt(result, "%f", 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; }