]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/json.c
json: add json_variant_unbase64() helper
[thirdparty/systemd.git] / src / shared / json.c
index 98fa067ef4db5ceffb367fc9cd53f893fcadf399..d1a477c36d608826b2b6a5b1f7498d988911617c 100644 (file)
@@ -71,9 +71,15 @@ struct JsonVariant {
         /* While comparing two arrays, we use this for marking what we already have seen */
         bool is_marked:1;
 
-        /* Ersase from memory when freeing */
+        /* Erase from memory when freeing */
         bool sensitive:1;
 
+        /* If this is an object the fields are strictly ordered by name */
+        bool sorted:1;
+
+        /* If in addition to this object all objects referenced by it are also ordered strictly by name */
+        bool normalized:1;
+
         /* The current 'depth' of the JsonVariant, i.e. how many levels of member variants this has */
         uint16_t depth;
 
@@ -215,10 +221,10 @@ static uint16_t json_variant_depth(JsonVariant *v) {
         return v->depth;
 }
 
-static JsonVariant *json_variant_normalize(JsonVariant *v) {
+static JsonVariant *json_variant_formalize(JsonVariant *v) {
 
-        /* Converts json variants to their normalized form, i.e. fully dereferenced and wherever possible converted to
-         * the "magic" version if there is one */
+        /* Converts json variant pointers to their normalized form, i.e. fully dereferenced and wherever
+         * possible converted to the "magic" version if there is one */
 
         if (!v)
                 return NULL;
@@ -259,9 +265,9 @@ static JsonVariant *json_variant_normalize(JsonVariant *v) {
         }
 }
 
-static JsonVariant *json_variant_conservative_normalize(JsonVariant *v) {
+static JsonVariant *json_variant_conservative_formalize(JsonVariant *v) {
 
-        /* Much like json_variant_normalize(), but won't simplify if the variant has a source/line location attached to
+        /* Much like json_variant_formalize(), but won't simplify if the variant has a source/line location attached to
          * it, in order not to lose context */
 
         if (!v)
@@ -273,7 +279,7 @@ static JsonVariant *json_variant_conservative_normalize(JsonVariant *v) {
         if (v->source || v->line > 0 || v->column > 0)
                 return v;
 
-        return json_variant_normalize(v);
+        return json_variant_formalize(v);
 }
 
 static int json_variant_new(JsonVariant **ret, JsonVariantType type, size_t space) {
@@ -405,6 +411,20 @@ int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n) {
         return 0;
 }
 
+int json_variant_new_base64(JsonVariant **ret, const void *p, size_t n) {
+        _cleanup_free_ char *s = NULL;
+        ssize_t k;
+
+        assert_return(ret, -EINVAL);
+        assert_return(n == 0 || p, -EINVAL);
+
+        k = base64mem(p, n, &s);
+        if (k < 0)
+                return k;
+
+        return json_variant_new_stringn(ret, s, k);
+}
+
 static void json_variant_set(JsonVariant *a, JsonVariant *b) {
         assert(a);
 
@@ -451,7 +471,7 @@ static void json_variant_set(JsonVariant *a, JsonVariant *b) {
         case JSON_VARIANT_ARRAY:
         case JSON_VARIANT_OBJECT:
                 a->is_reference = true;
-                a->reference = json_variant_ref(json_variant_conservative_normalize(b));
+                a->reference = json_variant_ref(json_variant_conservative_formalize(b));
                 break;
 
         case JSON_VARIANT_NULL:
@@ -476,6 +496,7 @@ static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) {
 
 int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        bool normalized = true;
 
         assert_return(ret, -EINVAL);
         if (n == 0) {
@@ -511,8 +532,13 @@ int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
 
                 json_variant_set(w, c);
                 json_variant_copy_source(w, c);
+
+                if (!json_variant_is_normalized(c))
+                        normalized = false;
         }
 
+        v->normalized = normalized;
+
         *ret = TAKE_PTR(v);
         return 0;
 }
@@ -550,6 +576,8 @@ int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n) {
                 };
         }
 
+        v->normalized = true;
+
         *ret = v;
         return 0;
 }
@@ -601,12 +629,16 @@ int json_variant_new_array_strv(JsonVariant **ret, char **l) {
                         memcpy(w->string, l[v->n_elements], k+1);
         }
 
+        v->normalized = true;
+
         *ret = TAKE_PTR(v);
         return 0;
 }
 
 int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) {
         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        const char *prev = NULL;
+        bool sorted = true, normalized = true;
 
         assert_return(ret, -EINVAL);
         if (n == 0) {
@@ -630,9 +662,20 @@ int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) {
                         *c = array[v->n_elements];
                 uint16_t d;
 
-                if ((v->n_elements & 1) == 0 &&
-                    !json_variant_is_string(c))
-                        return -EINVAL; /* Every second one needs to be a string, as it is the key name */
+                if ((v->n_elements & 1) == 0) {
+                        const char *k;
+
+                        if (!json_variant_is_string(c))
+                                return -EINVAL; /* Every second one needs to be a string, as it is the key name */
+
+                        assert_se(k = json_variant_string(c));
+
+                        if (prev && strcmp(k, prev) <= 0)
+                                sorted = normalized = false;
+
+                        prev = k;
+                } else if (!json_variant_is_normalized(c))
+                        normalized = false;
 
                 d = json_variant_depth(c);
                 if (d >= DEPTH_MAX) /* Refuse too deep nesting */
@@ -649,6 +692,9 @@ int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) {
                 json_variant_copy_source(w, c);
         }
 
+        v->normalized = normalized;
+        v->sorted = sorted;
+
         *ret = TAKE_PTR(v);
         return 0;
 }
@@ -1131,7 +1177,7 @@ JsonVariant *json_variant_by_index(JsonVariant *v, size_t idx) {
         if (idx >= v->n_elements)
                 return NULL;
 
-        return json_variant_conservative_normalize(v + 1 + idx);
+        return json_variant_conservative_formalize(v + 1 + idx);
 
 mismatch:
         log_debug("Element in non-array/non-object JSON variant requested by index, returning NULL.");
@@ -1154,6 +1200,37 @@ JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVaria
         if (v->is_reference)
                 return json_variant_by_key(v->reference, key);
 
+        if (v->sorted) {
+                size_t a = 0, b = v->n_elements/2;
+
+                /* If the variant is sorted we can use bisection to find the entry we need in O(log(n)) time */
+
+                while (b > a) {
+                        JsonVariant *p;
+                        const char *f;
+                        int c;
+
+                        i = (a + b) / 2;
+                        p = json_variant_dereference(v + 1 + i*2);
+
+                        assert_se(f = json_variant_string(p));
+
+                        c = strcmp(key, f);
+                        if (c == 0) {
+                                if (ret_key)
+                                        *ret_key = json_variant_conservative_formalize(v + 1 + i*2);
+
+                                return json_variant_conservative_formalize(v + 1 + i*2 + 1);
+                        } else if (c < 0)
+                                b = i;
+                        else
+                                a = i + 1;
+                }
+
+                goto not_found;
+        }
+
+        /* The variant is not sorted, hence search for the field linearly */
         for (i = 0; i < v->n_elements; i += 2) {
                 JsonVariant *p;
 
@@ -1165,9 +1242,9 @@ JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVaria
                 if (streq(json_variant_string(p), key)) {
 
                         if (ret_key)
-                                *ret_key = json_variant_conservative_normalize(v + 1 + i);
+                                *ret_key = json_variant_conservative_formalize(v + 1 + i);
 
-                        return json_variant_conservative_normalize(v + 1 + i + 1);
+                        return json_variant_conservative_formalize(v + 1 + i + 1);
                 }
         }
 
@@ -1192,8 +1269,8 @@ JsonVariant *json_variant_by_key(JsonVariant *v, const char *key) {
 bool json_variant_equal(JsonVariant *a, JsonVariant *b) {
         JsonVariantType t;
 
-        a = json_variant_normalize(a);
-        b = json_variant_normalize(b);
+        a = json_variant_formalize(a);
+        b = json_variant_formalize(b);
 
         if (a == b)
                 return true;
@@ -1305,7 +1382,7 @@ void json_variant_sensitive(JsonVariant *v) {
          * flag to all contained variants. And if those are then destroyed this is propagated further down,
          * and so on. */
 
-        v = json_variant_normalize(v);
+        v = json_variant_formalize(v);
         if (!json_variant_is_regular(v))
                 return;
 
@@ -1690,6 +1767,9 @@ void json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const cha
                 fputc('\n', f);
         if (flags & JSON_FORMAT_SSE)
                 fputc('\n', f); /* In case of SSE add a second newline */
+
+        if (flags & JSON_FORMAT_FLUSH)
+                fflush(f);
 }
 
 int json_variant_filter(JsonVariant **v, char **to_remove) {
@@ -1814,6 +1894,194 @@ int json_variant_set_field(JsonVariant **v, const char *field, JsonVariant *valu
         return 1;
 }
 
+int json_variant_set_field_string(JsonVariant **v, const char *field, const char *value) {
+        _cleanup_(json_variant_unrefp) JsonVariant *m = NULL;
+        int r;
+
+        r = json_variant_new_string(&m, value);
+        if (r < 0)
+                return r;
+
+        return json_variant_set_field(v, field, m);
+}
+
+int json_variant_set_field_unsigned(JsonVariant **v, const char *field, uintmax_t u) {
+        _cleanup_(json_variant_unrefp) JsonVariant *m = NULL;
+        int r;
+
+        r = json_variant_new_unsigned(&m, u);
+        if (r < 0)
+                return r;
+
+        return json_variant_set_field(v, field, m);
+}
+
+int json_variant_merge(JsonVariant **v, JsonVariant *m) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+        _cleanup_free_ JsonVariant **array = NULL;
+        size_t v_elements, m_elements, i, k;
+        bool v_blank, m_blank;
+        int r;
+
+        m = json_variant_dereference(m);
+
+        v_blank = json_variant_is_blank_object(*v);
+        m_blank = json_variant_is_blank_object(m);
+
+        if (!v_blank && !json_variant_is_object(*v))
+                return -EINVAL;
+        if (!m_blank && !json_variant_is_object(m))
+                return -EINVAL;
+
+        if (m_blank)
+                return 0; /* nothing to do */
+
+        if (v_blank) {
+                json_variant_unref(*v);
+                *v = json_variant_ref(m);
+                return 1;
+        }
+
+        v_elements = json_variant_elements(*v);
+        m_elements = json_variant_elements(m);
+        if (v_elements > SIZE_MAX - m_elements) /* overflow check */
+                return -ENOMEM;
+
+        array = new(JsonVariant*, v_elements + m_elements);
+        if (!array)
+                return -ENOMEM;
+
+        k = 0;
+        for (i = 0; i < v_elements; i += 2) {
+                JsonVariant *u;
+
+                u = json_variant_by_index(*v, i);
+                if (!json_variant_is_string(u))
+                        return -EINVAL;
+
+                if (json_variant_by_key(m, json_variant_string(u)))
+                        continue; /* skip if exists in second variant */
+
+                array[k++] = u;
+                array[k++] = json_variant_by_index(*v, i + 1);
+        }
+
+        for (i = 0; i < m_elements; i++)
+                array[k++] = json_variant_by_index(m, i);
+
+        r = json_variant_new_object(&w, array, k);
+        if (r < 0)
+                return r;
+
+        json_variant_unref(*v);
+        *v = TAKE_PTR(w);
+
+        return 1;
+}
+
+int json_variant_append_array(JsonVariant **v, JsonVariant *element) {
+        _cleanup_(json_variant_unrefp) JsonVariant *nv = NULL;
+        bool blank;
+        int r;
+
+        assert(v);
+        assert(element);
+
+
+        if (!*v || json_variant_is_null(*v))
+                blank = true;
+        else if (!json_variant_is_array(*v))
+                return -EINVAL;
+        else
+                blank = json_variant_elements(*v) == 0;
+
+        if (blank)
+                r = json_variant_new_array(&nv, (JsonVariant*[]) { element }, 1);
+        else {
+                _cleanup_free_ JsonVariant **array = NULL;
+                size_t i;
+
+                array = new(JsonVariant*, json_variant_elements(*v) + 1);
+                if (!array)
+                        return -ENOMEM;
+
+                for (i = 0; i < json_variant_elements(*v); i++)
+                        array[i] = json_variant_by_index(*v, i);
+
+                array[i] = element;
+
+                r = json_variant_new_array(&nv, array, i + 1);
+        }
+
+        if (r < 0)
+                return r;
+
+        json_variant_unref(*v);
+        *v = TAKE_PTR(nv);
+
+        return 0;
+}
+
+int json_variant_strv(JsonVariant *v, char ***ret) {
+        char **l = NULL;
+        size_t n, i;
+        bool sensitive;
+        int r;
+
+        assert(ret);
+
+        if (!v || json_variant_is_null(v)) {
+                l = new0(char*, 1);
+                if (!l)
+                        return -ENOMEM;
+
+                *ret = l;
+                return 0;
+        }
+
+        if (!json_variant_is_array(v))
+                return -EINVAL;
+
+        sensitive = v->sensitive;
+
+        n = json_variant_elements(v);
+        l = new(char*, n+1);
+        if (!l)
+                return -ENOMEM;
+
+        for (i = 0; i < n; i++) {
+                JsonVariant *e;
+
+                assert_se(e = json_variant_by_index(v, i));
+                sensitive = sensitive || e->sensitive;
+
+                if (!json_variant_is_string(e)) {
+                        l[i] = NULL;
+                        r = -EINVAL;
+                        goto fail;
+                }
+
+                l[i] = strdup(json_variant_string(e));
+                if (!l[i]) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+        }
+
+        l[i] = NULL;
+        *ret = TAKE_PTR(l);
+
+        return 0;
+
+fail:
+        if (sensitive)
+                strv_free_erase(l);
+        else
+                strv_free(l);
+
+        return r;
+}
+
 static int json_variant_copy(JsonVariant **nv, JsonVariant *v) {
         JsonVariantType t;
         JsonVariant *c;
@@ -1877,7 +2145,7 @@ static int json_variant_copy(JsonVariant **nv, JsonVariant *v) {
                 c->n_ref = 1;
                 c->type = t;
                 c->is_reference = true;
-                c->reference = json_variant_ref(json_variant_normalize(v));
+                c->reference = json_variant_ref(json_variant_formalize(v));
 
                 *nv = c;
                 return 0;
@@ -3197,6 +3465,36 @@ int json_buildv(JsonVariant **ret, va_list ap) {
                         break;
                 }
 
+                case _JSON_BUILD_BASE64: {
+                        const void *p;
+                        size_t n;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        p = va_arg(ap, const void *);
+                        n = va_arg(ap, size_t);
+
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_base64(&add, p, n);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
                 case _JSON_BUILD_OBJECT_BEGIN:
 
                         if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
@@ -3531,6 +3829,11 @@ int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchF
         assert(variant);
         assert(b);
 
+        if (json_variant_is_null(variant)) {
+                *b = -1;
+                return 0;
+        }
+
         if (!json_variant_is_boolean(variant))
                 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a boolean.", strna(name));
 
@@ -3611,6 +3914,9 @@ int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFla
         if (!json_variant_is_string(variant))
                 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
 
+        if ((flags & JSON_SAFE) && !string_is_safe(json_variant_string(variant)))
+                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name));
+
         r = free_and_strdup(s, json_variant_string(variant));
         if (r < 0)
                 return json_log(variant, flags, r, "Failed to allocate string: %m");
@@ -3634,6 +3940,9 @@ int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags
 
         /* Let's be flexible here: accept a single string in place of a single-item array */
         if (json_variant_is_string(variant)) {
+                if ((flags & JSON_SAFE) && !string_is_safe(json_variant_string(variant)))
+                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name));
+
                 l = strv_new(json_variant_string(variant));
                 if (!l)
                         return log_oom();
@@ -3649,6 +3958,9 @@ int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags
                 if (!json_variant_is_string(e))
                         return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
 
+                if ((flags & JSON_SAFE) && !string_is_safe(json_variant_string(e)))
+                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name));
+
                 r = strv_extend(&l, json_variant_string(e));
                 if (r < 0)
                         return json_log(e, flags, r, "Failed to append array element: %m");
@@ -3670,6 +3982,152 @@ int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFl
         return 0;
 }
 
+static int json_cmp_strings(const void *x, const void *y) {
+        JsonVariant *const *a = x, *const *b = y;
+
+        if (!json_variant_is_string(*a) || !json_variant_is_string(*b))
+                return CMP(*a, *b);
+
+        return strcmp(json_variant_string(*a), json_variant_string(*b));
+}
+
+int json_variant_sort(JsonVariant **v) {
+        _cleanup_free_ JsonVariant **a = NULL;
+        JsonVariant *n = NULL;
+        size_t i, m;
+        int r;
+
+        assert(v);
+
+        if (json_variant_is_sorted(*v))
+                return 0;
+
+        if (!json_variant_is_object(*v))
+                return -EMEDIUMTYPE;
+
+        /* Sorts they key/value pairs in an object variant */
+
+        m = json_variant_elements(*v);
+        a = new(JsonVariant*, m);
+        if (!a)
+                return -ENOMEM;
+
+        for (i = 0; i < m; i++)
+                a[i] = json_variant_by_index(*v, i);
+
+        qsort(a, m/2, sizeof(JsonVariant*)*2, json_cmp_strings);
+
+        r = json_variant_new_object(&n, a, m);
+        if (r < 0)
+                return r;
+        if (!n->sorted) /* Check if this worked. This will fail if there are multiple identical keys used. */
+                return -ENOTUNIQ;
+
+        json_variant_unref(*v);
+        *v = n;
+
+        return 1;
+}
+
+int json_variant_normalize(JsonVariant **v) {
+        _cleanup_free_ JsonVariant **a = NULL;
+        JsonVariant *n = NULL;
+        size_t i, j, m;
+        int r;
+
+        assert(v);
+
+        if (json_variant_is_normalized(*v))
+                return 0;
+
+        if (!json_variant_is_object(*v) && !json_variant_is_array(*v))
+                return -EMEDIUMTYPE;
+
+        /* Sorts the key/value pairs in an object variant anywhere down the tree in the specified variant */
+
+        m = json_variant_elements(*v);
+        a = new(JsonVariant*, m);
+        if (!a)
+                return -ENOMEM;
+
+        for (i = 0; i < m; i++) {
+                a[i] = json_variant_ref(json_variant_by_index(*v, i));
+
+                r = json_variant_normalize(a + i);
+                if (r < 0)
+                        goto finish;
+        }
+
+        qsort(a, m/2, sizeof(JsonVariant*)*2, json_cmp_strings);
+
+        if (json_variant_is_object(*v))
+                r = json_variant_new_object(&n, a, m);
+        else {
+                assert(json_variant_is_array(*v));
+                r = json_variant_new_array(&n, a, m);
+        }
+        if (r < 0)
+                goto finish;
+        if (!n->normalized) { /* Let's see if normalization worked. It will fail if there are multiple
+                               * identical keys used in the same object anywhere, or if there are floating
+                               * point numbers used (see below) */
+                r = -ENOTUNIQ;
+                goto finish;
+        }
+
+        json_variant_unref(*v);
+        *v = n;
+
+        r = 1;
+
+finish:
+        for (j = 0; j < i; j++)
+                json_variant_unref(a[j]);
+
+        return r;
+}
+
+bool json_variant_is_normalized(JsonVariant *v) {
+
+        /* For now, let's consider anything containing numbers not expressible as integers as
+         * non-normalized. That's because we cannot sensibly compare them due to accuracy issues, nor even
+         * store them if they are too large. */
+        if (json_variant_is_real(v) && !json_variant_is_integer(v) && !json_variant_is_unsigned(v))
+                return false;
+
+        /* The concept only applies to variants that include other variants, i.e. objects and arrays. All
+         * others are normalized anyway. */
+        if (!json_variant_is_object(v) && !json_variant_is_array(v))
+                return true;
+
+        /* Empty objects/arrays don't include any other variant, hence are always normalized too */
+        if (json_variant_elements(v) == 0)
+                return true;
+
+        return v->normalized; /* For everything else there's an explicit boolean we maintain */
+}
+
+bool json_variant_is_sorted(JsonVariant *v) {
+
+        /* Returns true if all key/value pairs of an object are properly sorted. Note that this only applies
+         * to objects, not arrays. */
+
+        if (!json_variant_is_object(v))
+                return true;
+        if (json_variant_elements(v) <= 1)
+                return true;
+
+        return v->sorted;
+}
+
+int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size) {
+
+        if (!json_variant_is_string(v))
+                return -EINVAL;
+
+        return unbase64mem(json_variant_string(v), (size_t) -1, ret, ret_size);
+}
+
 static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = {
         [JSON_VARIANT_STRING] = "string",
         [JSON_VARIANT_INTEGER] = "integer",