/*-*- 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 .
***/
#include 
#include 
#include 
#include 
#include 
#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;
}