Systemd/src/shared/json.c

4411 lines
145 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include <locale.h>
#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/types.h>
#include "sd-messages.h"
#include "alloc-util.h"
2019-03-14 12:24:39 +01:00
#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"
2019-07-04 18:27:12 +02:00
#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_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);
{
_cleanup_fclose_ FILE *f = NULL;
2019-04-04 11:46:44 +02:00
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);
}
2019-07-04 17:42:32 +02:00
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);
2019-07-04 17:42:32 +02:00
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)
fileio: beef up READ_FULL_FILE_CONNECT_SOCKET to allow setting sender socket name This beefs up the READ_FULL_FILE_CONNECT_SOCKET logic of read_full_file_full() a bit: when used a sender socket name may be specified. If specified as NULL behaviour is as before: the client socket name is picked by the kernel. But if specified as non-NULL the client can pick a socket name to use when connecting. This is useful to communicate a minimal amount of metainformation from client to server, outside of the transport payload. Specifically, these beefs up the service credential logic to pass an abstract AF_UNIX socket name as client socket name when connecting via READ_FULL_FILE_CONNECT_SOCKET, that includes the requesting unit name and the eventual credential name. This allows servers implementing the trivial credential socket logic to distinguish clients: via a simple getpeername() it can be determined which unit is requesting a credential, and which credential specifically. Example: with this patch in place, in a unit file "waldo.service" a configuration line like the following: LoadCredential=foo:/run/quux/creds.sock will result in a connection to the AF_UNIX socket /run/quux/creds.sock, originating from an abstract namespace AF_UNIX socket: @$RANDOM/unit/waldo.service/foo (The $RANDOM is replaced by some randomized string. This is included in the socket name order to avoid namespace squatting issues: the abstract socket namespace is open to unprivileged users after all, and care needs to be taken not to use guessable names) The services listening on the /run/quux/creds.sock socket may thus easily retrieve the name of the unit the credential is requested for plus the credential name, via a simpler getpeername(), discarding the random preifx and the /unit/ string. This logic uses "/" as separator between the fields, since both unit names and credential names appear in the file system, and thus are designed to use "/" as outer separators. Given that it's a good safe choice to use as separators here, too avoid any conflicts. This is a minimal patch only: the new logic is used only for the unit file credential logic. For other places where we use READ_FULL_FILE_CONNECT_SOCKET it is probably a good idea to use this scheme too, but this should be done carefully in later patches, since the socket names become API that way, and we should determine the right amount of info to pass over.
2020-11-02 12:07:51 +01:00
r = read_full_file_full(dir_fd, path, 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 (;;) {
2018-10-12 18:38:40 +02:00
_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_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;
}
2018-10-12 18:38:40 +02:00
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
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)
2019-04-23 15:26:09 +02:00
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;
}
2019-07-04 18:27:12 +02:00
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;
2019-07-04 18:27:12 +02:00
assert_cc(((uid_t) -1 < (uid_t) 0) == ((gid_t) -1 < (gid_t) 0));
REENABLE_WARNING;
2019-07-04 18:27:12 +02:00
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))
2019-07-04 18:27:12 +02:00
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);
}
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);