diff options
Diffstat (limited to 'src/libbasic/json.c')
-rw-r--r-- | src/libbasic/json.c | 871 |
1 files changed, 871 insertions, 0 deletions
diff --git a/src/libbasic/json.c b/src/libbasic/json.c new file mode 100644 index 0000000000..daa98fc815 --- /dev/null +++ b/src/libbasic/json.c @@ -0,0 +1,871 @@ +/*** + 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 <errno.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "alloc-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "macro.h" +#include "string-util.h" +#include "utf8.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) { + int r; + + 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) { + size_t i; + + ret->objects = new0(JsonVariant, variant->size); + if (!ret->objects) + return -ENOMEM; + + for (i = 0; i < variant->size; ++i) { + 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) { + size_t i; + + assert(variant); + + if (!variant->objects) + return NULL; + + for (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) { + size_t i; + + assert(variant); + assert(variant->type == JSON_VARIANT_OBJECT); + assert(variant->objects); + + for (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') { + char16_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, (char32_t) x); + else if (utf16_is_trailing_surrogate(x)) + return -EINVAL; + else { + char16_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; + int r; + + stopper = !json_is_value(var) && var->value.integer == terminator; + + 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_json_variant_unref_ 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_json_variant_unref_ JsonVariant *p = NULL; + + 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_json_variant_unref_ JsonVariant *var = NULL; + _cleanup_free_ char *rstr = 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; +} |