json.c revision fecb719ec1e1abc665f91d55adaa4951db5c1bed
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sys/types.h>
#include <math.h>
#include "macro.h"
#include "utf8.h"
#include "json.h"
int json_variant_new(JsonVariant **ret, JsonVariantType type) {
JsonVariant *v;
v = new0(JsonVariant, 1);
if (!v)
return -ENOMEM;
v->type = type;
*ret = v;
return 0;
}
static int json_variant_deep_copy(JsonVariant *ret, JsonVariant *variant) {
assert(ret);
assert(variant);
ret->type = variant->type;
ret->size = variant->size;
if (variant->type == JSON_VARIANT_STRING) {
ret->string = memdup(variant->string, variant->size+1);
if (!ret->string)
return -ENOMEM;
} else if (variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT) {
ret->objects = new0(JsonVariant, variant->size);
if (!ret->objects)
return -ENOMEM;
for (unsigned i = 0; i < variant->size; ++i) {
int r;
r = json_variant_deep_copy(&ret->objects[i], &variant->objects[i]);
if (r < 0)
return r;
}
}
else
ret->value = variant->value;
return 0;
}
static JsonVariant *json_object_unref(JsonVariant *variant);
static JsonVariant *json_variant_unref_inner(JsonVariant *variant) {
if (!variant)
return NULL;
if (variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT)
return json_object_unref(variant);
else if (variant->type == JSON_VARIANT_STRING)
free(variant->string);
return NULL;
}
static JsonVariant *json_raw_unref(JsonVariant *variant, size_t size) {
if (!variant)
return NULL;
for (size_t i = 0; i < size; ++i)
json_variant_unref_inner(&variant[i]);
free(variant);
return NULL;
}
static JsonVariant *json_object_unref(JsonVariant *variant) {
assert(variant);
if (!variant->objects)
return NULL;
for (unsigned i = 0; i < variant->size; ++i)
json_variant_unref_inner(&variant->objects[i]);
free(variant->objects);
return NULL;
}
static JsonVariant **json_variant_array_unref(JsonVariant **variant) {
size_t i = 0;
JsonVariant *p = NULL;
if (!variant)
return NULL;
while((p = (variant[i++])) != NULL) {
if (p->type == JSON_VARIANT_STRING)
free(p->string);
free(p);
}
free(variant);
return NULL;
}
DEFINE_TRIVIAL_CLEANUP_FUNC(JsonVariant **, json_variant_array_unref);
JsonVariant *json_variant_unref(JsonVariant *variant) {
if (!variant)
return NULL;
if (variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT)
json_object_unref(variant);
else if (variant->type == JSON_VARIANT_STRING)
free(variant->string);
free(variant);
return NULL;
}
char *json_variant_string(JsonVariant *variant){
assert(variant);
assert(variant->type == JSON_VARIANT_STRING);
return variant->string;
}
bool json_variant_bool(JsonVariant *variant) {
assert(variant);
assert(variant->type == JSON_VARIANT_BOOLEAN);
return variant->value.boolean;
}
intmax_t json_variant_integer(JsonVariant *variant) {
assert(variant);
assert(variant->type == JSON_VARIANT_INTEGER);
return variant->value.integer;
}
double json_variant_real(JsonVariant *variant) {
assert(variant);
assert(variant->type == JSON_VARIANT_REAL);
return variant->value.real;
}
JsonVariant *json_variant_element(JsonVariant *variant, unsigned index) {
assert(variant);
assert(variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT);
assert(index < variant->size);
assert(variant->objects);
return &variant->objects[index];
}
JsonVariant *json_variant_value(JsonVariant *variant, const char *key) {
assert(variant);
assert(variant->type == JSON_VARIANT_OBJECT);
assert(variant->objects);
for (unsigned i = 0; i < variant->size; i += 2) {
JsonVariant *p = &variant->objects[i];
if (p->type == JSON_VARIANT_STRING && streq(key, p->string))
return &variant->objects[i + 1];
}
return NULL;
}
static void inc_lines(unsigned *line, const char *s, size_t n) {
const char *p = s;
if (!line)
return;
for (;;) {
const char *f;
f = memchr(p, '\n', n);
if (!f)
return;
n -= (f - p) + 1;
p = f + 1;
(*line)++;
}
}
static int unhex_ucs2(const char *c, uint16_t *ret) {
int aa, bb, cc, dd;
uint16_t x;
assert(c);
assert(ret);
aa = unhexchar(c[0]);
if (aa < 0)
return -EINVAL;
bb = unhexchar(c[1]);
if (bb < 0)
return -EINVAL;
cc = unhexchar(c[2]);
if (cc < 0)
return -EINVAL;
dd = unhexchar(c[3]);
if (dd < 0)
return -EINVAL;
x = ((uint16_t) aa << 12) |
((uint16_t) bb << 8) |
((uint16_t) cc << 4) |
((uint16_t) dd);
if (x <= 0)
return -EINVAL;
*ret = x;
return 0;
}
static int json_parse_string(const char **p, char **ret) {
_cleanup_free_ char *s = NULL;
size_t n = 0, allocated = 0;
const char *c;
assert(p);
assert(*p);
assert(ret);
c = *p;
if (*c != '"')
return -EINVAL;
c++;
for (;;) {
int len;
/* Check for EOF */
if (*c == 0)
return -EINVAL;
/* Check for control characters 0x00..0x1f */
if (*c > 0 && *c < ' ')
return -EINVAL;
/* Check for control character 0x7f */
if (*c == 0x7f)
return -EINVAL;
if (*c == '"') {
if (!s) {
s = strdup("");
if (!s)
return -ENOMEM;
} else
s[n] = 0;
*p = c + 1;
*ret = s;
s = NULL;
return JSON_STRING;
}
if (*c == '\\') {
char ch = 0;
c++;
if (*c == 0)
return -EINVAL;
if (IN_SET(*c, '"', '\\', '/'))
ch = *c;
else if (*c == 'b')
ch = '\b';
else if (*c == 'f')
ch = '\f';
else if (*c == 'n')
ch = '\n';
else if (*c == 'r')
ch = '\r';
else if (*c == 't')
ch = '\t';
else if (*c == 'u') {
uint16_t x;
int r;
r = unhex_ucs2(c + 1, &x);
if (r < 0)
return r;
c += 5;
if (!GREEDY_REALLOC(s, allocated, n + 4))
return -ENOMEM;
if (!utf16_is_surrogate(x))
n += utf8_encode_unichar(s + n, x);
else if (utf16_is_trailing_surrogate(x))
return -EINVAL;
else {
uint16_t y;
if (c[0] != '\\' || c[1] != 'u')
return -EINVAL;
r = unhex_ucs2(c + 2, &y);
if (r < 0)
return r;
c += 6;
if (!utf16_is_trailing_surrogate(y))
return -EINVAL;
n += utf8_encode_unichar(s + n, utf16_surrogate_pair_to_unichar(x, y));
}
continue;
} else
return -EINVAL;
if (!GREEDY_REALLOC(s, allocated, n + 2))
return -ENOMEM;
s[n++] = ch;
c ++;
continue;
}
len = utf8_encoded_valid_unichar(c);
if (len < 0)
return len;
if (!GREEDY_REALLOC(s, allocated, n + len + 1))
return -ENOMEM;
memcpy(s + n, c, len);
n += len;
c += len;
}
}
static int json_parse_number(const char **p, union json_value *ret) {
bool negative = false, exponent_negative = false, is_double = false;
double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0;
intmax_t i = 0;
const char *c;
assert(p);
assert(*p);
assert(ret);
c = *p;
if (*c == '-') {
negative = true;
c++;
}
if (*c == '0')
c++;
else {
if (!strchr("123456789", *c) || *c == 0)
return -EINVAL;
do {
if (!is_double) {
int64_t t;
t = 10 * i + (*c - '0');
if (t < i) /* overflow */
is_double = false;
else
i = t;
}
x = 10.0 * x + (*c - '0');
c++;
} while (strchr("0123456789", *c) && *c != 0);
}
if (*c == '.') {
is_double = true;
c++;
if (!strchr("0123456789", *c) || *c == 0)
return -EINVAL;
do {
y = 10.0 * y + (*c - '0');
shift = 10.0 * shift;
c++;
} while (strchr("0123456789", *c) && *c != 0);
}
if (*c == 'e' || *c == 'E') {
is_double = true;
c++;
if (*c == '-') {
exponent_negative = true;
c++;
} else if (*c == '+')
c++;
if (!strchr("0123456789", *c) || *c == 0)
return -EINVAL;
do {
exponent = 10.0 * exponent + (*c - '0');
c++;
} while (strchr("0123456789", *c) && *c != 0);
}
*p = c;
if (is_double) {
ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10((exponent_negative ? -1.0 : 1.0) * exponent);
return JSON_REAL;
} else {
ret->integer = negative ? -i : i;
return JSON_INTEGER;
}
}
int json_tokenize(
const char **p,
char **ret_string,
union json_value *ret_value,
void **state,
unsigned *line) {
const char *c;
int t;
int r;
enum {
STATE_NULL,
STATE_VALUE,
STATE_VALUE_POST,
};
assert(p);
assert(*p);
assert(ret_string);
assert(ret_value);
assert(state);
t = PTR_TO_INT(*state);
c = *p;
if (t == STATE_NULL) {
if (line)
*line = 1;
t = STATE_VALUE;
}
for (;;) {
const char *b;
b = c + strspn(c, WHITESPACE);
if (*b == 0)
return JSON_END;
inc_lines(line, c, b - c);
c = b;
switch (t) {
case STATE_VALUE:
if (*c == '{') {
*ret_string = NULL;
*ret_value = JSON_VALUE_NULL;
*p = c + 1;
*state = INT_TO_PTR(STATE_VALUE);
return JSON_OBJECT_OPEN;
} else if (*c == '}') {
*ret_string = NULL;
*ret_value = JSON_VALUE_NULL;
*p = c + 1;
*state = INT_TO_PTR(STATE_VALUE_POST);
return JSON_OBJECT_CLOSE;
} else if (*c == '[') {
*ret_string = NULL;
*ret_value = JSON_VALUE_NULL;
*p = c + 1;
*state = INT_TO_PTR(STATE_VALUE);
return JSON_ARRAY_OPEN;
} else if (*c == ']') {
*ret_string = NULL;
*ret_value = JSON_VALUE_NULL;
*p = c + 1;
*state = INT_TO_PTR(STATE_VALUE_POST);
return JSON_ARRAY_CLOSE;
} else if (*c == '"') {
r = json_parse_string(&c, ret_string);
if (r < 0)
return r;
*ret_value = JSON_VALUE_NULL;
*p = c;
*state = INT_TO_PTR(STATE_VALUE_POST);
return r;
} else if (strchr("-0123456789", *c)) {
r = json_parse_number(&c, ret_value);
if (r < 0)
return r;
*ret_string = NULL;
*p = c;
*state = INT_TO_PTR(STATE_VALUE_POST);
return r;
} else if (startswith(c, "true")) {
*ret_string = NULL;
ret_value->boolean = true;
*p = c + 4;
*state = INT_TO_PTR(STATE_VALUE_POST);
return JSON_BOOLEAN;
} else if (startswith(c, "false")) {
*ret_string = NULL;
ret_value->boolean = false;
*p = c + 5;
*state = INT_TO_PTR(STATE_VALUE_POST);
return JSON_BOOLEAN;
} else if (startswith(c, "null")) {
*ret_string = NULL;
*ret_value = JSON_VALUE_NULL;
*p = c + 4;
*state = INT_TO_PTR(STATE_VALUE_POST);
return JSON_NULL;
} else
return -EINVAL;
case STATE_VALUE_POST:
if (*c == ':') {
*ret_string = NULL;
*ret_value = JSON_VALUE_NULL;
*p = c + 1;
*state = INT_TO_PTR(STATE_VALUE);
return JSON_COLON;
} else if (*c == ',') {
*ret_string = NULL;
*ret_value = JSON_VALUE_NULL;
*p = c + 1;
*state = INT_TO_PTR(STATE_VALUE);
return JSON_COMMA;
} else if (*c == '}') {
*ret_string = NULL;
*ret_value = JSON_VALUE_NULL;
*p = c + 1;
*state = INT_TO_PTR(STATE_VALUE_POST);
return JSON_OBJECT_CLOSE;
} else if (*c == ']') {
*ret_string = NULL;
*ret_value = JSON_VALUE_NULL;
*p = c + 1;
*state = INT_TO_PTR(STATE_VALUE_POST);
return JSON_ARRAY_CLOSE;
} else
return -EINVAL;
}
}
}
static bool json_is_value(JsonVariant *var) {
assert(var);
return var->type != JSON_VARIANT_CONTROL;
}
static int json_scoped_parse(JsonVariant **tokens, size_t *i, size_t n, JsonVariant *scope) {
bool arr = scope->type == JSON_VARIANT_ARRAY;
int terminator = arr ? JSON_ARRAY_CLOSE : JSON_OBJECT_CLOSE;
size_t allocated = 0, size = 0;
JsonVariant *key = NULL, *value = NULL, *var = NULL, *items = NULL;
enum {
STATE_KEY,
STATE_COLON,
STATE_COMMA,
STATE_VALUE
} state = arr ? STATE_VALUE : STATE_KEY;
assert(tokens);
assert(i);
assert(scope);
while((var = *i < n ? tokens[(*i)++] : NULL) != NULL) {
bool stopper = !json_is_value(var) && var->value.integer == terminator;
int r;
if (stopper) {
if (state != STATE_COMMA && size > 0)
goto error;
goto out;
}
if (state == STATE_KEY) {
if (var->type != JSON_VARIANT_STRING)
goto error;
else {
key = var;
state = STATE_COLON;
}
}
else if (state == STATE_COLON) {
if (key == NULL)
goto error;
if (json_is_value(var))
goto error;
if (var->value.integer != JSON_COLON)
goto error;
state = STATE_VALUE;
}
else if (state == STATE_VALUE) {
_cleanup_jsonunref_ JsonVariant *v = NULL;
size_t toadd = arr ? 1 : 2;
if (!json_is_value(var)) {
int type = (var->value.integer == JSON_ARRAY_OPEN) ? JSON_VARIANT_ARRAY : JSON_VARIANT_OBJECT;
r = json_variant_new(&v, type);
if (r < 0)
goto error;
r = json_scoped_parse(tokens, i, n, v);
if (r < 0)
goto error;
value = v;
}
else
value = var;
if(!GREEDY_REALLOC(items, allocated, size + toadd))
goto error;
if (arr) {
r = json_variant_deep_copy(&items[size], value);
if (r < 0)
goto error;
} else {
r = json_variant_deep_copy(&items[size], key);
if (r < 0)
goto error;
r = json_variant_deep_copy(&items[size+1], value);
if (r < 0)
goto error;
}
size += toadd;
state = STATE_COMMA;
}
else if (state == STATE_COMMA) {
if (json_is_value(var))
goto error;
if (var->value.integer != JSON_COMMA)
goto error;
key = NULL;
value = NULL;
state = arr ? STATE_VALUE : STATE_KEY;
}
}
error:
json_raw_unref(items, size);
return -EBADMSG;
out:
scope->size = size;
scope->objects = items;
return scope->type;
}
static int json_parse_tokens(JsonVariant **tokens, size_t ntokens, JsonVariant **rv) {
size_t it = 0;
int r;
JsonVariant *e;
_cleanup_jsonunref_ JsonVariant *p;
assert(tokens);
assert(ntokens);
e = tokens[it++];
r = json_variant_new(&p, JSON_VARIANT_OBJECT);
if (r < 0)
return r;
if (e->type != JSON_VARIANT_CONTROL && e->value.integer != JSON_OBJECT_OPEN)
return -EBADMSG;
r = json_scoped_parse(tokens, &it, ntokens, p);
if (r < 0)
return r;
*rv = p;
p = NULL;
return 0;
}
static int json_tokens(const char *string, size_t size, JsonVariant ***tokens, size_t *n) {
_cleanup_free_ char *buf = NULL;
_cleanup_(json_variant_array_unrefp) JsonVariant **items = NULL;
union json_value v = {};
void *json_state = NULL;
const char *p;
int t, r;
size_t allocated = 0, s = 0;
assert(string);
assert(n);
if (size <= 0)
return -EBADMSG;
buf = strndup(string, size);
if (!buf)
return -ENOMEM;
p = buf;
for (;;) {
_cleanup_free_ char *rstr = NULL;
_cleanup_jsonunref_ JsonVariant *var = NULL;
t = json_tokenize(&p, &rstr, &v, &json_state, NULL);
if (t < 0)
return t;
else if (t == JSON_END)
break;
if (t <= JSON_ARRAY_CLOSE) {
r = json_variant_new(&var, JSON_VARIANT_CONTROL);
if (r < 0)
return r;
var->value.integer = t;
} else {
switch (t) {
case JSON_STRING:
r = json_variant_new(&var, JSON_VARIANT_STRING);
if (r < 0)
return r;
var->size = strlen(rstr);
var->string = strdup(rstr);
if (!var->string) {
return -ENOMEM;
}
break;
case JSON_INTEGER:
r = json_variant_new(&var, JSON_VARIANT_INTEGER);
if (r < 0)
return r;
var->value = v;
break;
case JSON_REAL:
r = json_variant_new(&var, JSON_VARIANT_REAL);
if (r < 0)
return r;
var->value = v;
break;
case JSON_BOOLEAN:
r = json_variant_new(&var, JSON_VARIANT_BOOLEAN);
if (r < 0)
return r;
var->value = v;
break;
case JSON_NULL:
r = json_variant_new(&var, JSON_VARIANT_NULL);
if (r < 0)
return r;
break;
}
}
if (!GREEDY_REALLOC(items, allocated, s+2))
return -ENOMEM;
items[s++] = var;
items[s] = NULL;
var = NULL;
}
*n = s;
*tokens = items;
items = NULL;
return 0;
}
int json_parse(const char *string, JsonVariant **rv) {
_cleanup_(json_variant_array_unrefp) JsonVariant **s = NULL;
JsonVariant *v = NULL;
size_t n = 0;
int r;
assert(string);
assert(rv);
r = json_tokens(string, strlen(string), &s, &n);
if (r < 0)
return r;
r = json_parse_tokens(s, n, &v);
if (r < 0)
return r;
*rv = v;
return 0;
}