/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include #include #include "sd-messages.h" #include "alloc-util.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" #include "float.h" #include "hexdecoct.h" #include "json-internal.h" #include "json.h" #include "macro.h" #include "memory-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" #include "user-util.h" #include "utf8.h" /* Refuse putting together variants with a larger depth than 2K by default (as a protection against overflowing stacks * if code processes JSON objects recursively. Note that we store the depth in an uint16_t, hence make sure this * remains under 2^16. * * The value first was 16k, but it was discovered to be too high on llvm/x86-64. See also: * https://github.com/systemd/systemd/issues/10738 * * The value then was 4k, but it was discovered to be too high on s390x/aarch64. See also: * https://github.com/systemd/systemd/issues/14396 */ #define DEPTH_MAX (2U*1024U) assert_cc(DEPTH_MAX <= UINT16_MAX); typedef struct JsonSource { /* When we parse from a file or similar, encodes the filename, to indicate the source of a json variant */ size_t n_ref; unsigned max_line; unsigned max_column; char name[]; } JsonSource; /* On x86-64 this whole structure should have a size of 6 * 64 bit = 48 bytes */ struct JsonVariant { union { /* We either maintain a reference counter for this variant itself, or we are embedded into an * array/object, in which case only that surrounding object is ref-counted. (If 'embedded' is false, * see below.) */ size_t n_ref; /* If this JsonVariant is part of an array/object, then this field points to the surrounding * JSON_VARIANT_ARRAY/JSON_VARIANT_OBJECT object. (If 'embedded' is true, see below.) */ JsonVariant *parent; }; /* If this was parsed from some file or buffer, this stores where from, as well as the source line/column */ JsonSource *source; unsigned line, column; JsonVariantType type:5; /* A marker whether this variant is embedded into in array/object or not. If true, the 'parent' pointer above * is valid. If false, the 'n_ref' field above is valid instead. */ bool is_embedded:1; /* In some conditions (for example, if this object is part of an array of strings or objects), we don't store * any data inline, but instead simply reference an external object and act as surrogate of it. In that case * this bool is set, and the external object is referenced through the .reference field below. */ bool is_reference:1; /* While comparing two arrays, we use this for marking what we already have seen */ bool is_marked:1; /* 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; union { /* For simple types we store the value in-line. */ JsonValue value; /* For objects and arrays we store the number of elements immediately following */ size_t n_elements; /* If is_reference as indicated above is set, this is where the reference object is actually stored. */ JsonVariant *reference; /* Strings are placed immediately after the structure. Note that when this is a JsonVariant embedded * into an array we might encode strings up to INLINE_STRING_LENGTH characters directly inside the * element, while longer strings are stored as references. When this object is not embedded into an * array, but stand-alone we allocate the right size for the whole structure, i.e. the array might be * much larger than INLINE_STRING_LENGTH. * * Note that because we want to allocate arrays of the JsonVariant structure we specify [0] here, * rather than the prettier []. If we wouldn't, then this char array would have undefined size, and so * would the union and then the struct this is included in. And of structures with undefined size we * can't allocate arrays (at least not easily). */ char string[0]; }; }; /* Inside string arrays we have a series of JasonVariant structures one after the other. In this case, strings longer * than INLINE_STRING_MAX are stored as references, and all shorter ones inline. (This means — on x86-64 — strings up * to 15 chars are stored within the array elements, and all others in separate allocations) */ #define INLINE_STRING_MAX (sizeof(JsonVariant) - offsetof(JsonVariant, string) - 1U) /* Let's make sure this structure isn't increased in size accidentally. This check is only for our most relevant arch * (x86-64). */ #ifdef __x86_64__ assert_cc(sizeof(JsonVariant) == 48U); assert_cc(INLINE_STRING_MAX == 15U); #endif static JsonSource* json_source_new(const char *name) { JsonSource *s; assert(name); s = malloc(offsetof(JsonSource, name) + strlen(name) + 1); if (!s) return NULL; *s = (JsonSource) { .n_ref = 1, }; strcpy(s->name, name); return s; } DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(JsonSource, json_source, mfree); static bool json_source_equal(JsonSource *a, JsonSource *b) { if (a == b) return true; if (!a || !b) return false; return streq(a->name, b->name); } DEFINE_TRIVIAL_CLEANUP_FUNC(JsonSource*, json_source_unref); /* There are four kind of JsonVariant* pointers: * * 1. NULL * 2. A 'regular' one, i.e. pointing to malloc() memory * 3. A 'magic' one, i.e. one of the special JSON_VARIANT_MAGIC_XYZ values, that encode a few very basic values directly in the pointer. * 4. A 'const string' one, i.e. a pointer to a const string. * * The four kinds of pointers can be discerned like this: * * Detecting #1 is easy, just compare with NULL. Detecting #3 is similarly easy: all magic pointers are below * _JSON_VARIANT_MAGIC_MAX (which is pretty low, within the first memory page, which is special on Linux and other * OSes, as it is a faulting page). In order to discern #2 and #4 we check the lowest bit. If it's off it's #2, * otherwise #4. This makes use of the fact that malloc() will return "maximum aligned" memory, which definitely * means the pointer is even. This means we can use the uneven pointers to reference static strings, as long as we * make sure that all static strings used like this are aligned to 2 (or higher), and that we mask the bit on * access. The JSON_VARIANT_STRING_CONST() macro encodes strings as JsonVariant* pointers, with the bit set. */ static bool json_variant_is_magic(const JsonVariant *v) { if (!v) return false; return v < _JSON_VARIANT_MAGIC_MAX; } static bool json_variant_is_const_string(const JsonVariant *v) { if (v < _JSON_VARIANT_MAGIC_MAX) return false; /* A proper JsonVariant is aligned to whatever malloc() aligns things too, which is definitely not uneven. We * hence use all uneven pointers as indicators for const strings. */ return (((uintptr_t) v) & 1) != 0; } static bool json_variant_is_regular(const JsonVariant *v) { if (v < _JSON_VARIANT_MAGIC_MAX) return false; return (((uintptr_t) v) & 1) == 0; } static JsonVariant *json_variant_dereference(JsonVariant *v) { /* Recursively dereference variants that are references to other variants */ if (!v) return NULL; if (!json_variant_is_regular(v)) return v; if (!v->is_reference) return v; return json_variant_dereference(v->reference); } static uint16_t json_variant_depth(JsonVariant *v) { v = json_variant_dereference(v); if (!v) return 0; if (!json_variant_is_regular(v)) return 0; return v->depth; } static JsonVariant *json_variant_formalize(JsonVariant *v) { /* 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; v = json_variant_dereference(v); switch (json_variant_type(v)) { case JSON_VARIANT_BOOLEAN: return json_variant_boolean(v) ? JSON_VARIANT_MAGIC_TRUE : JSON_VARIANT_MAGIC_FALSE; case JSON_VARIANT_NULL: return JSON_VARIANT_MAGIC_NULL; case JSON_VARIANT_INTEGER: return json_variant_integer(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_INTEGER : v; case JSON_VARIANT_UNSIGNED: return json_variant_unsigned(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_UNSIGNED : v; case JSON_VARIANT_REAL: DISABLE_WARNING_FLOAT_EQUAL; return json_variant_real(v) == 0.0 ? JSON_VARIANT_MAGIC_ZERO_REAL : v; REENABLE_WARNING; case JSON_VARIANT_STRING: return isempty(json_variant_string(v)) ? JSON_VARIANT_MAGIC_EMPTY_STRING : v; case JSON_VARIANT_ARRAY: return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_ARRAY : v; case JSON_VARIANT_OBJECT: return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_OBJECT : v; default: return v; } } static JsonVariant *json_variant_conservative_formalize(JsonVariant *v) { /* 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) return NULL; if (!json_variant_is_regular(v)) return v; if (v->source || v->line > 0 || v->column > 0) return v; return json_variant_formalize(v); } static int json_variant_new(JsonVariant **ret, JsonVariantType type, size_t space) { JsonVariant *v; assert_return(ret, -EINVAL); v = malloc0(MAX(sizeof(JsonVariant), offsetof(JsonVariant, value) + space)); if (!v) return -ENOMEM; v->n_ref = 1; v->type = type; *ret = v; return 0; } int json_variant_new_integer(JsonVariant **ret, intmax_t i) { JsonVariant *v; int r; assert_return(ret, -EINVAL); if (i == 0) { *ret = JSON_VARIANT_MAGIC_ZERO_INTEGER; return 0; } r = json_variant_new(&v, JSON_VARIANT_INTEGER, sizeof(i)); if (r < 0) return r; v->value.integer = i; *ret = v; return 0; } int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u) { JsonVariant *v; int r; assert_return(ret, -EINVAL); if (u == 0) { *ret = JSON_VARIANT_MAGIC_ZERO_UNSIGNED; return 0; } r = json_variant_new(&v, JSON_VARIANT_UNSIGNED, sizeof(u)); if (r < 0) return r; v->value.unsig = u; *ret = v; return 0; } int json_variant_new_real(JsonVariant **ret, long double d) { JsonVariant *v; int r; assert_return(ret, -EINVAL); DISABLE_WARNING_FLOAT_EQUAL; if (d == 0.0) { *ret = JSON_VARIANT_MAGIC_ZERO_REAL; return 0; } REENABLE_WARNING; r = json_variant_new(&v, JSON_VARIANT_REAL, sizeof(d)); if (r < 0) return r; v->value.real = d; *ret = v; return 0; } int json_variant_new_boolean(JsonVariant **ret, bool b) { assert_return(ret, -EINVAL); if (b) *ret = JSON_VARIANT_MAGIC_TRUE; else *ret = JSON_VARIANT_MAGIC_FALSE; return 0; } int json_variant_new_null(JsonVariant **ret) { assert_return(ret, -EINVAL); *ret = JSON_VARIANT_MAGIC_NULL; return 0; } int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n) { JsonVariant *v; int r; assert_return(ret, -EINVAL); if (!s) { assert_return(IN_SET(n, 0, (size_t) -1), -EINVAL); return json_variant_new_null(ret); } if (n == (size_t) -1) /* determine length automatically */ n = strlen(s); else if (memchr(s, 0, n)) /* don't allow embedded NUL, as we can't express that in JSON */ return -EINVAL; if (n == 0) { *ret = JSON_VARIANT_MAGIC_EMPTY_STRING; return 0; } if (!utf8_is_valid_n(s, n)) /* JSON strings must be valid UTF-8 */ return -EUCLEAN; r = json_variant_new(&v, JSON_VARIANT_STRING, n + 1); if (r < 0) return r; memcpy(v->string, s, n); v->string[n] = 0; *ret = v; 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); } int json_variant_new_hex(JsonVariant **ret, const void *p, size_t n) { _cleanup_free_ char *s = NULL; assert_return(ret, -EINVAL); assert_return(n == 0 || p, -EINVAL); s = hexmem(p, n); if (!s) return -ENOMEM; return json_variant_new_stringn(ret, s, n*2); } int json_variant_new_id128(JsonVariant **ret, sd_id128_t id) { char s[SD_ID128_STRING_MAX]; return json_variant_new_string(ret, sd_id128_to_string(id, s)); } static void json_variant_set(JsonVariant *a, JsonVariant *b) { assert(a); b = json_variant_dereference(b); if (!b) { a->type = JSON_VARIANT_NULL; return; } a->type = json_variant_type(b); switch (a->type) { case JSON_VARIANT_INTEGER: a->value.integer = json_variant_integer(b); break; case JSON_VARIANT_UNSIGNED: a->value.unsig = json_variant_unsigned(b); break; case JSON_VARIANT_REAL: a->value.real = json_variant_real(b); break; case JSON_VARIANT_BOOLEAN: a->value.boolean = json_variant_boolean(b); break; case JSON_VARIANT_STRING: { const char *s; assert_se(s = json_variant_string(b)); /* Short strings we can store inline */ if (strnlen(s, INLINE_STRING_MAX+1) <= INLINE_STRING_MAX) { strcpy(a->string, s); break; } /* For longer strings, use a reference… */ _fallthrough_; } case JSON_VARIANT_ARRAY: case JSON_VARIANT_OBJECT: a->is_reference = true; a->reference = json_variant_ref(json_variant_conservative_formalize(b)); break; case JSON_VARIANT_NULL: break; default: assert_not_reached("Unexpected variant type"); } } static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) { assert(v); assert(from); if (!json_variant_is_regular(from)) return; v->line = from->line; v->column = from->column; v->source = json_source_ref(from->source); } 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) { *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY; return 0; } assert_return(array, -EINVAL); v = new(JsonVariant, n + 1); if (!v) return -ENOMEM; *v = (JsonVariant) { .n_ref = 1, .type = JSON_VARIANT_ARRAY, }; for (v->n_elements = 0; v->n_elements < n; v->n_elements++) { JsonVariant *w = v + 1 + v->n_elements, *c = array[v->n_elements]; uint16_t d; d = json_variant_depth(c); if (d >= DEPTH_MAX) /* Refuse too deep nesting */ return -ELNRNG; if (d >= v->depth) v->depth = d + 1; *w = (JsonVariant) { .is_embedded = true, .parent = v, }; 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; } int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n) { JsonVariant *v; size_t i; assert_return(ret, -EINVAL); if (n == 0) { *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY; return 0; } assert_return(p, -EINVAL); v = new(JsonVariant, n + 1); if (!v) return -ENOMEM; *v = (JsonVariant) { .n_ref = 1, .type = JSON_VARIANT_ARRAY, .n_elements = n, .depth = 1, }; for (i = 0; i < n; i++) { JsonVariant *w = v + 1 + i; *w = (JsonVariant) { .is_embedded = true, .parent = v, .type = JSON_VARIANT_UNSIGNED, .value.unsig = ((const uint8_t*) p)[i], }; } v->normalized = true; *ret = v; return 0; } int json_variant_new_array_strv(JsonVariant **ret, char **l) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; size_t n; int r; assert(ret); n = strv_length(l); if (n == 0) { *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY; return 0; } v = new(JsonVariant, n + 1); if (!v) return -ENOMEM; *v = (JsonVariant) { .n_ref = 1, .type = JSON_VARIANT_ARRAY, .depth = 1, }; for (v->n_elements = 0; v->n_elements < n; v->n_elements++) { JsonVariant *w = v + 1 + v->n_elements; size_t k; *w = (JsonVariant) { .is_embedded = true, .parent = v, .type = JSON_VARIANT_STRING, }; k = strlen(l[v->n_elements]); if (k > INLINE_STRING_MAX) { /* If string is too long, store it as reference. */ r = json_variant_new_string(&w->reference, l[v->n_elements]); if (r < 0) return r; w->is_reference = true; } else { if (!utf8_is_valid_n(l[v->n_elements], k)) /* JSON strings must be valid UTF-8 */ return -EUCLEAN; 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) { *ret = JSON_VARIANT_MAGIC_EMPTY_OBJECT; return 0; } assert_return(array, -EINVAL); assert_return(n % 2 == 0, -EINVAL); v = new(JsonVariant, n + 1); if (!v) return -ENOMEM; *v = (JsonVariant) { .n_ref = 1, .type = JSON_VARIANT_OBJECT, }; for (v->n_elements = 0; v->n_elements < n; v->n_elements++) { JsonVariant *w = v + 1 + v->n_elements, *c = array[v->n_elements]; uint16_t d; 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 */ return -ELNRNG; if (d >= v->depth) v->depth = d + 1; *w = (JsonVariant) { .is_embedded = true, .parent = v, }; json_variant_set(w, c); json_variant_copy_source(w, c); } v->normalized = normalized; v->sorted = sorted; *ret = TAKE_PTR(v); return 0; } static size_t json_variant_size(JsonVariant* v) { if (!json_variant_is_regular(v)) return 0; if (v->is_reference) return offsetof(JsonVariant, reference) + sizeof(JsonVariant*); switch (v->type) { case JSON_VARIANT_STRING: return offsetof(JsonVariant, string) + strlen(v->string) + 1; case JSON_VARIANT_REAL: return offsetof(JsonVariant, value) + sizeof(long double); case JSON_VARIANT_UNSIGNED: return offsetof(JsonVariant, value) + sizeof(uintmax_t); case JSON_VARIANT_INTEGER: return offsetof(JsonVariant, value) + sizeof(intmax_t); case JSON_VARIANT_BOOLEAN: return offsetof(JsonVariant, value) + sizeof(bool); case JSON_VARIANT_ARRAY: case JSON_VARIANT_OBJECT: return offsetof(JsonVariant, n_elements) + sizeof(size_t); case JSON_VARIANT_NULL: return offsetof(JsonVariant, value); default: assert_not_reached("unexpected type"); } } static void json_variant_free_inner(JsonVariant *v, bool force_sensitive) { bool sensitive; assert(v); if (!json_variant_is_regular(v)) return; json_source_unref(v->source); sensitive = v->sensitive || force_sensitive; if (v->is_reference) { if (sensitive) json_variant_sensitive(v->reference); json_variant_unref(v->reference); return; } if (IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) { size_t i; for (i = 0; i < v->n_elements; i++) json_variant_free_inner(v + 1 + i, sensitive); } if (sensitive) explicit_bzero_safe(v, json_variant_size(v)); } JsonVariant *json_variant_ref(JsonVariant *v) { if (!v) return NULL; if (!json_variant_is_regular(v)) return v; if (v->is_embedded) json_variant_ref(v->parent); /* ref the compounding variant instead */ else { assert(v->n_ref > 0); v->n_ref++; } return v; } JsonVariant *json_variant_unref(JsonVariant *v) { if (!v) return NULL; if (!json_variant_is_regular(v)) return NULL; if (v->is_embedded) json_variant_unref(v->parent); else { assert(v->n_ref > 0); v->n_ref--; if (v->n_ref == 0) { json_variant_free_inner(v, false); free(v); } } return NULL; } void json_variant_unref_many(JsonVariant **array, size_t n) { size_t i; assert(array || n == 0); for (i = 0; i < n; i++) json_variant_unref(array[i]); } const char *json_variant_string(JsonVariant *v) { if (!v) return NULL; if (v == JSON_VARIANT_MAGIC_EMPTY_STRING) return ""; if (json_variant_is_magic(v)) goto mismatch; if (json_variant_is_const_string(v)) { uintptr_t p = (uintptr_t) v; assert((p & 1) != 0); return (const char*) (p ^ 1U); } if (v->is_reference) return json_variant_string(v->reference); if (v->type != JSON_VARIANT_STRING) goto mismatch; return v->string; mismatch: log_debug("Non-string JSON variant requested as string, returning NULL."); return NULL; } bool json_variant_boolean(JsonVariant *v) { if (!v) goto mismatch; if (v == JSON_VARIANT_MAGIC_TRUE) return true; if (v == JSON_VARIANT_MAGIC_FALSE) return false; if (!json_variant_is_regular(v)) goto mismatch; if (v->type != JSON_VARIANT_BOOLEAN) goto mismatch; if (v->is_reference) return json_variant_boolean(v->reference); return v->value.boolean; mismatch: log_debug("Non-boolean JSON variant requested as boolean, returning false."); return false; } intmax_t json_variant_integer(JsonVariant *v) { if (!v) goto mismatch; if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) return 0; if (!json_variant_is_regular(v)) goto mismatch; if (v->is_reference) return json_variant_integer(v->reference); switch (v->type) { case JSON_VARIANT_INTEGER: return v->value.integer; case JSON_VARIANT_UNSIGNED: if (v->value.unsig <= INTMAX_MAX) return (intmax_t) v->value.unsig; log_debug("Unsigned integer %ju requested as signed integer and out of range, returning 0.", v->value.unsig); return 0; case JSON_VARIANT_REAL: { intmax_t converted; converted = (intmax_t) v->value.real; DISABLE_WARNING_FLOAT_EQUAL; if ((long double) converted == v->value.real) return converted; REENABLE_WARNING; log_debug("Real %Lg requested as integer, and cannot be converted losslessly, returning 0.", v->value.real); return 0; } default: break; } mismatch: log_debug("Non-integer JSON variant requested as integer, returning 0."); return 0; } uintmax_t json_variant_unsigned(JsonVariant *v) { if (!v) goto mismatch; if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) return 0; if (!json_variant_is_regular(v)) goto mismatch; if (v->is_reference) return json_variant_integer(v->reference); switch (v->type) { case JSON_VARIANT_INTEGER: if (v->value.integer >= 0) return (uintmax_t) v->value.integer; log_debug("Signed integer %ju requested as unsigned integer and out of range, returning 0.", v->value.integer); return 0; case JSON_VARIANT_UNSIGNED: return v->value.unsig; case JSON_VARIANT_REAL: { uintmax_t converted; converted = (uintmax_t) v->value.real; DISABLE_WARNING_FLOAT_EQUAL; if ((long double) converted == v->value.real) return converted; REENABLE_WARNING; log_debug("Real %Lg requested as unsigned integer, and cannot be converted losslessly, returning 0.", v->value.real); return 0; } default: break; } mismatch: log_debug("Non-integer JSON variant requested as unsigned, returning 0."); return 0; } long double json_variant_real(JsonVariant *v) { if (!v) return 0.0; if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) return 0.0; if (!json_variant_is_regular(v)) goto mismatch; if (v->is_reference) return json_variant_real(v->reference); switch (v->type) { case JSON_VARIANT_REAL: return v->value.real; case JSON_VARIANT_INTEGER: { long double converted; converted = (long double) v->value.integer; if ((intmax_t) converted == v->value.integer) return converted; log_debug("Signed integer %ji requested as real, and cannot be converted losslessly, returning 0.", v->value.integer); return 0.0; } case JSON_VARIANT_UNSIGNED: { long double converted; converted = (long double) v->value.unsig; if ((uintmax_t) converted == v->value.unsig) return converted; log_debug("Unsigned integer %ju requested as real, and cannot be converted losslessly, returning 0.", v->value.unsig); return 0.0; } default: break; } mismatch: log_debug("Non-integer JSON variant requested as integer, returning 0."); return 0.0; } bool json_variant_is_negative(JsonVariant *v) { if (!v) goto mismatch; if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) return false; if (!json_variant_is_regular(v)) goto mismatch; if (v->is_reference) return json_variant_is_negative(v->reference); /* This function is useful as checking whether numbers are negative is pretty complex since we have three types * of numbers. And some JSON code (OCI for example) uses negative numbers to mark "not defined" numeric * values. */ switch (v->type) { case JSON_VARIANT_REAL: return v->value.real < 0; case JSON_VARIANT_INTEGER: return v->value.integer < 0; case JSON_VARIANT_UNSIGNED: return false; default: break; } mismatch: log_debug("Non-integer JSON variant tested for negativity, returning false."); return false; } bool json_variant_is_blank_object(JsonVariant *v) { /* Returns true if the specified object is null or empty */ return !v || json_variant_is_null(v) || (json_variant_is_object(v) && json_variant_elements(v) == 0); } bool json_variant_is_blank_array(JsonVariant *v) { return !v || json_variant_is_null(v) || (json_variant_is_array(v) && json_variant_elements(v) == 0); } JsonVariantType json_variant_type(JsonVariant *v) { if (!v) return _JSON_VARIANT_TYPE_INVALID; if (json_variant_is_const_string(v)) return JSON_VARIANT_STRING; if (v == JSON_VARIANT_MAGIC_TRUE || v == JSON_VARIANT_MAGIC_FALSE) return JSON_VARIANT_BOOLEAN; if (v == JSON_VARIANT_MAGIC_NULL) return JSON_VARIANT_NULL; if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER) return JSON_VARIANT_INTEGER; if (v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED) return JSON_VARIANT_UNSIGNED; if (v == JSON_VARIANT_MAGIC_ZERO_REAL) return JSON_VARIANT_REAL; if (v == JSON_VARIANT_MAGIC_EMPTY_STRING) return JSON_VARIANT_STRING; if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY) return JSON_VARIANT_ARRAY; if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT) return JSON_VARIANT_OBJECT; return v->type; } _function_no_sanitize_float_cast_overflow_ bool json_variant_has_type(JsonVariant *v, JsonVariantType type) { JsonVariantType rt; /* Note: we turn off ubsan float cast overflo detection for this function, since it would complain * about our float casts but we do them explicitly to detect conversion errors. */ v = json_variant_dereference(v); if (!v) return false; rt = json_variant_type(v); if (rt == type) return true; /* If it's a const string, then it only can be a string, and if it is not, it's not */ if (json_variant_is_const_string(v)) return false; /* All three magic zeroes qualify as integer, unsigned and as real */ if ((v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) && IN_SET(type, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL, JSON_VARIANT_NUMBER)) return true; /* All other magic variant types are only equal to themselves */ if (json_variant_is_magic(v)) return false; /* Handle the "number" pseudo type */ if (type == JSON_VARIANT_NUMBER) return IN_SET(rt, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL); /* Integer conversions are OK in many cases */ if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_UNSIGNED) return v->value.integer >= 0; if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_INTEGER) return v->value.unsig <= INTMAX_MAX; /* Any integer that can be converted lossley to a real and back may also be considered a real */ if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_REAL) return (intmax_t) (long double) v->value.integer == v->value.integer; if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_REAL) return (uintmax_t) (long double) v->value.unsig == v->value.unsig; DISABLE_WARNING_FLOAT_EQUAL; /* Any real that can be converted losslessly to an integer and back may also be considered an integer */ if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_INTEGER) return (long double) (intmax_t) v->value.real == v->value.real; if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_UNSIGNED) return (long double) (uintmax_t) v->value.real == v->value.real; REENABLE_WARNING; return false; } size_t json_variant_elements(JsonVariant *v) { if (!v) return 0; if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY || v == JSON_VARIANT_MAGIC_EMPTY_OBJECT) return 0; if (!json_variant_is_regular(v)) goto mismatch; if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) goto mismatch; if (v->is_reference) return json_variant_elements(v->reference); return v->n_elements; mismatch: log_debug("Number of elements in non-array/non-object JSON variant requested, returning 0."); return 0; } JsonVariant *json_variant_by_index(JsonVariant *v, size_t idx) { if (!v) return NULL; if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY || v == JSON_VARIANT_MAGIC_EMPTY_OBJECT) return NULL; if (!json_variant_is_regular(v)) goto mismatch; if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) goto mismatch; if (v->is_reference) return json_variant_by_index(v->reference, idx); if (idx >= v->n_elements) return NULL; return json_variant_conservative_formalize(v + 1 + idx); mismatch: log_debug("Element in non-array/non-object JSON variant requested by index, returning NULL."); return NULL; } JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVariant **ret_key) { size_t i; if (!v) goto not_found; if (!key) goto not_found; if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT) goto not_found; if (!json_variant_is_regular(v)) goto mismatch; if (v->type != JSON_VARIANT_OBJECT) goto mismatch; 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; p = json_variant_dereference(v + 1 + i); if (!json_variant_has_type(p, JSON_VARIANT_STRING)) continue; if (streq(json_variant_string(p), key)) { if (ret_key) *ret_key = json_variant_conservative_formalize(v + 1 + i); return json_variant_conservative_formalize(v + 1 + i + 1); } } not_found: if (ret_key) *ret_key = NULL; return NULL; mismatch: log_debug("Element in non-object JSON variant requested by key, returning NULL."); if (ret_key) *ret_key = NULL; return NULL; } JsonVariant *json_variant_by_key(JsonVariant *v, const char *key) { return json_variant_by_key_full(v, key, NULL); } bool json_variant_equal(JsonVariant *a, JsonVariant *b) { JsonVariantType t; a = json_variant_formalize(a); b = json_variant_formalize(b); if (a == b) return true; t = json_variant_type(a); if (!json_variant_has_type(b, t)) return false; switch (t) { case JSON_VARIANT_STRING: return streq(json_variant_string(a), json_variant_string(b)); case JSON_VARIANT_INTEGER: return json_variant_integer(a) == json_variant_integer(b); case JSON_VARIANT_UNSIGNED: return json_variant_unsigned(a) == json_variant_unsigned(b); case JSON_VARIANT_REAL: DISABLE_WARNING_FLOAT_EQUAL; return json_variant_real(a) == json_variant_real(b); REENABLE_WARNING; case JSON_VARIANT_BOOLEAN: return json_variant_boolean(a) == json_variant_boolean(b); case JSON_VARIANT_NULL: return true; case JSON_VARIANT_ARRAY: { size_t i, n; n = json_variant_elements(a); if (n != json_variant_elements(b)) return false; for (i = 0; i < n; i++) { if (!json_variant_equal(json_variant_by_index(a, i), json_variant_by_index(b, i))) return false; } return true; } case JSON_VARIANT_OBJECT: { size_t i, n; n = json_variant_elements(a); if (n != json_variant_elements(b)) return false; /* Iterate through all keys in 'a' */ for (i = 0; i < n; i += 2) { bool found = false; size_t j; /* Match them against all keys in 'b' */ for (j = 0; j < n; j += 2) { JsonVariant *key_b; key_b = json_variant_by_index(b, j); /* During the first iteration unmark everything */ if (i == 0) key_b->is_marked = false; else if (key_b->is_marked) /* In later iterations if we already marked something, don't bother with it again */ continue; if (found) continue; if (json_variant_equal(json_variant_by_index(a, i), key_b) && json_variant_equal(json_variant_by_index(a, i+1), json_variant_by_index(b, j+1))) { /* Key and values match! */ key_b->is_marked = found = true; /* In the first iteration we continue the inner loop since we want to mark * everything, otherwise exit the loop quickly after we found what we were * looking for. */ if (i != 0) break; } } if (!found) return false; } return true; } default: assert_not_reached("Unknown variant type."); } } void json_variant_sensitive(JsonVariant *v) { assert(v); /* Marks a variant as "sensitive", so that it is erased from memory when it is destroyed. This is a * one-way operation: as soon as it is marked this way it remains marked this way until it's * destroyed. A magic variant is never sensitive though, even when asked, since it's too * basic. Similar, const string variant are never sensitive either, after all they are included in * the source code as they are, which is not suitable for inclusion of secrets. * * Note that this flag has a recursive effect: when we destroy an object or array we'll propagate the * flag to all contained variants. And if those are then destroyed this is propagated further down, * and so on. */ v = json_variant_formalize(v); if (!json_variant_is_regular(v)) return; v->sensitive = true; } bool json_variant_is_sensitive(JsonVariant *v) { v = json_variant_formalize(v); if (!json_variant_is_regular(v)) return false; return v->sensitive; } static void json_variant_propagate_sensitive(JsonVariant *from, JsonVariant *to) { if (json_variant_is_sensitive(from)) json_variant_sensitive(to); } int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column) { assert_return(v, -EINVAL); if (ret_source) *ret_source = json_variant_is_regular(v) && v->source ? v->source->name : NULL; if (ret_line) *ret_line = json_variant_is_regular(v) ? v->line : 0; if (ret_column) *ret_column = json_variant_is_regular(v) ? v->column : 0; return 0; } static int print_source(FILE *f, JsonVariant *v, JsonFormatFlags flags, bool whitespace) { size_t w, k; if (!FLAGS_SET(flags, JSON_FORMAT_SOURCE|JSON_FORMAT_PRETTY)) return 0; if (!json_variant_is_regular(v)) return 0; if (!v->source && v->line == 0 && v->column == 0) return 0; /* The max width we need to format the line numbers for this source file */ w = (v->source && v->source->max_line > 0) ? DECIMAL_STR_WIDTH(v->source->max_line) : DECIMAL_STR_MAX(unsigned)-1; k = (v->source && v->source->max_column > 0) ? DECIMAL_STR_WIDTH(v->source->max_column) : DECIMAL_STR_MAX(unsigned) -1; if (whitespace) { size_t i, n; n = 1 + (v->source ? strlen(v->source->name) : 0) + ((v->source && (v->line > 0 || v->column > 0)) ? 1 : 0) + (v->line > 0 ? w : 0) + (((v->source || v->line > 0) && v->column > 0) ? 1 : 0) + (v->column > 0 ? k : 0) + 2; for (i = 0; i < n; i++) fputc(' ', f); } else { fputc('[', f); if (v->source) fputs(v->source->name, f); if (v->source && (v->line > 0 || v->column > 0)) fputc(':', f); if (v->line > 0) fprintf(f, "%*u", (int) w, v->line); if ((v->source || v->line > 0) || v->column > 0) fputc(':', f); if (v->column > 0) fprintf(f, "%*u", (int) k, v->column); fputc(']', f); fputc(' ', f); } return 0; } static void json_format_string(FILE *f, const char *q, JsonFormatFlags flags) { assert(q); fputc('"', f); if (flags & JSON_FORMAT_COLOR) fputs(ansi_green(), f); for (; *q; q++) switch (*q) { case '"': fputs("\\\"", f); break; case '\\': fputs("\\\\", f); break; case '\b': fputs("\\b", f); break; case '\f': fputs("\\f", f); break; case '\n': fputs("\\n", f); break; case '\r': fputs("\\r", f); break; case '\t': fputs("\\t", f); break; default: if ((signed char) *q >= 0 && *q < ' ') fprintf(f, "\\u%04x", *q); else fputc(*q, f); break; } if (flags & JSON_FORMAT_COLOR) fputs(ANSI_NORMAL, f); fputc('"', f); } static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const char *prefix) { int r; assert(f); assert(v); switch (json_variant_type(v)) { case JSON_VARIANT_REAL: { locale_t loc; loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); if (loc == (locale_t) 0) return -errno; if (flags & JSON_FORMAT_COLOR) fputs(ansi_highlight_blue(), f); fprintf(f, "%.*Le", DECIMAL_DIG, json_variant_real(v)); if (flags & JSON_FORMAT_COLOR) fputs(ANSI_NORMAL, f); freelocale(loc); break; } case JSON_VARIANT_INTEGER: if (flags & JSON_FORMAT_COLOR) fputs(ansi_highlight_blue(), f); fprintf(f, "%" PRIdMAX, json_variant_integer(v)); if (flags & JSON_FORMAT_COLOR) fputs(ANSI_NORMAL, f); break; case JSON_VARIANT_UNSIGNED: if (flags & JSON_FORMAT_COLOR) fputs(ansi_highlight_blue(), f); fprintf(f, "%" PRIuMAX, json_variant_unsigned(v)); if (flags & JSON_FORMAT_COLOR) fputs(ANSI_NORMAL, f); break; case JSON_VARIANT_BOOLEAN: if (flags & JSON_FORMAT_COLOR) fputs(ANSI_HIGHLIGHT, f); if (json_variant_boolean(v)) fputs("true", f); else fputs("false", f); if (flags & JSON_FORMAT_COLOR) fputs(ANSI_NORMAL, f); break; case JSON_VARIANT_NULL: if (flags & JSON_FORMAT_COLOR) fputs(ANSI_HIGHLIGHT, f); fputs("null", f); if (flags & JSON_FORMAT_COLOR) fputs(ANSI_NORMAL, f); break; case JSON_VARIANT_STRING: json_format_string(f, json_variant_string(v), flags); break; case JSON_VARIANT_ARRAY: { size_t i, n; n = json_variant_elements(v); if (n == 0) fputs("[]", f); else { _cleanup_free_ char *joined = NULL; const char *prefix2; if (flags & JSON_FORMAT_PRETTY) { joined = strjoin(strempty(prefix), "\t"); if (!joined) return -ENOMEM; prefix2 = joined; fputs("[\n", f); } else { prefix2 = strempty(prefix); fputc('[', f); } for (i = 0; i < n; i++) { JsonVariant *e; assert_se(e = json_variant_by_index(v, i)); if (i > 0) { if (flags & JSON_FORMAT_PRETTY) fputs(",\n", f); else fputc(',', f); } if (flags & JSON_FORMAT_PRETTY) { print_source(f, e, flags, false); fputs(prefix2, f); } r = json_format(f, e, flags, prefix2); if (r < 0) return r; } if (flags & JSON_FORMAT_PRETTY) { fputc('\n', f); print_source(f, v, flags, true); fputs(strempty(prefix), f); } fputc(']', f); } break; } case JSON_VARIANT_OBJECT: { size_t i, n; n = json_variant_elements(v); if (n == 0) fputs("{}", f); else { _cleanup_free_ char *joined = NULL; const char *prefix2; if (flags & JSON_FORMAT_PRETTY) { joined = strjoin(strempty(prefix), "\t"); if (!joined) return -ENOMEM; prefix2 = joined; fputs("{\n", f); } else { prefix2 = strempty(prefix); fputc('{', f); } for (i = 0; i < n; i += 2) { JsonVariant *e; e = json_variant_by_index(v, i); if (i > 0) { if (flags & JSON_FORMAT_PRETTY) fputs(",\n", f); else fputc(',', f); } if (flags & JSON_FORMAT_PRETTY) { print_source(f, e, flags, false); fputs(prefix2, f); } r = json_format(f, e, flags, prefix2); if (r < 0) return r; fputs(flags & JSON_FORMAT_PRETTY ? " : " : ":", f); r = json_format(f, json_variant_by_index(v, i+1), flags, prefix2); if (r < 0) return r; } if (flags & JSON_FORMAT_PRETTY) { fputc('\n', f); print_source(f, v, flags, true); fputs(strempty(prefix), f); } fputc('}', f); } break; } default: assert_not_reached("Unexpected variant type."); } return 0; } int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) { _cleanup_free_ char *s = NULL; size_t sz = 0; int r; /* Returns the length of the generated string (without the terminating NUL), * or negative on error. */ assert_return(v, -EINVAL); assert_return(ret, -EINVAL); if (flags & JSON_FORMAT_OFF) return -ENOEXEC; { _cleanup_fclose_ FILE *f = NULL; f = open_memstream_unlocked(&s, &sz); if (!f) return -ENOMEM; json_variant_dump(v, flags, f, NULL); /* Add terminating 0, so that the output buffer is a valid string. */ fputc('\0', f); r = fflush_and_check(f); } if (r < 0) return r; assert(s); *ret = TAKE_PTR(s); assert(sz > 0); return (int) sz - 1; } void json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const char *prefix) { if (!v) return; if (!f) f = stdout; print_source(f, v, flags, false); if (((flags & (JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_COLOR)) == JSON_FORMAT_COLOR_AUTO) && colors_enabled()) flags |= JSON_FORMAT_COLOR; if (((flags & (JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_PRETTY)) == JSON_FORMAT_PRETTY_AUTO)) flags |= on_tty() ? JSON_FORMAT_PRETTY : JSON_FORMAT_NEWLINE; if (flags & JSON_FORMAT_SSE) fputs("data: ", f); if (flags & JSON_FORMAT_SEQ) fputc('\x1e', f); /* ASCII Record Separator */ json_format(f, v, flags, prefix); if (flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_SEQ|JSON_FORMAT_SSE|JSON_FORMAT_NEWLINE)) 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) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; _cleanup_free_ JsonVariant **array = NULL; size_t i, n = 0, k = 0; int r; assert(v); if (json_variant_is_blank_object(*v)) return 0; if (!json_variant_is_object(*v)) return -EINVAL; if (strv_isempty(to_remove)) return 0; for (i = 0; i < json_variant_elements(*v); i += 2) { JsonVariant *p; p = json_variant_by_index(*v, i); if (!json_variant_has_type(p, JSON_VARIANT_STRING)) return -EINVAL; if (strv_contains(to_remove, json_variant_string(p))) { if (!array) { array = new(JsonVariant*, json_variant_elements(*v) - 2); if (!array) return -ENOMEM; for (k = 0; k < i; k++) array[k] = json_variant_by_index(*v, k); } n++; } else if (array) { array[k++] = p; array[k++] = json_variant_by_index(*v, i + 1); } } if (n == 0) return 0; r = json_variant_new_object(&w, array, k); if (r < 0) return r; json_variant_propagate_sensitive(*v, w); json_variant_unref(*v); *v = TAKE_PTR(w); return (int) n; } int json_variant_set_field(JsonVariant **v, const char *field, JsonVariant *value) { _cleanup_(json_variant_unrefp) JsonVariant *field_variant = NULL, *w = NULL; _cleanup_free_ JsonVariant **array = NULL; size_t i, k = 0; int r; assert(v); assert(field); if (json_variant_is_blank_object(*v)) { array = new(JsonVariant*, 2); if (!array) return -ENOMEM; } else { if (!json_variant_is_object(*v)) return -EINVAL; for (i = 0; i < json_variant_elements(*v); i += 2) { JsonVariant *p; p = json_variant_by_index(*v, i); if (!json_variant_is_string(p)) return -EINVAL; if (streq(json_variant_string(p), field)) { if (!array) { array = new(JsonVariant*, json_variant_elements(*v)); if (!array) return -ENOMEM; for (k = 0; k < i; k++) array[k] = json_variant_by_index(*v, k); } } else if (array) { array[k++] = p; array[k++] = json_variant_by_index(*v, i + 1); } } if (!array) { array = new(JsonVariant*, json_variant_elements(*v) + 2); if (!array) return -ENOMEM; for (k = 0; k < json_variant_elements(*v); k++) array[k] = json_variant_by_index(*v, k); } } r = json_variant_new_string(&field_variant, field); if (r < 0) return r; array[k++] = field_variant; array[k++] = value; r = json_variant_new_object(&w, array, k); if (r < 0) return r; json_variant_propagate_sensitive(*v, w); json_variant_unref(*v); *v = TAKE_PTR(w); 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_integer(JsonVariant **v, const char *field, intmax_t i) { _cleanup_(json_variant_unrefp) JsonVariant *m = NULL; int r; r = json_variant_new_integer(&m, i); 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_set_field_boolean(JsonVariant **v, const char *field, bool b) { _cleanup_(json_variant_unrefp) JsonVariant *m = NULL; int r; r = json_variant_new_boolean(&m, b); if (r < 0) return r; return json_variant_set_field(v, field, m); } int json_variant_set_field_strv(JsonVariant **v, const char *field, char **l) { _cleanup_(json_variant_unrefp) JsonVariant *m = NULL; int r; r = json_variant_new_array_strv(&m, l); 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_propagate_sensitive(*v, w); json_variant_propagate_sensitive(m, w); 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_propagate_sensitive(*v, nv); 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; JsonValue value; const void *source; size_t k; assert(nv); assert(v); /* Let's copy the simple types literally, and the larger types by references */ t = json_variant_type(v); switch (t) { case JSON_VARIANT_INTEGER: k = sizeof(intmax_t); value.integer = json_variant_integer(v); source = &value; break; case JSON_VARIANT_UNSIGNED: k = sizeof(uintmax_t); value.unsig = json_variant_unsigned(v); source = &value; break; case JSON_VARIANT_REAL: k = sizeof(long double); value.real = json_variant_real(v); source = &value; break; case JSON_VARIANT_BOOLEAN: k = sizeof(bool); value.boolean = json_variant_boolean(v); source = &value; break; case JSON_VARIANT_NULL: k = 0; source = NULL; break; case JSON_VARIANT_STRING: source = json_variant_string(v); k = strnlen(source, INLINE_STRING_MAX + 1); if (k <= INLINE_STRING_MAX) { k ++; break; } _fallthrough_; default: /* Everything else copy by reference */ c = malloc0(MAX(sizeof(JsonVariant), offsetof(JsonVariant, reference) + sizeof(JsonVariant*))); if (!c) return -ENOMEM; c->n_ref = 1; c->type = t; c->is_reference = true; c->reference = json_variant_ref(json_variant_formalize(v)); *nv = c; return 0; } c = malloc0(MAX(sizeof(JsonVariant), offsetof(JsonVariant, value) + k)); if (!c) return -ENOMEM; c->n_ref = 1; c->type = t; memcpy_safe(&c->value, source, k); json_variant_propagate_sensitive(v, c); *nv = c; return 0; } static bool json_single_ref(JsonVariant *v) { /* Checks whether the caller is the single owner of the object, i.e. can get away with changing it */ if (!json_variant_is_regular(v)) return false; if (v->is_embedded) return json_single_ref(v->parent); assert(v->n_ref > 0); return v->n_ref == 1; } static int json_variant_set_source(JsonVariant **v, JsonSource *source, unsigned line, unsigned column) { JsonVariant *w; int r; assert(v); /* Patch in source and line/column number. Tries to do this in-place if the caller is the sole referencer of * the object. If not, allocates a new object, possibly a surrogate for the original one */ if (!*v) return 0; if (source && line > source->max_line) source->max_line = line; if (source && column > source->max_column) source->max_column = column; if (!json_variant_is_regular(*v)) { if (!source && line == 0 && column == 0) return 0; } else { if (json_source_equal((*v)->source, source) && (*v)->line == line && (*v)->column == column) return 0; if (json_single_ref(*v)) { /* Sole reference? */ json_source_unref((*v)->source); (*v)->source = json_source_ref(source); (*v)->line = line; (*v)->column = column; return 1; } } r = json_variant_copy(&w, *v); if (r < 0) return r; assert(json_variant_is_regular(w)); assert(!w->is_embedded); assert(w->n_ref == 1); assert(!w->source); w->source = json_source_ref(source); w->line = line; w->column = column; json_variant_unref(*v); *v = w; return 1; } static void inc_lines_columns(unsigned *line, unsigned *column, const char *s, size_t n) { assert(line); assert(column); assert(s || n == 0); while (n > 0) { if (*s == '\n') { (*line)++; *column = 1; } else if ((signed char) *s >= 0 && *s < 127) /* Process ASCII chars quickly */ (*column)++; else { int w; w = utf8_encoded_valid_unichar(s, n); if (w < 0) /* count invalid unichars as normal characters */ w = 1; else if ((size_t) w > n) /* never read more than the specified number of characters */ w = (int) n; (*column)++; s += w; n -= w; continue; } s++; 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; 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 = TAKE_PTR(s); return JSON_TOKEN_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 + 5)) 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, (size_t) -1); 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, JsonValue *ret) { bool negative = false, exponent_negative = false, is_real = false; long double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0; intmax_t i = 0; uintmax_t u = 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_real) { if (negative) { if (i < INTMAX_MIN / 10) /* overflow */ is_real = true; else { intmax_t t = 10 * i; if (t < INTMAX_MIN + (*c - '0')) /* overflow */ is_real = true; else i = t - (*c - '0'); } } else { if (u > UINTMAX_MAX / 10) /* overflow */ is_real = true; else { uintmax_t t = 10 * u; if (t > UINTMAX_MAX - (*c - '0')) /* overflow */ is_real = true; else u = t + (*c - '0'); } } } x = 10.0 * x + (*c - '0'); c++; } while (strchr("0123456789", *c) && *c != 0); } if (*c == '.') { is_real = 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 (IN_SET(*c, 'e', 'E')) { is_real = 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_real) { ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10l((exponent_negative ? -1.0 : 1.0) * exponent); return JSON_TOKEN_REAL; } else if (negative) { ret->integer = i; return JSON_TOKEN_INTEGER; } else { ret->unsig = u; return JSON_TOKEN_UNSIGNED; } } int json_tokenize( const char **p, char **ret_string, JsonValue *ret_value, unsigned *ret_line, /* 'ret_line' returns the line at the beginning of this token */ unsigned *ret_column, void **state, unsigned *line, /* 'line' is used as a line state, it always reflect the line we are at after the token was read */ unsigned *column) { unsigned start_line, start_column; const char *start, *c; size_t n; int t, r; enum { STATE_NULL, STATE_VALUE, STATE_VALUE_POST, }; assert(p); assert(*p); assert(ret_string); assert(ret_value); assert(ret_line); assert(ret_column); assert(line); assert(column); assert(state); t = PTR_TO_INT(*state); if (t == STATE_NULL) { *line = 1; *column = 1; t = STATE_VALUE; } /* Skip over the whitespace */ n = strspn(*p, WHITESPACE); inc_lines_columns(line, column, *p, n); c = *p + n; /* Remember where we started processing this token */ start = c; start_line = *line; start_column = *column; if (*c == 0) { *ret_string = NULL; *ret_value = JSON_VALUE_NULL; r = JSON_TOKEN_END; goto finish; } switch (t) { case STATE_VALUE: if (*c == '{') { c++; *state = INT_TO_PTR(STATE_VALUE); r = JSON_TOKEN_OBJECT_OPEN; goto null_return; } else if (*c == '}') { c++; *state = INT_TO_PTR(STATE_VALUE_POST); r = JSON_TOKEN_OBJECT_CLOSE; goto null_return; } else if (*c == '[') { c++; *state = INT_TO_PTR(STATE_VALUE); r = JSON_TOKEN_ARRAY_OPEN; goto null_return; } else if (*c == ']') { c++; *state = INT_TO_PTR(STATE_VALUE_POST); r = JSON_TOKEN_ARRAY_CLOSE; goto null_return; } else if (*c == '"') { r = json_parse_string(&c, ret_string); if (r < 0) return r; *ret_value = JSON_VALUE_NULL; *state = INT_TO_PTR(STATE_VALUE_POST); goto finish; } else if (strchr("-0123456789", *c)) { r = json_parse_number(&c, ret_value); if (r < 0) return r; *ret_string = NULL; *state = INT_TO_PTR(STATE_VALUE_POST); goto finish; } else if (startswith(c, "true")) { *ret_string = NULL; ret_value->boolean = true; c += 4; *state = INT_TO_PTR(STATE_VALUE_POST); r = JSON_TOKEN_BOOLEAN; goto finish; } else if (startswith(c, "false")) { *ret_string = NULL; ret_value->boolean = false; c += 5; *state = INT_TO_PTR(STATE_VALUE_POST); r = JSON_TOKEN_BOOLEAN; goto finish; } else if (startswith(c, "null")) { *ret_string = NULL; *ret_value = JSON_VALUE_NULL; c += 4; *state = INT_TO_PTR(STATE_VALUE_POST); r = JSON_TOKEN_NULL; goto finish; } return -EINVAL; case STATE_VALUE_POST: if (*c == ':') { c++; *state = INT_TO_PTR(STATE_VALUE); r = JSON_TOKEN_COLON; goto null_return; } else if (*c == ',') { c++; *state = INT_TO_PTR(STATE_VALUE); r = JSON_TOKEN_COMMA; goto null_return; } else if (*c == '}') { c++; *state = INT_TO_PTR(STATE_VALUE_POST); r = JSON_TOKEN_OBJECT_CLOSE; goto null_return; } else if (*c == ']') { c++; *state = INT_TO_PTR(STATE_VALUE_POST); r = JSON_TOKEN_ARRAY_CLOSE; goto null_return; } return -EINVAL; default: assert_not_reached("Unexpected tokenizer state"); } null_return: *ret_string = NULL; *ret_value = JSON_VALUE_NULL; finish: inc_lines_columns(line, column, start, c - start); *p = c; *ret_line = start_line; *ret_column = start_column; return r; } typedef enum JsonExpect { /* The following values are used by json_parse() */ EXPECT_TOPLEVEL, EXPECT_END, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY, EXPECT_OBJECT_COLON, EXPECT_OBJECT_VALUE, EXPECT_OBJECT_COMMA, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT, EXPECT_ARRAY_COMMA, /* And these are used by json_build() */ EXPECT_ARRAY_ELEMENT, EXPECT_OBJECT_KEY, } JsonExpect; typedef struct JsonStack { JsonExpect expect; JsonVariant **elements; size_t n_elements, n_elements_allocated; unsigned line_before; unsigned column_before; size_t n_suppress; /* When building: if > 0, suppress this many subsequent elements. If == (size_t) -1, suppress all subsequent elements */ } JsonStack; static void json_stack_release(JsonStack *s) { assert(s); json_variant_unref_many(s->elements, s->n_elements); s->elements = mfree(s->elements); } static int json_parse_internal( const char **input, JsonSource *source, JsonParseFlags flags, JsonVariant **ret, unsigned *line, unsigned *column, bool continue_end) { size_t n_stack = 1, n_stack_allocated = 0, i; unsigned line_buffer = 0, column_buffer = 0; void *tokenizer_state = NULL; JsonStack *stack = NULL; const char *p; int r; assert_return(input, -EINVAL); assert_return(ret, -EINVAL); p = *input; if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack)) return -ENOMEM; stack[0] = (JsonStack) { .expect = EXPECT_TOPLEVEL, }; if (!line) line = &line_buffer; if (!column) column = &column_buffer; for (;;) { _cleanup_(json_variant_unrefp) JsonVariant *add = NULL; _cleanup_free_ char *string = NULL; unsigned line_token, column_token; JsonStack *current; JsonValue value; int token; assert(n_stack > 0); current = stack + n_stack - 1; if (continue_end && current->expect == EXPECT_END) goto done; token = json_tokenize(&p, &string, &value, &line_token, &column_token, &tokenizer_state, line, column); if (token < 0) { r = token; goto finish; } switch (token) { case JSON_TOKEN_END: if (current->expect != EXPECT_END) { r = -EINVAL; goto finish; } assert(current->n_elements == 1); assert(n_stack == 1); goto done; case JSON_TOKEN_COLON: if (current->expect != EXPECT_OBJECT_COLON) { r = -EINVAL; goto finish; } current->expect = EXPECT_OBJECT_VALUE; break; case JSON_TOKEN_COMMA: if (current->expect == EXPECT_OBJECT_COMMA) current->expect = EXPECT_OBJECT_NEXT_KEY; else if (current->expect == EXPECT_ARRAY_COMMA) current->expect = EXPECT_ARRAY_NEXT_ELEMENT; else { r = -EINVAL; goto finish; } break; case JSON_TOKEN_OBJECT_OPEN: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) { r = -ENOMEM; goto finish; } current = stack + n_stack - 1; /* Prepare the expect for when we return from the child */ if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; else if (current->expect == EXPECT_OBJECT_VALUE) current->expect = EXPECT_OBJECT_COMMA; else { assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); current->expect = EXPECT_ARRAY_COMMA; } stack[n_stack++] = (JsonStack) { .expect = EXPECT_OBJECT_FIRST_KEY, .line_before = line_token, .column_before = column_token, }; current = stack + n_stack - 1; break; case JSON_TOKEN_OBJECT_CLOSE: if (!IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_COMMA)) { r = -EINVAL; goto finish; } assert(n_stack > 1); r = json_variant_new_object(&add, current->elements, current->n_elements); if (r < 0) goto finish; line_token = current->line_before; column_token = current->column_before; json_stack_release(current); n_stack--, current--; break; case JSON_TOKEN_ARRAY_OPEN: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) { r = -ENOMEM; goto finish; } current = stack + n_stack - 1; /* Prepare the expect for when we return from the child */ if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; else if (current->expect == EXPECT_OBJECT_VALUE) current->expect = EXPECT_OBJECT_COMMA; else { assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); current->expect = EXPECT_ARRAY_COMMA; } stack[n_stack++] = (JsonStack) { .expect = EXPECT_ARRAY_FIRST_ELEMENT, .line_before = line_token, .column_before = column_token, }; break; case JSON_TOKEN_ARRAY_CLOSE: if (!IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_COMMA)) { r = -EINVAL; goto finish; } assert(n_stack > 1); r = json_variant_new_array(&add, current->elements, current->n_elements); if (r < 0) goto finish; line_token = current->line_before; column_token = current->column_before; json_stack_release(current); n_stack--, current--; break; case JSON_TOKEN_STRING: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } r = json_variant_new_string(&add, string); if (r < 0) goto finish; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; else if (IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY)) current->expect = EXPECT_OBJECT_COLON; else if (current->expect == EXPECT_OBJECT_VALUE) current->expect = EXPECT_OBJECT_COMMA; else { assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); current->expect = EXPECT_ARRAY_COMMA; } break; case JSON_TOKEN_REAL: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } r = json_variant_new_real(&add, value.real); if (r < 0) goto finish; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; else if (current->expect == EXPECT_OBJECT_VALUE) current->expect = EXPECT_OBJECT_COMMA; else { assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); current->expect = EXPECT_ARRAY_COMMA; } break; case JSON_TOKEN_INTEGER: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } r = json_variant_new_integer(&add, value.integer); if (r < 0) goto finish; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; else if (current->expect == EXPECT_OBJECT_VALUE) current->expect = EXPECT_OBJECT_COMMA; else { assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); current->expect = EXPECT_ARRAY_COMMA; } break; case JSON_TOKEN_UNSIGNED: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } r = json_variant_new_unsigned(&add, value.unsig); if (r < 0) goto finish; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; else if (current->expect == EXPECT_OBJECT_VALUE) current->expect = EXPECT_OBJECT_COMMA; else { assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); current->expect = EXPECT_ARRAY_COMMA; } break; case JSON_TOKEN_BOOLEAN: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } r = json_variant_new_boolean(&add, value.boolean); if (r < 0) goto finish; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; else if (current->expect == EXPECT_OBJECT_VALUE) current->expect = EXPECT_OBJECT_COMMA; else { assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); current->expect = EXPECT_ARRAY_COMMA; } break; case JSON_TOKEN_NULL: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } r = json_variant_new_null(&add); if (r < 0) goto finish; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; else if (current->expect == EXPECT_OBJECT_VALUE) current->expect = EXPECT_OBJECT_COMMA; else { assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); current->expect = EXPECT_ARRAY_COMMA; } break; default: assert_not_reached("Unexpected token"); } if (add) { /* If we are asked to make this parsed object sensitive, then let's apply this * immediately after allocating each variant, so that when we abort half-way * everything we already allocated that is then freed is correctly marked. */ if (FLAGS_SET(flags, JSON_PARSE_SENSITIVE)) json_variant_sensitive(add); (void) json_variant_set_source(&add, source, line_token, column_token); if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) { r = -ENOMEM; goto finish; } current->elements[current->n_elements++] = TAKE_PTR(add); } } done: assert(n_stack == 1); assert(stack[0].n_elements == 1); *ret = json_variant_ref(stack[0].elements[0]); *input = p; r = 0; finish: for (i = 0; i < n_stack; i++) json_stack_release(stack + i); free(stack); return r; } int json_parse(const char *input, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { return json_parse_internal(&input, NULL, flags, ret, ret_line, ret_column, false); } int json_parse_continue(const char **p, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { return json_parse_internal(p, NULL, flags, ret, ret_line, ret_column, true); } int json_parse_file_at(FILE *f, int dir_fd, const char *path, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { _cleanup_(json_source_unrefp) JsonSource *source = NULL; _cleanup_free_ char *text = NULL; const char *p; int r; if (f) r = read_full_stream(f, &text, NULL); else if (path) r = read_full_file_full(dir_fd, path, UINT64_MAX, SIZE_MAX, 0, NULL, &text, NULL); else return -EINVAL; if (r < 0) return r; if (path) { source = json_source_new(path); if (!source) return -ENOMEM; } p = text; return json_parse_internal(&p, source, flags, ret, ret_line, ret_column, false); } int json_buildv(JsonVariant **ret, va_list ap) { JsonStack *stack = NULL; size_t n_stack = 1, n_stack_allocated = 0, i; int r; assert_return(ret, -EINVAL); if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack)) return -ENOMEM; stack[0] = (JsonStack) { .expect = EXPECT_TOPLEVEL, }; for (;;) { _cleanup_(json_variant_unrefp) JsonVariant *add = NULL; size_t n_subtract = 0; /* how much to subtract from current->n_suppress, i.e. how many elements would * have been added to the current variant */ JsonStack *current; int command; assert(n_stack > 0); current = stack + n_stack - 1; if (current->expect == EXPECT_END) goto done; command = va_arg(ap, int); switch (command) { case _JSON_BUILD_STRING: { const char *p; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } p = va_arg(ap, const char *); if (current->n_suppress == 0) { r = json_variant_new_string(&add, p); 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_INTEGER: { intmax_t j; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } j = va_arg(ap, intmax_t); if (current->n_suppress == 0) { r = json_variant_new_integer(&add, j); 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_UNSIGNED: { uintmax_t j; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } j = va_arg(ap, uintmax_t); if (current->n_suppress == 0) { r = json_variant_new_unsigned(&add, j); 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_REAL: { long double d; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } d = va_arg(ap, long double); if (current->n_suppress == 0) { r = json_variant_new_real(&add, d); 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_BOOLEAN: { bool b; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } b = va_arg(ap, int); if (current->n_suppress == 0) { r = json_variant_new_boolean(&add, b); 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_NULL: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } if (current->n_suppress == 0) { r = json_variant_new_null(&add); 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_VARIANT: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } /* Note that we don't care for current->n_suppress here, after all the variant is already * allocated anyway... */ add = va_arg(ap, JsonVariant*); if (!add) add = JSON_VARIANT_MAGIC_NULL; else json_variant_ref(add); 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_VARIANT_ARRAY: { JsonVariant **array; size_t n; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } array = va_arg(ap, JsonVariant**); n = va_arg(ap, size_t); if (current->n_suppress == 0) { r = json_variant_new_array(&add, array, 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_LITERAL: { const char *l; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } l = va_arg(ap, const char *); if (l) { /* Note that we don't care for current->n_suppress here, we should generate parsing * errors even in suppressed object properties */ r = json_parse(l, 0, &add, NULL, NULL); if (r < 0) goto finish; } else add = JSON_VARIANT_MAGIC_NULL; 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_ARRAY_BEGIN: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) { r = -ENOMEM; goto finish; } current = stack + n_stack - 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); stack[n_stack++] = (JsonStack) { .expect = EXPECT_ARRAY_ELEMENT, .n_suppress = current->n_suppress != 0 ? (size_t) -1 : 0, /* if we shall suppress the * new array, then we should * also suppress all array * members */ }; break; case _JSON_BUILD_ARRAY_END: if (current->expect != EXPECT_ARRAY_ELEMENT) { r = -EINVAL; goto finish; } assert(n_stack > 1); if (current->n_suppress == 0) { r = json_variant_new_array(&add, current->elements, current->n_elements); if (r < 0) goto finish; } n_subtract = 1; json_stack_release(current); n_stack--, current--; break; case _JSON_BUILD_STRV: { char **l; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } l = va_arg(ap, char **); if (current->n_suppress == 0) { r = json_variant_new_array_strv(&add, l); 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_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_HEX: { 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_hex(&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_ID128: { sd_id128_t id; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } id = va_arg(ap, sd_id128_t); if (current->n_suppress == 0) { r = json_variant_new_id128(&add, id); 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_BYTE_ARRAY: { const void *array; size_t n; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { r = -EINVAL; goto finish; } array = va_arg(ap, const void*); n = va_arg(ap, size_t); if (current->n_suppress == 0) { r = json_variant_new_array_bytes(&add, array, 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)) { r = -EINVAL; goto finish; } if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) { r = -ENOMEM; goto finish; } current = stack + n_stack - 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); stack[n_stack++] = (JsonStack) { .expect = EXPECT_OBJECT_KEY, .n_suppress = current->n_suppress != 0 ? (size_t) -1 : 0, /* if we shall suppress the * new object, then we should * also suppress all object * members */ }; break; case _JSON_BUILD_OBJECT_END: if (current->expect != EXPECT_OBJECT_KEY) { r = -EINVAL; goto finish; } assert(n_stack > 1); if (current->n_suppress == 0) { r = json_variant_new_object(&add, current->elements, current->n_elements); if (r < 0) goto finish; } n_subtract = 1; json_stack_release(current); n_stack--, current--; break; case _JSON_BUILD_PAIR: { const char *n; if (current->expect != EXPECT_OBJECT_KEY) { r = -EINVAL; goto finish; } n = va_arg(ap, const char *); if (current->n_suppress == 0) { r = json_variant_new_string(&add, n); if (r < 0) goto finish; } n_subtract = 1; current->expect = EXPECT_OBJECT_VALUE; break; } case _JSON_BUILD_PAIR_CONDITION: { const char *n; bool b; if (current->expect != EXPECT_OBJECT_KEY) { r = -EINVAL; goto finish; } b = va_arg(ap, int); n = va_arg(ap, const char *); if (b && current->n_suppress == 0) { r = json_variant_new_string(&add, n); if (r < 0) goto finish; } n_subtract = 1; /* we generated one item */ if (!b && current->n_suppress != (size_t) -1) current->n_suppress += 2; /* Suppress this one and the next item */ current->expect = EXPECT_OBJECT_VALUE; break; }} /* If a variant was generated, add it to our current variant, but only if we are not supposed to suppress additions */ if (add && current->n_suppress == 0) { if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) { r = -ENOMEM; goto finish; } current->elements[current->n_elements++] = TAKE_PTR(add); } /* If we are supposed to suppress items, let's subtract how many items where generated from that * counter. Except if the counter is (size_t) -1, i.e. we shall suppress an infinite number of elements * on this stack level */ if (current->n_suppress != (size_t) -1) { if (current->n_suppress <= n_subtract) /* Saturated */ current->n_suppress = 0; else current->n_suppress -= n_subtract; } } done: assert(n_stack == 1); assert(stack[0].n_elements == 1); *ret = json_variant_ref(stack[0].elements[0]); r = 0; finish: for (i = 0; i < n_stack; i++) json_stack_release(stack + i); free(stack); return r; } int json_build(JsonVariant **ret, ...) { va_list ap; int r; va_start(ap, ret); r = json_buildv(ret, ap); va_end(ap); return r; } int json_log_internal( JsonVariant *variant, int level, int error, const char *file, int line, const char *func, const char *format, ...) { PROTECT_ERRNO; unsigned source_line, source_column; char buffer[LINE_MAX]; const char *source; va_list ap; int r; errno = ERRNO_VALUE(error); va_start(ap, format); (void) vsnprintf(buffer, sizeof buffer, format, ap); va_end(ap); if (variant) { r = json_variant_get_source(variant, &source, &source_line, &source_column); if (r < 0) return r; } else { source = NULL; source_line = 0; source_column = 0; } if (source && source_line > 0 && source_column > 0) return log_struct_internal( LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level), error, file, line, func, "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR, "CONFIG_FILE=%s", source, "CONFIG_LINE=%u", source_line, "CONFIG_COLUMN=%u", source_column, LOG_MESSAGE("%s:%u:%u: %s", source, source_line, source_column, buffer), NULL); else if (source_line > 0 && source_column > 0) return log_struct_internal( LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level), error, file, line, func, "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR, "CONFIG_LINE=%u", source_line, "CONFIG_COLUMN=%u", source_column, LOG_MESSAGE("(string):%u:%u: %s", source_line, source_column, buffer), NULL); else return log_struct_internal( LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level), error, file, line, func, "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR, LOG_MESSAGE("%s", buffer), NULL); } int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata) { const JsonDispatch *p; size_t i, n, m; int r, done = 0; bool *found; if (!json_variant_is_object(v)) { json_log(v, flags, 0, "JSON variant is not an object."); if (flags & JSON_PERMISSIVE) return 0; return -EINVAL; } for (p = table, m = 0; p->name; p++) m++; found = newa0(bool, m); n = json_variant_elements(v); for (i = 0; i < n; i += 2) { JsonVariant *key, *value; assert_se(key = json_variant_by_index(v, i)); assert_se(value = json_variant_by_index(v, i+1)); for (p = table; p->name; p++) if (p->name == POINTER_MAX || streq_ptr(json_variant_string(key), p->name)) break; if (p->name) { /* Found a matching entry! :-) */ JsonDispatchFlags merged_flags; merged_flags = flags | p->flags; if (p->type != _JSON_VARIANT_TYPE_INVALID && !json_variant_has_type(value, p->type)) { json_log(value, merged_flags, 0, "Object field '%s' has wrong type %s, expected %s.", json_variant_string(key), json_variant_type_to_string(json_variant_type(value)), json_variant_type_to_string(p->type)); if (merged_flags & JSON_PERMISSIVE) continue; return -EINVAL; } if (found[p-table]) { json_log(value, merged_flags, 0, "Duplicate object field '%s'.", json_variant_string(key)); if (merged_flags & JSON_PERMISSIVE) continue; return -ENOTUNIQ; } found[p-table] = true; if (p->callback) { r = p->callback(json_variant_string(key), value, merged_flags, (uint8_t*) userdata + p->offset); if (r < 0) { if (merged_flags & JSON_PERMISSIVE) continue; return r; } } done ++; } else { /* Didn't find a matching entry! :-( */ if (bad) { r = bad(json_variant_string(key), value, flags, userdata); if (r < 0) { if (flags & JSON_PERMISSIVE) continue; return r; } else done ++; } else { json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key)); if (flags & JSON_PERMISSIVE) continue; return -EADDRNOTAVAIL; } } } for (p = table; p->name; p++) { JsonDispatchFlags merged_flags = p->flags | flags; if ((merged_flags & JSON_MANDATORY) && !found[p-table]) { json_log(v, merged_flags, 0, "Missing object field '%s'.", p->name); if ((merged_flags & JSON_PERMISSIVE)) continue; return -ENXIO; } } return done; } int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { bool *b = userdata; assert(variant); assert(b); if (!json_variant_is_boolean(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a boolean.", strna(name)); *b = json_variant_boolean(variant); return 0; } int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { int *b = userdata; 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)); *b = json_variant_boolean(variant); return 0; } int json_dispatch_integer(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { intmax_t *i = userdata; assert(variant); assert(i); if (!json_variant_is_integer(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); *i = json_variant_integer(variant); return 0; } int json_dispatch_unsigned(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { uintmax_t *u = userdata; assert(variant); assert(u); if (!json_variant_is_unsigned(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer.", strna(name)); *u = json_variant_unsigned(variant); return 0; } int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { uint32_t *u = userdata; assert(variant); assert(u); if (!json_variant_is_unsigned(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer.", strna(name)); if (json_variant_unsigned(variant) > UINT32_MAX) return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); *u = (uint32_t) json_variant_unsigned(variant); return 0; } int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { int32_t *i = userdata; assert(variant); assert(i); if (!json_variant_is_integer(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); if (json_variant_integer(variant) < INT32_MIN || json_variant_integer(variant) > INT32_MAX) return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); *i = (int32_t) json_variant_integer(variant); return 0; } int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { char **s = userdata; int r; assert(variant); assert(s); if (json_variant_is_null(variant)) { *s = mfree(*s); return 0; } 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"); return 0; } int json_dispatch_const_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { const char **s = userdata; assert(variant); assert(s); if (json_variant_is_null(variant)) { *s = NULL; return 0; } 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)); *s = json_variant_string(variant); return 0; } int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { _cleanup_strv_free_ char **l = NULL; char ***s = userdata; JsonVariant *e; int r; assert(variant); assert(s); if (json_variant_is_null(variant)) { *s = strv_free(*s); return 0; } /* 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(); strv_free_and_replace(*s, l); return 0; } if (!json_variant_is_array(variant)) return json_log(variant, SYNTHETIC_ERRNO(EINVAL), flags, "JSON field '%s' is not an array.", strna(name)); JSON_VARIANT_ARRAY_FOREACH(e, variant) { 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"); } strv_free_and_replace(*s, l); return 0; } int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { JsonVariant **p = userdata; assert(variant); assert(p); json_variant_unref(*p); *p = json_variant_ref(variant); return 0; } int json_dispatch_uid_gid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { uid_t *uid = userdata; uintmax_t k; assert_cc(sizeof(uid_t) == sizeof(uint32_t)); assert_cc(sizeof(gid_t) == sizeof(uint32_t)); DISABLE_WARNING_TYPE_LIMITS; assert_cc(((uid_t) -1 < (uid_t) 0) == ((gid_t) -1 < (gid_t) 0)); REENABLE_WARNING; if (json_variant_is_null(variant)) { *uid = UID_INVALID; return 0; } if (!json_variant_is_unsigned(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a integer.", strna(name)); k = json_variant_unsigned(variant); if (k > UINT32_MAX || !uid_is_valid(k)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid UID/GID.", strna(name)); *uid = k; return 0; } int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { char **s = userdata; const char *n; int r; if (json_variant_is_null(variant)) { *s = mfree(*s); return 0; } if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); n = json_variant_string(variant); if (!valid_user_group_name(n, FLAGS_SET(flags, JSON_RELAX) ? VALID_USER_RELAX : 0)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid user/group name.", strna(name)); r = free_and_strdup(s, n); if (r < 0) return json_log(variant, flags, r, "Failed to allocate string: %m"); return 0; } int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { sd_id128_t *uuid = userdata; int r; if (json_variant_is_null(variant)) { *uuid = SD_ID128_NULL; return 0; } if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); r = sd_id128_from_string(json_variant_string(variant), uuid); if (r < 0) return json_log(variant, flags, r, "JSON field '%s' is not a valid UID.", strna(name)); return 0; } int json_dispatch_unsupported(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not allowed in this object.", strna(name)); } 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; json_variant_propagate_sensitive(*v, n); 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; json_variant_propagate_sensitive(*v, n); 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); } int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size) { if (!json_variant_is_string(v)) return -EINVAL; return unhexmem(json_variant_string(v), (size_t) -1, ret, ret_size); } int json_parse_cmdline_parameter_and_warn(const char *s, JsonFormatFlags *ret) { assert(s); assert(ret); if (streq(s, "pretty")) *ret = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO; else if (streq(s, "short")) *ret = JSON_FORMAT_NEWLINE; else if (streq(s, "off")) *ret = JSON_FORMAT_OFF; else if (streq(s, "help")) { puts("pretty\n" "short\n" "off"); return 0; /* 0 means → we showed a brief help, exit now */ } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown argument to --json= switch: %s", s); return 1; /* 1 means → properly parsed */ } static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = { [JSON_VARIANT_STRING] = "string", [JSON_VARIANT_INTEGER] = "integer", [JSON_VARIANT_UNSIGNED] = "unsigned", [JSON_VARIANT_REAL] = "real", [JSON_VARIANT_NUMBER] = "number", [JSON_VARIANT_BOOLEAN] = "boolean", [JSON_VARIANT_ARRAY] = "array", [JSON_VARIANT_OBJECT] = "object", [JSON_VARIANT_NULL] = "null", }; DEFINE_STRING_TABLE_LOOKUP(json_variant_type, JsonVariantType);