chiark / gitweb /
json: fix a mem leak
[elogind.git] / src / shared / json.c
index 47f801c..5d7d0db 100644 (file)
 
 #include <sys/types.h>
 #include <math.h>
-
 #include "macro.h"
-#include "log.h"
-#include "util.h"
 #include "utf8.h"
 #include "json.h"
 
-enum {
-        STATE_NULL,
-        STATE_VALUE,
-        STATE_VALUE_POST,
-};
+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;
@@ -53,6 +206,42 @@ static void inc_lines(unsigned *line, const char *s, size_t n) {
         }
 }
 
+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;
@@ -119,39 +308,40 @@ static int json_parse_string(const char **p, char **ret) {
                         else if (*c == 't')
                                 ch = '\t';
                         else if (*c == 'u') {
-                                int aa, bb, cc, dd;
                                 uint16_t x;
+                                int r;
 
-                                aa = unhexchar(c[1]);
-                                if (aa < 0)
-                                        return -EINVAL;
+                                r = unhex_ucs2(c + 1, &x);
+                                if (r < 0)
+                                        return r;
 
-                                bb = unhexchar(c[2]);
-                                if (bb < 0)
-                                        return -EINVAL;
+                                c += 5;
 
-                                cc = unhexchar(c[3]);
-                                if (cc < 0)
-                                        return -EINVAL;
+                                if (!GREEDY_REALLOC(s, allocated, n + 4))
+                                        return -ENOMEM;
 
-                                dd = unhexchar(c[4]);
-                                if (dd < 0)
+                                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;
 
-                                x =     ((uint16_t) aa << 12) |
-                                        ((uint16_t) bb << 8) |
-                                        ((uint16_t) cc << 4) |
-                                        ((uint16_t) dd);
+                                        r = unhex_ucs2(c + 2, &y);
+                                        if (r < 0)
+                                                return r;
 
-                                if (x <= 0)
-                                        return -EINVAL;
+                                        c += 6;
 
-                                if (!GREEDY_REALLOC(s, allocated, n + 4))
-                                        return -ENOMEM;
+                                        if (!utf16_is_trailing_surrogate(y))
+                                                return -EINVAL;
+
+                                        n += utf8_encode_unichar(s + n, utf16_surrogate_pair_to_unichar(x, y));
+                                }
 
-                                n += utf8_encode_unichar(s + n, x);
-                                c += 5;
                                 continue;
                         } else
                                 return -EINVAL;
@@ -249,9 +439,6 @@ static int json_parse_number(const char **p, union json_value *ret) {
                 } while (strchr("0123456789", *c) && *c != 0);
         }
 
-        if (*c != 0)
-                return -EINVAL;
-
         *p = c;
 
         if (is_double) {
@@ -274,6 +461,12 @@ int json_tokenize(
         int t;
         int r;
 
+        enum {
+                STATE_NULL,
+                STATE_VALUE,
+                STATE_VALUE_POST,
+        };
+
         assert(p);
         assert(*p);
         assert(ret_string);
@@ -407,3 +600,258 @@ int json_tokenize(
 
         }
 }
+
+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;
+}