#include <k5-platform.h>
#include <k5-base64.h>
#include <k5-json.h>
#include <k5-buf.h>
#define MAX_DECODE_DEPTH 64
#define MIN_ALLOC_SLOT 16
typedef void (*type_dealloc_fn)(void *val);
typedef struct json_type_st {
k5_json_tid tid;
const char *name;
type_dealloc_fn dealloc;
} *json_type;
struct value_base {
json_type isa;
unsigned int ref_cnt;
};
#define PTR2BASE(ptr) (((struct value_base *)ptr) - 1)
#define BASE2PTR(ptr) ((void *)(((struct value_base *)ptr) + 1))
k5_json_value
k5_json_retain(k5_json_value val)
{
struct value_base *p;
if (val == NULL)
return val;
p = PTR2BASE(val);
assert(p->ref_cnt != 0);
p->ref_cnt++;
return val;
}
void
k5_json_release(k5_json_value val)
{
struct value_base *p;
if (val == NULL)
return;
p = PTR2BASE(val);
assert(p->ref_cnt != 0);
p->ref_cnt--;
if (p->ref_cnt == 0) {
if (p->isa->dealloc != NULL)
p->isa->dealloc(val);
free(p);
}
}
static json_type
get_isa(k5_json_value val)
{
struct value_base *p = PTR2BASE(val);
return p->isa;
}
k5_json_tid
k5_json_get_tid(k5_json_value val)
{
json_type isa = get_isa(val);
return isa->tid;
}
static k5_json_value
alloc_value(json_type type, size_t size)
{
struct value_base *p = calloc(1, size + sizeof(*p));
if (p == NULL)
return NULL;
p->isa = type;
p->ref_cnt = 1;
return BASE2PTR(p);
}
static struct json_type_st null_type = { K5_JSON_TID_NULL, "null", NULL };
int
k5_json_null_create(k5_json_null *val_out)
{
*val_out = alloc_value(&null_type, 0);
return (*val_out == NULL) ? ENOMEM : 0;
}
int
k5_json_null_create_val(k5_json_value *val_out)
{
*val_out = alloc_value(&null_type, 0);
return (*val_out == NULL) ? ENOMEM : 0;
}
static struct json_type_st bool_type = { K5_JSON_TID_BOOL, "bool", NULL };
int
k5_json_bool_create(int truth, k5_json_bool *val_out)
{
k5_json_bool b;
*val_out = NULL;
b = alloc_value(&bool_type, 1);
if (b == NULL)
return ENOMEM;
*(unsigned char *)b = !!truth;
*val_out = b;
return 0;
}
int
k5_json_bool_value(k5_json_bool bval)
{
return *(unsigned char *)bval;
}
struct k5_json_array_st {
k5_json_value *values;
size_t len;
size_t allocated;
};
static void
array_dealloc(void *ptr)
{
k5_json_array array = ptr;
size_t i;
for (i = 0; i < array->len; i++)
k5_json_release(array->values[i]);
free(array->values);
}
static struct json_type_st array_type = {
K5_JSON_TID_ARRAY, "array", array_dealloc
};
int
k5_json_array_create(k5_json_array *val_out)
{
*val_out = alloc_value(&array_type, sizeof(struct k5_json_array_st));
return (*val_out == NULL) ? ENOMEM : 0;
}
int
k5_json_array_add(k5_json_array array, k5_json_value val)
{
k5_json_value *ptr;
size_t new_alloc;
if (array->len >= array->allocated) {
new_alloc = array->len + 1 + (array->len >> 1);
if (new_alloc < MIN_ALLOC_SLOT)
new_alloc = MIN_ALLOC_SLOT;
ptr = realloc(array->values, new_alloc * sizeof(*array->values));
if (ptr == NULL)
return ENOMEM;
array->values = ptr;
array->allocated = new_alloc;
}
array->values[array->len++] = k5_json_retain(val);
return 0;
}
size_t
k5_json_array_length(k5_json_array array)
{
return array->len;
}
k5_json_value
k5_json_array_get(k5_json_array array, size_t idx)
{
if (idx >= array->len)
abort();
return array->values[idx];
}
void
k5_json_array_set(k5_json_array array, size_t idx, k5_json_value val)
{
if (idx >= array->len)
abort();
k5_json_release(array->values[idx]);
array->values[idx] = k5_json_retain(val);
}
int
k5_json_array_fmt(k5_json_array *array_out, const char *template, ...)
{
const char *p;
va_list ap;
const char *cstring;
unsigned char *data;
size_t len;
long long nval;
k5_json_array array;
k5_json_value val;
k5_json_number num;
k5_json_string str;
k5_json_bool b;
k5_json_null null;
int truth, ret;
*array_out = NULL;
if (k5_json_array_create(&array))
return ENOMEM;
va_start(ap, template);
for (p = template; *p != '\0'; p++) {
switch (*p) {
case 'v':
val = k5_json_retain(va_arg(ap, k5_json_value));
break;
case 'n':
if (k5_json_null_create(&null))
goto err;
val = null;
break;
case 'b':
truth = va_arg(ap, int);
if (k5_json_bool_create(truth, &b))
goto err;
val = b;
break;
case 'i':
nval = va_arg(ap, int);
if (k5_json_number_create(nval, &num))
goto err;
val = num;
break;
case 'L':
nval = va_arg(ap, long long);
if (k5_json_number_create(nval, &num))
goto err;
val = num;
break;
case 's':
cstring = va_arg(ap, const char *);
if (cstring == NULL) {
if (k5_json_null_create(&null))
goto err;
val = null;
} else {
if (k5_json_string_create(cstring, &str))
goto err;
val = str;
}
break;
case 'B':
data = va_arg(ap, unsigned char *);
len = va_arg(ap, size_t);
if (k5_json_string_create_base64(data, len, &str))
goto err;
val = str;
break;
default:
goto err;
}
ret = k5_json_array_add(array, val);
k5_json_release(val);
if (ret)
goto err;
}
va_end(ap);
*array_out = array;
return 0;
err:
va_end(ap);
k5_json_release(array);
return ENOMEM;
}
struct entry {
char *key;
k5_json_value value;
};
struct k5_json_object_st {
struct entry *entries;
size_t len;
size_t allocated;
};
static void
object_dealloc(void *ptr)
{
k5_json_object obj = ptr;
size_t i;
for (i = 0; i < obj->len; i++) {
free(obj->entries[i].key);
k5_json_release(obj->entries[i].value);
}
free(obj->entries);
}
static struct json_type_st object_type = {
K5_JSON_TID_OBJECT, "object", object_dealloc
};
int
k5_json_object_create(k5_json_object *val_out)
{
*val_out = alloc_value(&object_type, sizeof(struct k5_json_object_st));
return (*val_out == NULL) ? ENOMEM : 0;
}
size_t
k5_json_object_count(k5_json_object obj)
{
return obj->len;
}
static struct entry *
object_search(k5_json_object obj, const char *key)
{
size_t i;
for (i = 0; i < obj->len; i++) {
if (strcmp(key, obj->entries[i].key) == 0)
return &obj->entries[i];
}
return NULL;
}
k5_json_value
k5_json_object_get(k5_json_object obj, const char *key)
{
struct entry *ent;
ent = object_search(obj, key);
if (ent == NULL)
return NULL;
return ent->value;
}
int
k5_json_object_set(k5_json_object obj, const char *key, k5_json_value val)
{
struct entry *ent, *ptr;
size_t new_alloc, i;
ent = object_search(obj, key);
if (ent != NULL) {
k5_json_release(ent->value);
if (val == NULL) {
free(ent->key);
for (i = ent - obj->entries; i < obj->len - 1; i++)
obj->entries[i] = obj->entries[i + 1];
obj->len--;
} else {
ent->value = k5_json_retain(val);
}
return 0;
}
if (val == NULL)
return 0;
if (obj->len >= obj->allocated) {
new_alloc = obj->len + 1 + (obj->len >> 1);
if (new_alloc < MIN_ALLOC_SLOT)
new_alloc = MIN_ALLOC_SLOT;
ptr = realloc(obj->entries, new_alloc * sizeof(*obj->entries));
if (ptr == NULL)
return ENOMEM;
obj->entries = ptr;
obj->allocated = new_alloc;
}
obj->entries[obj->len].key = strdup(key);
if (obj->entries[obj->len].key == NULL)
return ENOMEM;
obj->entries[obj->len].value = k5_json_retain(val);
obj->len++;
return 0;
}
void
k5_json_object_iterate(k5_json_object obj, k5_json_object_iterator_fn func,
void *arg)
{
size_t i;
for (i = 0; i < obj->len; i++)
func(arg, obj->entries[i].key, obj->entries[i].value);
}
static struct json_type_st string_type = {
K5_JSON_TID_STRING, "string", NULL
};
int
k5_json_string_create(const char *cstring, k5_json_string *val_out)
{
return k5_json_string_create_len(cstring, strlen(cstring), val_out);
}
int
k5_json_string_create_len(const void *data, size_t len,
k5_json_string *val_out)
{
char *s;
*val_out = NULL;
s = alloc_value(&string_type, len + 1);
if (s == NULL)
return ENOMEM;
if (len > 0)
memcpy(s, data, len);
s[len] = '\0';
*val_out = (k5_json_string)s;
return 0;
}
int
k5_json_string_create_base64(const void *data, size_t len,
k5_json_string *val_out)
{
char *base64;
int ret;
*val_out = NULL;
base64 = k5_base64_encode(data, len);
if (base64 == NULL)
return ENOMEM;
ret = k5_json_string_create(base64, val_out);
free(base64);
return ret;
}
const char *
k5_json_string_utf8(k5_json_string string)
{
return (const char *)string;
}
int
k5_json_string_unbase64(k5_json_string string, unsigned char **data_out,
size_t *len_out)
{
unsigned char *data;
size_t len;
*data_out = NULL;
*len_out = 0;
data = k5_base64_decode((const char *)string, &len);
if (data == NULL)
return (len == 0) ? ENOMEM : EINVAL;
*data_out = data;
*len_out = len;
return 0;
}
static struct json_type_st number_type = {
K5_JSON_TID_NUMBER, "number", NULL
};
int
k5_json_number_create(long long number, k5_json_number *val_out)
{
k5_json_number n;
*val_out = NULL;
n = alloc_value(&number_type, sizeof(long long));
if (n == NULL)
return ENOMEM;
*((long long *)n) = number;
*val_out = n;
return 0;
}
long long
k5_json_number_value(k5_json_number number)
{
return *(long long *)number;
}
static const char quotemap_json[] = "\"\\/bfnrt";
static const char quotemap_c[] = "\"\\/\b\f\n\r\t";
static const char needs_quote[] = "\\\"\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17"
"\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37";
static int encode_value(struct k5buf *buf, k5_json_value val);
static void
encode_string(struct k5buf *buf, const char *str)
{
size_t n;
const char *p;
k5_buf_add(buf, "\"");
while (*str != '\0') {
n = strcspn(str, needs_quote);
k5_buf_add_len(buf, str, n);
str += n;
if (*str == '\0')
break;
k5_buf_add(buf, "\\");
p = strchr(quotemap_c, *str);
if (p != NULL)
k5_buf_add_len(buf, quotemap_json + (p - quotemap_c), 1);
else
k5_buf_add_fmt(buf, "u00%02X", (unsigned int)*str);
str++;
}
k5_buf_add(buf, "\"");
}
struct obj_ctx {
struct k5buf *buf;
int ret;
int first;
};
static void
encode_obj_entry(void *ctx, const char *key, k5_json_value value)
{
struct obj_ctx *j = ctx;
if (j->ret)
return;
if (j->first)
j->first = 0;
else
k5_buf_add(j->buf, ",");
encode_string(j->buf, key);
k5_buf_add(j->buf, ":");
j->ret = encode_value(j->buf, value);
}
static int
encode_value(struct k5buf *buf, k5_json_value val)
{
k5_json_tid type;
int ret;
size_t i, len;
struct obj_ctx ctx;
if (val == NULL)
return EINVAL;
type = k5_json_get_tid(val);
switch (type) {
case K5_JSON_TID_ARRAY:
k5_buf_add(buf, "[");
len = k5_json_array_length(val);
for (i = 0; i < len; i++) {
if (i != 0)
k5_buf_add(buf, ",");
ret = encode_value(buf, k5_json_array_get(val, i));
if (ret)
return ret;
}
k5_buf_add(buf, "]");
return 0;
case K5_JSON_TID_OBJECT:
k5_buf_add(buf, "{");
ctx.buf = buf;
ctx.ret = 0;
ctx.first = 1;
k5_json_object_iterate(val, encode_obj_entry, &ctx);
k5_buf_add(buf, "}");
return ctx.ret;
case K5_JSON_TID_STRING:
encode_string(buf, k5_json_string_utf8(val));
return 0;
case K5_JSON_TID_NUMBER:
k5_buf_add_fmt(buf, "%lld", k5_json_number_value(val));
return 0;
case K5_JSON_TID_NULL:
k5_buf_add(buf, "null");
return 0;
case K5_JSON_TID_BOOL:
k5_buf_add(buf, k5_json_bool_value(val) ? "true" : "false");
return 0;
default:
return EINVAL;
}
}
int
k5_json_encode(k5_json_value val, char **json_out)
{
struct k5buf buf;
int ret;
*json_out = NULL;
k5_buf_init_dynamic(&buf);
ret = encode_value(&buf, val);
if (ret) {
k5_buf_free(&buf);
return ret;
}
*json_out = k5_buf_cstring(&buf);
return (*json_out == NULL) ? ENOMEM : 0;
}
struct decode_ctx {
const unsigned char *p;
size_t depth;
};
static int parse_value(struct decode_ctx *ctx, k5_json_value *val_out);
static int
white_spaces(struct decode_ctx *ctx)
{
unsigned char c;
for (; *ctx->p != '\0'; ctx->p++) {
c = *ctx->p;
if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
return 0;
}
return -1;
}
static inline int
is_digit(unsigned char c)
{
return ('0' <= c && c <= '9');
}
static inline int
is_hex_digit(unsigned char c)
{
return is_digit(c) || ('A' <= c && c <= 'F');
}
static inline unsigned int
hexval(unsigned char c)
{
if (is_digit(c))
return c - '0';
else if ('A' <= c && c <= 'F')
return c - 'A' + 10;
abort();
}
static int
parse_number(struct decode_ctx *ctx, k5_json_number *val_out)
{
const unsigned long long umax = ~0ULL, smax = (1ULL << 63) - 1;
unsigned long long number = 0;
int neg = 1;
*val_out = NULL;
if (*ctx->p == '-') {
neg = -1;
ctx->p++;
}
if (!is_digit(*ctx->p))
return EINVAL;
while (is_digit(*ctx->p)) {
if (number + 1 > umax / 10)
return EOVERFLOW;
number = (number * 10) + (*ctx->p - '0');
ctx->p++;
}
if (number > smax + 1 || (number > smax && neg == 1))
return EOVERFLOW;
return k5_json_number_create(number * neg, val_out);
}
static int
parse_string(struct decode_ctx *ctx, char **str_out)
{
const unsigned char *p, *start, *end = NULL;
const char *q;
char *buf, *pos;
unsigned int code;
*str_out = NULL;
if (*ctx->p != '"')
return EINVAL;
start = ++ctx->p;
for (; *ctx->p != '\0'; ctx->p++) {
if (*ctx->p == '\\') {
ctx->p++;
if (*ctx->p == '\0')
return EINVAL;
} else if (*ctx->p == '"') {
end = ctx->p++;
break;
}
}
if (end == NULL)
return EINVAL;
pos = buf = malloc(end - start + 1);
if (buf == NULL)
return ENOMEM;
for (p = start; p < end;) {
if (*p == '\\') {
p++;
if (*p == 'u' && is_hex_digit(p[1]) && is_hex_digit(p[2]) &&
is_hex_digit(p[3]) && is_hex_digit(p[4])) {
code = (hexval(p[1]) << 12) | (hexval(p[2]) << 8) |
(hexval(p[3]) << 4) | hexval(p[4]);
if (code <= 0xff) {
*pos++ = code;
} else {
free(buf);
return EINVAL;
}
p += 5;
} else {
q = strchr(quotemap_json, *p);
if (q != NULL) {
*pos++ = quotemap_c[q - quotemap_json];
} else {
free(buf);
return EINVAL;
}
p++;
}
} else {
*pos++ = *p++;
}
}
*pos = '\0';
*str_out = buf;
return 0;
}
static int
parse_object_association(k5_json_object obj, struct decode_ctx *ctx)
{
char *key = NULL;
k5_json_value val;
int ret;
ret = parse_string(ctx, &key);
if (ret)
return ret;
if (white_spaces(ctx))
goto invalid;
if (*ctx->p != ':')
goto invalid;
ctx->p++;
if (white_spaces(ctx))
goto invalid;
ret = parse_value(ctx, &val);
if (ret) {
free(key);
return ret;
}
ret = k5_json_object_set(obj, key, val);
free(key);
k5_json_release(val);
return ret;
invalid:
free(key);
return EINVAL;
}
static int
parse_object(struct decode_ctx *ctx, k5_json_object *val_out)
{
k5_json_object obj = NULL;
int ret;
*val_out = NULL;
if (*ctx->p != '{')
return EINVAL;
ctx->p++;
if (white_spaces(ctx))
return EINVAL;
ret = k5_json_object_create(&obj);
if (ret)
return ret;
if (*ctx->p != '}') {
while (1) {
ret = parse_object_association(obj, ctx);
if (ret) {
k5_json_release(obj);
return ret;
}
if (white_spaces(ctx))
goto invalid;
if (*ctx->p == '}')
break;
if (*ctx->p != ',')
goto invalid;
ctx->p++;
if (white_spaces(ctx))
goto invalid;
}
}
ctx->p++;
*val_out = obj;
return 0;
invalid:
k5_json_release(obj);
return EINVAL;
}
static int
parse_array_item(k5_json_array array, struct decode_ctx *ctx)
{
k5_json_value val;
int ret;
ret = parse_value(ctx, &val);
if (ret)
return ret;
ret = k5_json_array_add(array, val);
k5_json_release(val);
return ret;
}
static int
parse_array(struct decode_ctx *ctx, k5_json_array *val_out)
{
k5_json_array array = NULL;
int ret;
*val_out = NULL;
if (*ctx->p != '[')
return EINVAL;
ctx->p++;
if (white_spaces(ctx))
return EINVAL;
ret = k5_json_array_create(&array);
if (ret)
return ret;
if (*ctx->p != ']') {
while (1) {
ret = parse_array_item(array, ctx);
if (ret) {
k5_json_release(array);
return ret;
}
if (white_spaces(ctx))
goto invalid;
if (*ctx->p == ']')
break;
if (*ctx->p != ',')
goto invalid;
ctx->p++;
if (white_spaces(ctx))
goto invalid;
}
}
ctx->p++;
*val_out = array;
return 0;
invalid:
k5_json_release(array);
return EINVAL;
}
static int
parse_value(struct decode_ctx *ctx, k5_json_value *val_out)
{
k5_json_null null;
k5_json_bool bval;
k5_json_number num;
k5_json_string str;
k5_json_object obj;
k5_json_array array;
char *cstring;
int ret;
*val_out = NULL;
if (white_spaces(ctx))
return EINVAL;
if (*ctx->p == '"') {
ret = parse_string(ctx, &cstring);
if (ret)
return ret;
ret = k5_json_string_create(cstring, &str);
free(cstring);
if (ret)
return ret;
*val_out = str;
} else if (*ctx->p == '{') {
if (ctx->depth-- == 1)
return EINVAL;
ret = parse_object(ctx, &obj);
if (ret)
return ret;
ctx->depth++;
*val_out = obj;
} else if (*ctx->p == '[') {
if (ctx->depth-- == 1)
return EINVAL;
ret = parse_array(ctx, &array);
ctx->depth++;
*val_out = array;
} else if (is_digit(*ctx->p) || *ctx->p == '-') {
ret = parse_number(ctx, &num);
if (ret)
return ret;
*val_out = num;
} else if (strncmp((char *)ctx->p, "null", 4) == 0) {
ctx->p += 4;
ret = k5_json_null_create(&null);
if (ret)
return ret;
*val_out = null;
} else if (strncmp((char *)ctx->p, "true", 4) == 0) {
ctx->p += 4;
ret = k5_json_bool_create(1, &bval);
if (ret)
return ret;
*val_out = bval;
} else if (strncmp((char *)ctx->p, "false", 5) == 0) {
ctx->p += 5;
ret = k5_json_bool_create(0, &bval);
if (ret)
return ret;
*val_out = bval;
} else {
return EINVAL;
}
return 0;
}
int
k5_json_decode(const char *string, k5_json_value *val_out)
{
struct decode_ctx ctx;
k5_json_value val;
int ret;
*val_out = NULL;
ctx.p = (unsigned char *)string;
ctx.depth = MAX_DECODE_DEPTH;
ret = parse_value(&ctx, &val);
if (ret)
return ret;
if (white_spaces(&ctx) == 0) {
k5_json_release(val);
return EINVAL;
}
*val_out = val;
return 0;
}