1
0
mirror of https://git.kore.io/kore.git synced 2024-11-10 20:29:11 +01:00
kore/src/json.c
Joris Vink f6af4a27f4 Enable type-limits warning.
Remove unneeded comparison in the JSON code.
via https://marc.info/?l=openbsd-ports&m=159958572325174&w=2
2020-09-08 19:29:15 +02:00

824 lines
16 KiB
C

/*
* Copyright (c) 2019-2020 Joris Vink <joris@coders.se>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <float.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include "kore.h"
static int json_guess_type(u_int8_t, int *);
static int json_next(struct kore_json *, u_int8_t *);
static int json_peek(struct kore_json *, u_int8_t *);
static int json_consume_whitespace(struct kore_json *);
static int json_next_byte(struct kore_json *, u_int8_t *, int);
static char *json_get_string(struct kore_json *);
static int json_parse_array(struct kore_json *, struct kore_json_item *);
static int json_parse_object(struct kore_json *, struct kore_json_item *);
static int json_parse_string(struct kore_json *, struct kore_json_item *);
static int json_parse_number(struct kore_json *, struct kore_json_item *);
static int json_parse_literal(struct kore_json *, struct kore_json_item *);
static struct kore_json_item *json_item_alloc(int, const char *,
struct kore_json_item *);
static struct kore_json_item *json_find_item(struct kore_json_item *,
char **, int, int);
static u_int8_t json_null_literal[] = { 'n', 'u', 'l', 'l' };
static u_int8_t json_true_literal[] = { 't', 'r', 'u', 'e' };
static u_int8_t json_false_literal[] = { 'f', 'a', 'l', 's', 'e' };
static const char *json_errtab[] = {
"no error",
"invalid JSON object",
"invalid JSON array",
"invalid JSON string",
"invalid JSON number",
"invalid JSON literal",
"too many nested items",
"end of stream while parsing JSON",
"invalid JSON",
"invalid search query specified",
"item not found",
"item found, but not expected value"
};
void
kore_json_init(struct kore_json *json, const u_int8_t *data, size_t len)
{
memset(json, 0, sizeof(*json));
json->data = data;
json->length = len;
kore_buf_init(&json->tmpbuf, 1024);
}
int
kore_json_parse(struct kore_json *json)
{
u_int8_t ch;
int type;
if (json->root)
return (KORE_RESULT_OK);
if (!json_peek(json, &ch))
return (KORE_RESULT_ERROR);
if (!json_guess_type(ch, &type)) {
json->error = KORE_JSON_ERR_INVALID_JSON;
return (KORE_RESULT_ERROR);
}
json->root = json_item_alloc(type, NULL, NULL);
if (!json->root->parse(json, json->root)) {
if (json->error == 0)
json->error = KORE_JSON_ERR_INVALID_JSON;
return (KORE_RESULT_ERROR);
}
return (KORE_RESULT_OK);
}
struct kore_json_item *
kore_json_find(struct kore_json_item *root, const char *path, int type)
{
struct kore_json_item *item;
char *copy;
char *tokens[KORE_JSON_DEPTH_MAX + 1];
copy = kore_strdup(path);
if (!kore_split_string(copy, "/", tokens, KORE_JSON_DEPTH_MAX)) {
kore_free(copy);
return (NULL);
}
item = json_find_item(root, tokens, type, 0);
kore_free(copy);
return (item);
}
void
kore_json_cleanup(struct kore_json *json)
{
if (json == NULL)
return;
kore_buf_cleanup(&json->tmpbuf);
kore_json_item_free(json->root);
}
const char *
kore_json_strerror(struct kore_json *json)
{
if (json->error >= 0 && json->error <= KORE_JSON_ERR_LAST)
return (json_errtab[json->error]);
return ("unknown JSON error");
}
struct kore_json_item *
kore_json_create_item(struct kore_json_item *parent, const char *name,
int type, ...)
{
const char *p;
va_list args;
struct kore_json_item *item;
item = kore_calloc(1, sizeof(*item));
item->type = type;
va_start(args, type);
switch (item->type) {
case KORE_JSON_TYPE_OBJECT:
TAILQ_INIT(&item->data.items);
break;
case KORE_JSON_TYPE_ARRAY:
TAILQ_INIT(&item->data.items);
break;
case KORE_JSON_TYPE_STRING:
p = va_arg(args, const char *);
item->data.string = kore_strdup(p);
break;
case KORE_JSON_TYPE_NUMBER:
item->data.number = va_arg(args, double);
break;
case KORE_JSON_TYPE_LITERAL:
item->data.literal = va_arg(args, int);
break;
default:
fatal("%s: unknown type %d", __func__, item->type);
}
if (name)
item->name = kore_strdup(name);
if (parent) {
if (parent->type != KORE_JSON_TYPE_OBJECT &&
parent->type != KORE_JSON_TYPE_ARRAY) {
fatal("%s: invalid parent type (%d)",
__func__, parent->type);
}
TAILQ_INSERT_TAIL(&parent->data.items, item, list);
}
va_end(args);
return (item);
}
void
kore_json_item_tobuf(struct kore_json_item *item, struct kore_buf *buf)
{
struct kore_json_item *nitem;
if (item->name)
kore_buf_appendf(buf, "\"%s\":", item->name);
switch (item->type) {
case KORE_JSON_TYPE_OBJECT:
kore_buf_appendf(buf, "{");
TAILQ_FOREACH(nitem, &item->data.items, list) {
kore_json_item_tobuf(nitem, buf);
if (TAILQ_NEXT(nitem, list))
kore_buf_appendf(buf, ",");
}
kore_buf_appendf(buf, "}");
break;
case KORE_JSON_TYPE_ARRAY:
kore_buf_appendf(buf, "[");
TAILQ_FOREACH(nitem, &item->data.items, list) {
kore_json_item_tobuf(nitem, buf);
if (TAILQ_NEXT(nitem, list))
kore_buf_appendf(buf, ",");
}
kore_buf_appendf(buf, "]");
break;
case KORE_JSON_TYPE_STRING:
kore_buf_appendf(buf, "\"%s\"", item->data.string);
break;
case KORE_JSON_TYPE_NUMBER:
kore_buf_appendf(buf, "%f", item->data.number);
break;
case KORE_JSON_TYPE_LITERAL:
switch (item->data.literal) {
case KORE_JSON_TRUE:
kore_buf_append(buf,
json_true_literal, sizeof(json_true_literal));
break;
case KORE_JSON_FALSE:
kore_buf_append(buf,
json_false_literal, sizeof(json_false_literal));
break;
case KORE_JSON_NULL:
kore_buf_append(buf,
json_null_literal, sizeof(json_null_literal));
break;
default:
fatal("%s: unknown literal %d", __func__,
item->data.literal);
}
break;
default:
fatal("%s: unknown type %d", __func__, item->type);
}
}
static struct kore_json_item *
json_find_item(struct kore_json_item *object, char **tokens, int type, int pos)
{
char *p, *str;
struct kore_json_item *item, *nitem;
int err, idx, spot;
if (tokens[pos] == NULL)
return (NULL);
if (object->type != KORE_JSON_TYPE_OBJECT &&
object->type != KORE_JSON_TYPE_ARRAY)
return (NULL);
if ((str = strchr(tokens[pos], '[')) != NULL) {
*(str)++ = '\0';
if ((p = strchr(str, ']')) == NULL)
return (NULL);
*p = '\0';
spot = kore_strtonum(str, 10, 0, USHRT_MAX, &err);
if (err != KORE_RESULT_OK)
return (NULL);
} else {
spot = -1;
}
item = NULL;
TAILQ_FOREACH(item, &object->data.items, list) {
if (item->name && strcmp(item->name, tokens[pos]))
continue;
if (item->type == KORE_JSON_TYPE_ARRAY && spot != -1) {
idx = 0;
nitem = NULL;
TAILQ_FOREACH(nitem, &item->data.items, list) {
if (idx++ == spot)
break;
}
if (nitem == NULL)
return (NULL);
item = nitem;
}
if (tokens[pos + 1] == NULL) {
if (item->type == type)
return (item);
return (NULL);
}
if (item->type == KORE_JSON_TYPE_OBJECT ||
item->type == KORE_JSON_TYPE_ARRAY) {
item = json_find_item(item, tokens, type, pos + 1);
} else {
item = NULL;
}
break;
}
return (item);
}
void
kore_json_item_free(struct kore_json_item *item)
{
struct kore_json_item *node;
if (item == NULL)
return;
switch (item->type) {
case KORE_JSON_TYPE_OBJECT:
case KORE_JSON_TYPE_ARRAY:
while ((node = TAILQ_FIRST(&item->data.items)) != NULL) {
TAILQ_REMOVE(&item->data.items, node, list);
kore_json_item_free(node);
}
break;
case KORE_JSON_TYPE_STRING:
kore_free(item->data.string);
break;
case KORE_JSON_TYPE_NUMBER:
case KORE_JSON_TYPE_LITERAL:
break;
default:
fatal("%s: unknown type %d", __func__, item->type);
}
kore_free(item->name);
kore_free(item);
}
static struct kore_json_item *
json_item_alloc(int type, const char *name, struct kore_json_item *parent)
{
struct kore_json_item *item;
item = kore_calloc(1, sizeof(*item));
item->type = type;
item->parent = parent;
switch (item->type) {
case KORE_JSON_TYPE_OBJECT:
TAILQ_INIT(&item->data.items);
item->parse = json_parse_object;
break;
case KORE_JSON_TYPE_ARRAY:
TAILQ_INIT(&item->data.items);
item->parse = json_parse_array;
break;
case KORE_JSON_TYPE_STRING:
item->parse = json_parse_string;
break;
case KORE_JSON_TYPE_NUMBER:
item->parse = json_parse_number;
break;
case KORE_JSON_TYPE_LITERAL:
item->parse = json_parse_literal;
break;
default:
fatal("%s: unknown type %d", __func__, item->type);
}
if (name)
item->name = kore_strdup(name);
if (parent) {
if (parent->type != KORE_JSON_TYPE_OBJECT &&
parent->type != KORE_JSON_TYPE_ARRAY) {
fatal("%s: invalid parent type (%d)",
__func__, parent->type);
}
TAILQ_INSERT_TAIL(&parent->data.items, item, list);
}
return (item);
}
static int
json_peek(struct kore_json *json, u_int8_t *ch)
{
return (json_next_byte(json, ch, 1));
}
static int
json_next(struct kore_json *json, u_int8_t *ch)
{
return (json_next_byte(json, ch, 0));
}
static int
json_next_byte(struct kore_json *json, u_int8_t *ch, int peek)
{
if (json->offset >= json->length) {
json->error = KORE_JSON_ERR_EOF;
return (KORE_RESULT_ERROR);
}
*ch = json->data[json->offset];
if (peek == 0)
json->offset++;
return (KORE_RESULT_OK);
}
static int
json_consume_whitespace(struct kore_json *json)
{
u_int8_t ch;
for (;;) {
if (!json_peek(json, &ch))
return (KORE_RESULT_ERROR);
if (ch != ' ' && ch != '\n' && ch != '\r' && ch != '\t')
break;
json->offset++;
}
return (KORE_RESULT_OK);
}
static int
json_guess_type(u_int8_t ch, int *type)
{
if (ch == '-' || (ch >= '0' && ch <= '9')) {
*type = KORE_JSON_TYPE_NUMBER;
return (KORE_RESULT_OK);
}
switch (ch) {
case '{':
*type = KORE_JSON_TYPE_OBJECT;
break;
case '"':
*type = KORE_JSON_TYPE_STRING;
break;
case '[':
*type = KORE_JSON_TYPE_ARRAY;
break;
case 'f':
case 'n':
case 't':
*type = KORE_JSON_TYPE_LITERAL;
break;
default:
return (KORE_RESULT_ERROR);
}
return (KORE_RESULT_OK);
}
static int
json_parse_object(struct kore_json *json, struct kore_json_item *object)
{
u_int8_t ch;
char *key;
struct kore_json_item *item;
int ret, type;
if (json->depth++ >= KORE_JSON_DEPTH_MAX) {
json->error = KORE_JSON_ERR_DEPTH;
return (KORE_RESULT_ERROR);
}
key = NULL;
ret = KORE_RESULT_ERROR;
if (!json_next(json, &ch))
goto cleanup;
if (ch != '{')
goto cleanup;
for (;;) {
if (!json_consume_whitespace(json))
goto cleanup;
if (!json_peek(json, &ch))
goto cleanup;
switch (ch) {
case '}':
json->offset++;
ret = KORE_RESULT_OK;
goto cleanup;
case '"':
if ((key = json_get_string(json)) == NULL)
goto cleanup;
break;
default:
goto cleanup;
}
if (!json_consume_whitespace(json))
goto cleanup;
if (!json_next(json, &ch))
goto cleanup;
if (ch != ':')
goto cleanup;
if (!json_consume_whitespace(json))
goto cleanup;
if (!json_peek(json, &ch))
goto cleanup;
if (!json_guess_type(ch, &type))
goto cleanup;
item = json_item_alloc(type, key, object);
if (!item->parse(json, item))
goto cleanup;
key = NULL;
if (!json_consume_whitespace(json))
goto cleanup;
if (!json_next(json, &ch))
goto cleanup;
if (ch == ',')
continue;
if (ch == '}') {
ret = KORE_RESULT_OK;
break;
}
break;
}
cleanup:
if (ret == KORE_RESULT_ERROR && json->error == 0)
json->error = KORE_JSON_ERR_INVALID_OBJECT;
json->depth--;
return (ret);
}
static int
json_parse_array(struct kore_json *json, struct kore_json_item *array)
{
u_int8_t ch;
char *key;
struct kore_json_item *item;
int ret, type;
if (json->depth++ >= KORE_JSON_DEPTH_MAX) {
json->error = KORE_JSON_ERR_DEPTH;
return (KORE_RESULT_ERROR);
}
key = NULL;
ret = KORE_RESULT_ERROR;
if (!json_next(json, &ch))
goto cleanup;
if (ch != '[')
goto cleanup;
for (;;) {
if (!json_consume_whitespace(json))
goto cleanup;
if (!json_peek(json, &ch))
goto cleanup;
if (ch == ']') {
json->offset++;
ret = KORE_RESULT_OK;
goto cleanup;
}
if (!json_guess_type(ch, &type))
goto cleanup;
item = json_item_alloc(type, key, array);
if (!item->parse(json, item))
goto cleanup;
key = NULL;
if (!json_consume_whitespace(json))
goto cleanup;
if (!json_next(json, &ch))
goto cleanup;
if (ch == ',')
continue;
if (ch == ']') {
ret = KORE_RESULT_OK;
break;
}
break;
}
cleanup:
if (ret == KORE_RESULT_ERROR && json->error == 0)
json->error = KORE_JSON_ERR_INVALID_ARRAY;
json->depth--;
return (ret);
}
static int
json_parse_string(struct kore_json *json, struct kore_json_item *string)
{
char *value;
if ((value = json_get_string(json)) == NULL)
return (KORE_RESULT_ERROR);
string->type = KORE_JSON_TYPE_STRING;
string->data.string = kore_strdup(value);
return (KORE_RESULT_OK);
}
static int
json_parse_number(struct kore_json *json, struct kore_json_item *number)
{
u_int8_t ch;
int ret;
char *str;
str = NULL;
ret = KORE_RESULT_ERROR;
kore_buf_reset(&json->tmpbuf);
for (;;) {
if (!json_peek(json, &ch))
goto cleanup;
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '+':
case '-':
case 'e':
case 'E':
case '.':
kore_buf_append(&json->tmpbuf, &ch, sizeof(ch));
json->offset++;
continue;
}
break;
}
str = kore_buf_stringify(&json->tmpbuf, NULL);
number->data.number = kore_strtodouble(str, -DBL_MAX, DBL_MAX, &ret);
if (ret != KORE_RESULT_OK)
goto cleanup;
number->type = KORE_JSON_TYPE_NUMBER;
cleanup:
if (ret == KORE_RESULT_ERROR && json->error == 0)
json->error = KORE_JSON_ERR_INVALID_NUMBER;
return (ret);
}
static int
json_parse_literal(struct kore_json *json, struct kore_json_item *literal)
{
size_t len, idx;
int ret, val;
u_int8_t ch, *tmpl;
ret = KORE_RESULT_ERROR;
if (!json_next(json, &ch))
goto cleanup;
switch (ch) {
case 'f':
val = KORE_JSON_FALSE;
tmpl = json_false_literal;
len = sizeof(json_false_literal) - 1;
break;
case 'n':
val = KORE_JSON_NULL;
tmpl = json_null_literal;
len = sizeof(json_null_literal) - 1;
break;
case 't':
val = KORE_JSON_TRUE;
tmpl = json_true_literal;
len = sizeof(json_true_literal) - 1;
break;
default:
goto cleanup;
}
for (idx = 0; idx < len; idx++) {
if (!json_next(json, &ch))
goto cleanup;
if (ch != tmpl[idx + 1])
goto cleanup;
}
literal->data.literal = val;
literal->type = KORE_JSON_TYPE_LITERAL;
ret = KORE_RESULT_OK;
cleanup:
if (ret == KORE_RESULT_ERROR && json->error == 0)
json->error = KORE_JSON_ERR_INVALID_LITERAL;
return (ret);
}
static char *
json_get_string(struct kore_json *json)
{
u_int8_t ch;
char *res;
res = NULL;
if (!json_next(json, &ch))
goto cleanup;
if (ch != '"')
goto cleanup;
kore_buf_reset(&json->tmpbuf);
for (;;) {
if (!json_next(json, &ch))
goto cleanup;
if (ch == '"')
break;
if (ch <= 0x1f)
goto cleanup;
if (ch == '\\') {
if (!json_next(json, &ch))
goto cleanup;
switch (ch) {
case '\"':
case '\\':
case '/':
break;
case 'b':
ch = '\b';
break;
case 'f':
ch = '\f';
break;
case 'n':
ch = '\n';
break;
case 'r':
ch = '\r';
break;
case 'u':
/* XXX - not supported. */
goto cleanup;
}
}
kore_buf_append(&json->tmpbuf, &ch, sizeof(ch));
}
res = kore_buf_stringify(&json->tmpbuf, NULL);
cleanup:
if (res == NULL && json->error == 0)
json->error = KORE_JSON_ERR_INVALID_STRING;
return (res);
}