diff options
author | Yu Watanabe <watanabe.yu+github@gmail.com> | 2019-12-17 23:10:08 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-12-17 23:10:08 +0900 |
commit | 3267cb45e9458b473413a1322fc03f70da5d3645 (patch) | |
tree | f50b45aa4ab59b1b5b98ecfcda7d675f0493a1d8 | |
parent | 05de16766b6bae290c500857269b753df2a0c649 (diff) | |
parent | 2d8143048bc67470c76e8975cdecfb6b9823ecce (diff) | |
download | systemd-3267cb45e9458b473413a1322fc03f70da5d3645.tar.gz |
Merge pull request #14208 from poettering/json-homed-prepare
json bits from homed PR
-rw-r--r-- | src/basic/fileio.c | 124 | ||||
-rw-r--r-- | src/basic/fileio.h | 7 | ||||
-rw-r--r-- | src/fuzz/fuzz-json.c | 2 | ||||
-rw-r--r-- | src/network/netdev/macsec.c | 2 | ||||
-rw-r--r-- | src/network/netdev/wireguard.c | 2 | ||||
-rw-r--r-- | src/nspawn/nspawn-oci.c | 19 | ||||
-rw-r--r-- | src/shared/json.c | 886 | ||||
-rw-r--r-- | src/shared/json.h | 76 | ||||
-rw-r--r-- | src/shared/varlink.c | 2 | ||||
-rw-r--r-- | src/test/test-json.c | 104 |
10 files changed, 1132 insertions, 92 deletions
diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 714a00cd9a..fe0c4f4707 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -136,16 +136,21 @@ static int write_string_file_atomic( assert(fn); assert(line); + /* Note that we'd really like to use O_TMPFILE here, but can't really, since we want replacement + * semantics here, and O_TMPFILE can't offer that. i.e. rename() replaces but linkat() doesn't. */ + r = fopen_temporary(fn, &f, &p); if (r < 0) return r; - (void) fchmod_umask(fileno(f), 0644); - r = write_string_stream_ts(f, line, flags, ts); if (r < 0) goto fail; + r = fchmod_umask(fileno(f), FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0644); + if (r < 0) + goto fail; + if (rename(p, fn) < 0) { r = -errno; goto fail; @@ -165,7 +170,7 @@ int write_string_file_ts( struct timespec *ts) { _cleanup_fclose_ FILE *f = NULL; - int q, r; + int q, r, fd; assert(fn); assert(line); @@ -190,26 +195,20 @@ int write_string_file_ts( } else assert(!ts); - if (flags & WRITE_STRING_FILE_CREATE) { - r = fopen_unlocked(fn, "we", &f); - if (r < 0) - goto fail; - } else { - int fd; - - /* We manually build our own version of fopen(..., "we") that - * works without O_CREAT */ - fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY | ((flags & WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0)); - if (fd < 0) { - r = -errno; - goto fail; - } + /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */ + fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY | + (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0), + (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666)); + if (fd < 0) { + r = -errno; + goto fail; + } - r = fdopen_unlocked(fd, "w", &f); - if (r < 0) { - safe_close(fd); - goto fail; - } + r = fdopen_unlocked(fd, "w", &f); + if (r < 0) { + safe_close(fd); + goto fail; } if (flags & WRITE_STRING_FILE_DISABLE_BUFFER) @@ -543,17 +542,19 @@ finalize: return r; } -int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) { +int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) { _cleanup_fclose_ FILE *f = NULL; int r; assert(filename); assert(contents); - r = fopen_unlocked(filename, "re", &f); + r = xfopenat(dir_fd, filename, "re", 0, &f); if (r < 0) return r; + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + return read_full_stream_full(f, filename, flags, contents, size); } @@ -680,6 +681,81 @@ DIR *xopendirat(int fd, const char *name, int flags) { return d; } +static int mode_to_flags(const char *mode) { + const char *p; + int flags; + + if ((p = startswith(mode, "r+"))) + flags = O_RDWR; + else if ((p = startswith(mode, "r"))) + flags = O_RDONLY; + else if ((p = startswith(mode, "w+"))) + flags = O_RDWR|O_CREAT|O_TRUNC; + else if ((p = startswith(mode, "w"))) + flags = O_WRONLY|O_CREAT|O_TRUNC; + else if ((p = startswith(mode, "a+"))) + flags = O_RDWR|O_CREAT|O_APPEND; + else if ((p = startswith(mode, "a"))) + flags = O_WRONLY|O_CREAT|O_APPEND; + else + return -EINVAL; + + for (; *p != 0; p++) { + + switch (*p) { + + case 'e': + flags |= O_CLOEXEC; + break; + + case 'x': + flags |= O_EXCL; + break; + + case 'm': + /* ignore this here, fdopen() might care later though */ + break; + + case 'c': /* not sure what to do about this one */ + default: + return -EINVAL; + } + } + + return flags; +} + +int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) { + FILE *f; + + /* A combination of fopen() with openat() */ + + if (dir_fd == AT_FDCWD && flags == 0) { + f = fopen(path, mode); + if (!f) + return -errno; + } else { + int fd, mode_flags; + + mode_flags = mode_to_flags(mode); + if (mode_flags < 0) + return mode_flags; + + fd = openat(dir_fd, path, mode_flags | flags); + if (fd < 0) + return -errno; + + f = fdopen(fd, mode); + if (!f) { + safe_close(fd); + return -errno; + } + } + + *ret = f; + return 0; +} + static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) { char **i; diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 31bfef33ac..e6fea2afd4 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -6,6 +6,7 @@ #include <stddef.h> #include <stdio.h> #include <sys/stat.h> +#include <sys/fcntl.h> #include <sys/types.h> #include "macro.h" @@ -22,6 +23,7 @@ typedef enum { WRITE_STRING_FILE_DISABLE_BUFFER = 1 << 5, WRITE_STRING_FILE_NOFOLLOW = 1 << 6, WRITE_STRING_FILE_MKDIR_0755 = 1 << 7, + WRITE_STRING_FILE_MODE_0600 = 1 << 8, /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file() @@ -52,9 +54,9 @@ static inline int write_string_file(const char *fn, const char *line, WriteStrin int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4); int read_one_line_file(const char *filename, char **line); -int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size); +int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size); static inline int read_full_file(const char *filename, char **contents, size_t *size) { - return read_full_file_full(filename, 0, contents, size); + return read_full_file_full(AT_FDCWD, filename, 0, contents, size); } int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size); int read_full_stream_full(FILE *f, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size); @@ -69,6 +71,7 @@ int executable_is_script(const char *path, char **interpreter); int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field); DIR *xopendirat(int dirfd, const char *name, int flags); +int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret); int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f); int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f); diff --git a/src/fuzz/fuzz-json.c b/src/fuzz/fuzz-json.c index ce7b69dbb9..c01e2a570c 100644 --- a/src/fuzz/fuzz-json.c +++ b/src/fuzz/fuzz-json.c @@ -18,7 +18,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { f = fmemopen_unlocked((char*) data, size, "re"); assert_se(f); - if (json_parse_file(f, NULL, &v, NULL, NULL) < 0) + if (json_parse_file(f, NULL, 0, &v, NULL, NULL) < 0) return 0; g = open_memstream_unlocked(&out, &out_size); diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c index 010e16bcaf..7d1fec3afe 100644 --- a/src/network/netdev/macsec.c +++ b/src/network/netdev/macsec.c @@ -981,7 +981,7 @@ static int macsec_read_key_file(NetDev *netdev, SecurityAssociation *sa) { (void) warn_file_is_world_accessible(sa->key_file, NULL, NULL, 0); - r = read_full_file_full(sa->key_file, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX, (char **) &key, &key_len); + r = read_full_file_full(AT_FDCWD, sa->key_file, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX, (char **) &key, &key_len); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to read key from '%s', ignoring: %m", diff --git a/src/network/netdev/wireguard.c b/src/network/netdev/wireguard.c index 71ff41d574..bfed13e2ec 100644 --- a/src/network/netdev/wireguard.c +++ b/src/network/netdev/wireguard.c @@ -902,7 +902,7 @@ static int wireguard_read_key_file(const char *filename, uint8_t dest[static WG_ (void) warn_file_is_world_accessible(filename, NULL, NULL, 0); - r = read_full_file_full(filename, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64, &key, &key_len); + r = read_full_file_full(AT_FDCWD, filename, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64, &key, &key_len); if (r < 0) return r; diff --git a/src/nspawn/nspawn-oci.c b/src/nspawn/nspawn-oci.c index 4519c74b95..782c03c539 100644 --- a/src/nspawn/nspawn-oci.c +++ b/src/nspawn/nspawn-oci.c @@ -172,24 +172,13 @@ static int oci_env(const char *name, JsonVariant *v, JsonDispatchFlags flags, vo static int oci_args(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { _cleanup_strv_free_ char **l = NULL; char ***value = userdata; - JsonVariant *e; int r; assert(value); - JSON_VARIANT_ARRAY_FOREACH(e, v) { - const char *n; - - if (!json_variant_is_string(e)) - return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), - "Argument is not a string."); - - assert_se(n = json_variant_string(e)); - - r = strv_extend(&l, n); - if (r < 0) - return log_oom(); - } + r = json_variant_strv(v, &l); + if (r < 0) + return json_log(v, flags, r, "Cannot parse arguments as list of strings: %m"); if (strv_isempty(l)) return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), @@ -2214,7 +2203,7 @@ int oci_load(FILE *f, const char *bundle, Settings **ret) { path = strjoina(bundle, "/config.json"); - r = json_parse_file(f, path, &oci, &line, &column); + r = json_parse_file(f, path, 0, &oci, &line, &column); if (r < 0) { if (line != 0 && column != 0) return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", path, line, column); diff --git a/src/shared/json.c b/src/shared/json.c index 7393c100cd..869aa279ee 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -23,6 +23,7 @@ #include "string-util.h" #include "strv.h" #include "terminal-util.h" +#include "user-util.h" #include "utf8.h" /* Refuse putting together variants with a larger depth than 4K by default (as a protection against overflowing stacks @@ -71,6 +72,15 @@ struct JsonVariant { /* 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; @@ -212,10 +222,10 @@ static uint16_t json_variant_depth(JsonVariant *v) { return v->depth; } -static JsonVariant *json_variant_normalize(JsonVariant *v) { +static JsonVariant *json_variant_formalize(JsonVariant *v) { - /* Converts json variants to their normalized form, i.e. fully dereferenced and wherever possible converted to - * the "magic" version if there is one */ + /* Converts json variant pointers to their normalized form, i.e. fully dereferenced and wherever + * possible converted to the "magic" version if there is one */ if (!v) return NULL; @@ -256,9 +266,9 @@ static JsonVariant *json_variant_normalize(JsonVariant *v) { } } -static JsonVariant *json_variant_conservative_normalize(JsonVariant *v) { +static JsonVariant *json_variant_conservative_formalize(JsonVariant *v) { - /* Much like json_variant_normalize(), but won't simplify if the variant has a source/line location attached to + /* Much like json_variant_formalize(), but won't simplify if the variant has a source/line location attached to * it, in order not to lose context */ if (!v) @@ -270,7 +280,7 @@ static JsonVariant *json_variant_conservative_normalize(JsonVariant *v) { if (v->source || v->line > 0 || v->column > 0) return v; - return json_variant_normalize(v); + return json_variant_formalize(v); } static int json_variant_new(JsonVariant **ret, JsonVariantType type, size_t space) { @@ -402,6 +412,20 @@ int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n) { return 0; } +int json_variant_new_base64(JsonVariant **ret, const void *p, size_t n) { + _cleanup_free_ char *s = NULL; + ssize_t k; + + assert_return(ret, -EINVAL); + assert_return(n == 0 || p, -EINVAL); + + k = base64mem(p, n, &s); + if (k < 0) + return k; + + return json_variant_new_stringn(ret, s, k); +} + static void json_variant_set(JsonVariant *a, JsonVariant *b) { assert(a); @@ -448,7 +472,7 @@ static void json_variant_set(JsonVariant *a, JsonVariant *b) { case JSON_VARIANT_ARRAY: case JSON_VARIANT_OBJECT: a->is_reference = true; - a->reference = json_variant_ref(json_variant_conservative_normalize(b)); + a->reference = json_variant_ref(json_variant_conservative_formalize(b)); break; case JSON_VARIANT_NULL: @@ -473,6 +497,7 @@ static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) { int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + bool normalized = true; assert_return(ret, -EINVAL); if (n == 0) { @@ -508,8 +533,13 @@ int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) { json_variant_set(w, c); json_variant_copy_source(w, c); + + if (!json_variant_is_normalized(c)) + normalized = false; } + v->normalized = normalized; + *ret = TAKE_PTR(v); return 0; } @@ -547,6 +577,8 @@ int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n) { }; } + v->normalized = true; + *ret = v; return 0; } @@ -598,12 +630,16 @@ int json_variant_new_array_strv(JsonVariant **ret, char **l) { memcpy(w->string, l[v->n_elements], k+1); } + v->normalized = true; + *ret = TAKE_PTR(v); return 0; } int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + const char *prev = NULL; + bool sorted = true, normalized = true; assert_return(ret, -EINVAL); if (n == 0) { @@ -627,9 +663,20 @@ int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) { *c = array[v->n_elements]; uint16_t d; - if ((v->n_elements & 1) == 0 && - !json_variant_is_string(c)) - return -EINVAL; /* Every second one needs to be a string, as it is the key name */ + if ((v->n_elements & 1) == 0) { + const char *k; + + if (!json_variant_is_string(c)) + return -EINVAL; /* Every second one needs to be a string, as it is the key name */ + + assert_se(k = json_variant_string(c)); + + if (prev && strcmp(k, prev) <= 0) + sorted = normalized = false; + + prev = k; + } else if (!json_variant_is_normalized(c)) + normalized = false; d = json_variant_depth(c); if (d >= DEPTH_MAX) /* Refuse too deep nesting */ @@ -646,11 +693,53 @@ int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) { json_variant_copy_source(w, c); } + v->normalized = normalized; + v->sorted = sorted; + *ret = TAKE_PTR(v); return 0; } -static void json_variant_free_inner(JsonVariant *v) { +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)) @@ -658,7 +747,12 @@ static void json_variant_free_inner(JsonVariant *v) { 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; } @@ -667,8 +761,11 @@ static void json_variant_free_inner(JsonVariant *v) { size_t i; for (i = 0; i < v->n_elements; i++) - json_variant_free_inner(v + 1 + 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) { @@ -700,7 +797,7 @@ JsonVariant *json_variant_unref(JsonVariant *v) { v->n_ref--; if (v->n_ref == 0) { - json_variant_free_inner(v); + json_variant_free_inner(v, false); free(v); } } @@ -946,6 +1043,19 @@ mismatch: 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) @@ -1068,7 +1178,7 @@ JsonVariant *json_variant_by_index(JsonVariant *v, size_t idx) { if (idx >= v->n_elements) return NULL; - return json_variant_conservative_normalize(v + 1 + idx); + return json_variant_conservative_formalize(v + 1 + idx); mismatch: log_debug("Element in non-array/non-object JSON variant requested by index, returning NULL."); @@ -1091,6 +1201,37 @@ JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVaria if (v->is_reference) return json_variant_by_key(v->reference, key); + if (v->sorted) { + size_t a = 0, b = v->n_elements/2; + + /* If the variant is sorted we can use bisection to find the entry we need in O(log(n)) time */ + + while (b > a) { + JsonVariant *p; + const char *f; + int c; + + i = (a + b) / 2; + p = json_variant_dereference(v + 1 + i*2); + + assert_se(f = json_variant_string(p)); + + c = strcmp(key, f); + if (c == 0) { + if (ret_key) + *ret_key = json_variant_conservative_formalize(v + 1 + i*2); + + return json_variant_conservative_formalize(v + 1 + i*2 + 1); + } else if (c < 0) + b = i; + else + a = i + 1; + } + + goto not_found; + } + + /* The variant is not sorted, hence search for the field linearly */ for (i = 0; i < v->n_elements; i += 2) { JsonVariant *p; @@ -1102,9 +1243,9 @@ JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVaria if (streq(json_variant_string(p), key)) { if (ret_key) - *ret_key = json_variant_conservative_normalize(v + 1 + i); + *ret_key = json_variant_conservative_formalize(v + 1 + i); - return json_variant_conservative_normalize(v + 1 + i + 1); + return json_variant_conservative_formalize(v + 1 + i + 1); } } @@ -1129,8 +1270,8 @@ JsonVariant *json_variant_by_key(JsonVariant *v, const char *key) { bool json_variant_equal(JsonVariant *a, JsonVariant *b) { JsonVariantType t; - a = json_variant_normalize(a); - b = json_variant_normalize(b); + a = json_variant_formalize(a); + b = json_variant_formalize(b); if (a == b) return true; @@ -1229,6 +1370,26 @@ bool json_variant_equal(JsonVariant *a, JsonVariant *b) { } } +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 + * destoryed. 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; +} + int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column) { assert_return(v, -EINVAL); @@ -1596,6 +1757,9 @@ void json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const cha 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) @@ -1607,6 +1771,341 @@ void json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const cha fputc('\n', f); if (flags & JSON_FORMAT_SSE) fputc('\n', f); /* In case of SSE add a second newline */ + + if (flags & JSON_FORMAT_FLUSH) + fflush(f); +} + +int json_variant_filter(JsonVariant **v, char **to_remove) { + _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_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_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_merge(JsonVariant **v, JsonVariant *m) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + _cleanup_free_ JsonVariant **array = NULL; + size_t v_elements, m_elements, i, k; + bool v_blank, m_blank; + int r; + + m = json_variant_dereference(m); + + v_blank = json_variant_is_blank_object(*v); + m_blank = json_variant_is_blank_object(m); + + if (!v_blank && !json_variant_is_object(*v)) + return -EINVAL; + if (!m_blank && !json_variant_is_object(m)) + return -EINVAL; + + if (m_blank) + return 0; /* nothing to do */ + + if (v_blank) { + json_variant_unref(*v); + *v = json_variant_ref(m); + return 1; + } + + v_elements = json_variant_elements(*v); + m_elements = json_variant_elements(m); + if (v_elements > SIZE_MAX - m_elements) /* overflow check */ + return -ENOMEM; + + array = new(JsonVariant*, v_elements + m_elements); + if (!array) + return -ENOMEM; + + k = 0; + for (i = 0; i < v_elements; i += 2) { + JsonVariant *u; + + u = json_variant_by_index(*v, i); + if (!json_variant_is_string(u)) + return -EINVAL; + + if (json_variant_by_key(m, json_variant_string(u))) + continue; /* skip if exists in second variant */ + + array[k++] = u; + array[k++] = json_variant_by_index(*v, i + 1); + } + + for (i = 0; i < m_elements; i++) + array[k++] = json_variant_by_index(m, i); + + r = json_variant_new_object(&w, array, k); + if (r < 0) + return r; + + json_variant_unref(*v); + *v = TAKE_PTR(w); + + return 1; +} + +int json_variant_append_array(JsonVariant **v, JsonVariant *element) { + _cleanup_(json_variant_unrefp) JsonVariant *nv = NULL; + bool blank; + int r; + + assert(v); + assert(element); + + + if (!*v || json_variant_is_null(*v)) + blank = true; + else if (!json_variant_is_array(*v)) + return -EINVAL; + else + blank = json_variant_elements(*v) == 0; + + if (blank) + r = json_variant_new_array(&nv, (JsonVariant*[]) { element }, 1); + else { + _cleanup_free_ JsonVariant **array = NULL; + size_t i; + + array = new(JsonVariant*, json_variant_elements(*v) + 1); + if (!array) + return -ENOMEM; + + for (i = 0; i < json_variant_elements(*v); i++) + array[i] = json_variant_by_index(*v, i); + + array[i] = element; + + r = json_variant_new_array(&nv, array, i + 1); + } + + if (r < 0) + return r; + + json_variant_unref(*v); + *v = TAKE_PTR(nv); + + return 0; +} + +int json_variant_strv(JsonVariant *v, char ***ret) { + char **l = NULL; + size_t n, i; + bool sensitive; + int r; + + assert(ret); + + if (!v || json_variant_is_null(v)) { + l = new0(char*, 1); + if (!l) + return -ENOMEM; + + *ret = l; + return 0; + } + + if (!json_variant_is_array(v)) + return -EINVAL; + + sensitive = v->sensitive; + + n = json_variant_elements(v); + l = new(char*, n+1); + if (!l) + return -ENOMEM; + + for (i = 0; i < n; i++) { + JsonVariant *e; + + assert_se(e = json_variant_by_index(v, i)); + sensitive = sensitive || e->sensitive; + + if (!json_variant_is_string(e)) { + l[i] = NULL; + r = -EINVAL; + goto fail; + } + + l[i] = strdup(json_variant_string(e)); + if (!l[i]) { + r = -ENOMEM; + goto fail; + } + } + + l[i] = NULL; + *ret = TAKE_PTR(l); + + return 0; + +fail: + if (sensitive) + strv_free_erase(l); + else + strv_free(l); + + return r; } static int json_variant_copy(JsonVariant **nv, JsonVariant *v) { @@ -1672,7 +2171,7 @@ static int json_variant_copy(JsonVariant **nv, JsonVariant *v) { c->n_ref = 1; c->type = t; c->is_reference = true; - c->reference = json_variant_ref(json_variant_normalize(v)); + c->reference = json_variant_ref(json_variant_formalize(v)); *nv = c; return 0; @@ -2274,6 +2773,7 @@ static void json_stack_release(JsonStack *s) { static int json_parse_internal( const char **input, JsonSource *source, + JsonParseFlags flags, JsonVariant **ret, unsigned *line, unsigned *column, @@ -2592,6 +3092,12 @@ static int json_parse_internal( } 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)) { @@ -2620,15 +3126,15 @@ finish: return r; } -int json_parse(const char *input, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { - return json_parse_internal(&input, NULL, ret, ret_line, ret_column, false); +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, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { - return json_parse_internal(p, NULL, ret, ret_line, ret_column, true); +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(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { +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; @@ -2637,7 +3143,7 @@ int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_ if (f) r = read_full_stream(f, &text, NULL); else if (path) - r = read_full_file(path, &text, NULL); + r = read_full_file_full(dir_fd, path, 0, &text, NULL); else return -EINVAL; if (r < 0) @@ -2650,7 +3156,7 @@ int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_ } p = text; - return json_parse_internal(&p, source, ret, ret_line, ret_column, false); + return json_parse_internal(&p, source, flags, ret, ret_line, ret_column, false); } int json_buildv(JsonVariant **ret, va_list ap) { @@ -2874,6 +3380,36 @@ int json_buildv(JsonVariant **ret, va_list ap) { 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; @@ -2888,7 +3424,7 @@ int json_buildv(JsonVariant **ret, va_list ap) { /* 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, &add, NULL, NULL); + r = json_parse(l, 0, &add, NULL, NULL); if (r < 0) goto finish; } else @@ -2985,6 +3521,36 @@ int json_buildv(JsonVariant **ret, va_list ap) { break; } + case _JSON_BUILD_BASE64: { + const void *p; + size_t n; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + p = va_arg(ap, const void *); + n = va_arg(ap, size_t); + + if (current->n_suppress == 0) { + r = json_variant_new_base64(&add, p, n); + if (r < 0) + goto finish; + } + + n_subtract = 1; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + case _JSON_BUILD_OBJECT_BEGIN: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { @@ -3319,6 +3885,11 @@ int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchF assert(variant); assert(b); + if (json_variant_is_null(variant)) { + *b = -1; + return 0; + } + if (!json_variant_is_boolean(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a boolean.", strna(name)); @@ -3399,6 +3970,9 @@ int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFla if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + if ((flags & JSON_SAFE) && !string_is_safe(json_variant_string(variant))) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name)); + r = free_and_strdup(s, json_variant_string(variant)); if (r < 0) return json_log(variant, flags, r, "Failed to allocate string: %m"); @@ -3406,6 +3980,27 @@ int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFla 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; @@ -3420,6 +4015,19 @@ int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags 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)); @@ -3427,6 +4035,9 @@ int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags if (!json_variant_is_string(e)) return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string."); + if ((flags & JSON_SAFE) && !string_is_safe(json_variant_string(e))) + return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name)); + r = strv_extend(&l, json_variant_string(e)); if (r < 0) return json_log(e, flags, r, "Failed to append array element: %m"); @@ -3448,6 +4059,227 @@ int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFl return 0; } +int json_dispatch_uid_gid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + uid_t *uid = userdata; + uintmax_t k; + + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); + assert_cc(sizeof(gid_t) == sizeof(uint32_t)); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + assert_cc(((uid_t) -1 < (uid_t) 0) == ((gid_t) -1 < (gid_t) 0)); +#pragma GCC diagnostic pop + + 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)) + 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; + if (!n->sorted) /* Check if this worked. This will fail if there are multiple identical keys used. */ + return -ENOTUNIQ; + + json_variant_unref(*v); + *v = n; + + return 1; +} + +int json_variant_normalize(JsonVariant **v) { + _cleanup_free_ JsonVariant **a = NULL; + JsonVariant *n = NULL; + size_t i, j, m; + int r; + + assert(v); + + if (json_variant_is_normalized(*v)) + return 0; + + if (!json_variant_is_object(*v) && !json_variant_is_array(*v)) + return -EMEDIUMTYPE; + + /* Sorts the key/value pairs in an object variant anywhere down the tree in the specified variant */ + + m = json_variant_elements(*v); + a = new(JsonVariant*, m); + if (!a) + return -ENOMEM; + + for (i = 0; i < m; i++) { + a[i] = json_variant_ref(json_variant_by_index(*v, i)); + + r = json_variant_normalize(a + i); + if (r < 0) + goto finish; + } + + qsort(a, m/2, sizeof(JsonVariant*)*2, json_cmp_strings); + + if (json_variant_is_object(*v)) + r = json_variant_new_object(&n, a, m); + else { + assert(json_variant_is_array(*v)); + r = json_variant_new_array(&n, a, m); + } + if (r < 0) + goto finish; + if (!n->normalized) { /* Let's see if normalization worked. It will fail if there are multiple + * identical keys used in the same object anywhere, or if there are floating + * point numbers used (see below) */ + r = -ENOTUNIQ; + goto finish; + } + + json_variant_unref(*v); + *v = n; + + r = 1; + +finish: + for (j = 0; j < i; j++) + json_variant_unref(a[j]); + + return r; +} + +bool json_variant_is_normalized(JsonVariant *v) { + + /* For now, let's consider anything containing numbers not expressible as integers as + * non-normalized. That's because we cannot sensibly compare them due to accuracy issues, nor even + * store them if they are too large. */ + if (json_variant_is_real(v) && !json_variant_is_integer(v) && !json_variant_is_unsigned(v)) + return false; + + /* The concept only applies to variants that include other variants, i.e. objects and arrays. All + * others are normalized anyway. */ + if (!json_variant_is_object(v) && !json_variant_is_array(v)) + return true; + + /* Empty objects/arrays don't include any other variant, hence are always normalized too */ + if (json_variant_elements(v) == 0) + return true; + + return v->normalized; /* For everything else there's an explicit boolean we maintain */ +} + +bool json_variant_is_sorted(JsonVariant *v) { + + /* Returns true if all key/value pairs of an object are properly sorted. Note that this only applies + * to objects, not arrays. */ + + if (!json_variant_is_object(v)) + return true; + if (json_variant_elements(v) <= 1) + return true; + + return v->sorted; +} + +int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size) { + + if (!json_variant_is_string(v)) + return -EINVAL; + + return unbase64mem(json_variant_string(v), (size_t) -1, ret, ret_size); +} + static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = { [JSON_VARIANT_STRING] = "string", [JSON_VARIANT_INTEGER] = "integer", diff --git a/src/shared/json.h b/src/shared/json.h index 1f9c620ebb..b9b300a1d2 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once +#include <fcntl.h> #include <stdbool.h> #include <stddef.h> #include <stdint.h> @@ -54,6 +55,7 @@ typedef enum JsonVariantType { } JsonVariantType; int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n); +int json_variant_new_base64(JsonVariant **ret, const void *p, size_t n); int json_variant_new_integer(JsonVariant **ret, intmax_t i); int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u); int json_variant_new_real(JsonVariant **ret, long double d); @@ -120,6 +122,10 @@ static inline bool json_variant_is_null(JsonVariant *v) { } bool json_variant_is_negative(JsonVariant *v); +bool json_variant_is_blank_object(JsonVariant *v); +bool json_variant_is_blank_array(JsonVariant *v); +bool json_variant_is_normalized(JsonVariant *v); +bool json_variant_is_sorted(JsonVariant *v); size_t json_variant_elements(JsonVariant *v); JsonVariant *json_variant_by_index(JsonVariant *v, size_t index); @@ -128,6 +134,8 @@ JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVaria bool json_variant_equal(JsonVariant *a, JsonVariant *b); +void json_variant_sensitive(JsonVariant *v); + struct json_variant_foreach_state { JsonVariant *variant; size_t idx; @@ -153,21 +161,48 @@ struct json_variant_foreach_state { int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column); typedef enum JsonFormatFlags { - JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */ - JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */ - JSON_FORMAT_COLOR = 1 << 2, /* insert ANSI color sequences */ - JSON_FORMAT_COLOR_AUTO = 1 << 3, /* insert ANSI color sequences if colors_enabled() says so */ - JSON_FORMAT_SOURCE = 1 << 4, /* prefix with source filename/line/column */ - JSON_FORMAT_SSE = 1 << 5, /* prefix/suffix with W3C server-sent events */ - JSON_FORMAT_SEQ = 1 << 6, /* prefix/suffix with RFC 7464 application/json-seq */ + JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */ + JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */ + JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */ + JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */ + JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */ + JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */ + JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */ + JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */ + JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */ } JsonFormatFlags; int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret); void json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const char *prefix); -int json_parse(const char *string, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column); -int json_parse_continue(const char **p, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column); -int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column); +int json_variant_filter(JsonVariant **v, char **to_remove); + +int json_variant_set_field(JsonVariant **v, const char *field, JsonVariant *value); +int json_variant_set_field_string(JsonVariant **v, const char *field, const char *value); +int json_variant_set_field_integer(JsonVariant **v, const char *field, intmax_t value); +int json_variant_set_field_unsigned(JsonVariant **v, const char *field, uintmax_t value); +int json_variant_set_field_boolean(JsonVariant **v, const char *field, bool b); + +int json_variant_append_array(JsonVariant **v, JsonVariant *element); + +int json_variant_merge(JsonVariant **v, JsonVariant *m); + +int json_variant_strv(JsonVariant *v, char ***ret); + +int json_variant_sort(JsonVariant **v); +int json_variant_normalize(JsonVariant **v); + +typedef enum JsonParseFlags { + JSON_PARSE_SENSITIVE = 1 << 0, /* mark variant as "sensitive", i.e. something containing secret key material or such */ +} JsonParseFlags; + +int json_parse(const char *string, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column); +int json_parse_continue(const char **p, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column); +int json_parse_file_at(FILE *f, int dir_fd, const char *path, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column); + +static inline int json_parse_file(FILE *f, const char *path, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { + return json_parse_file_at(f, AT_FDCWD, path, flags, ret, ret_line, ret_column); +} enum { _JSON_BUILD_STRING, @@ -183,8 +218,10 @@ enum { _JSON_BUILD_PAIR_CONDITION, _JSON_BUILD_NULL, _JSON_BUILD_VARIANT, + _JSON_BUILD_VARIANT_ARRAY, _JSON_BUILD_LITERAL, _JSON_BUILD_STRV, + _JSON_BUILD_BASE64, _JSON_BUILD_MAX, }; @@ -194,13 +231,17 @@ enum { #define JSON_BUILD_REAL(d) _JSON_BUILD_REAL, ({ long double _x = d; _x; }) #define JSON_BUILD_BOOLEAN(b) _JSON_BUILD_BOOLEAN, ({ bool _x = b; _x; }) #define JSON_BUILD_ARRAY(...) _JSON_BUILD_ARRAY_BEGIN, __VA_ARGS__, _JSON_BUILD_ARRAY_END +#define JSON_BUILD_EMPTY_ARRAY _JSON_BUILD_ARRAY_BEGIN, _JSON_BUILD_ARRAY_END #define JSON_BUILD_OBJECT(...) _JSON_BUILD_OBJECT_BEGIN, __VA_ARGS__, _JSON_BUILD_OBJECT_END +#define JSON_BUILD_EMPTY_OBJECT _JSON_BUILD_OBJECT_BEGIN, _JSON_BUILD_OBJECT_END #define JSON_BUILD_PAIR(n, ...) _JSON_BUILD_PAIR, ({ const char *_x = n; _x; }), __VA_ARGS__ #define JSON_BUILD_PAIR_CONDITION(c, n, ...) _JSON_BUILD_PAIR_CONDITION, ({ bool _x = c; _x; }), ({ const char *_x = n; _x; }), __VA_ARGS__ #define JSON_BUILD_NULL _JSON_BUILD_NULL #define JSON_BUILD_VARIANT(v) _JSON_BUILD_VARIANT, ({ JsonVariant *_x = v; _x; }) +#define JSON_BUILD_VARIANT_ARRAY(v, n) _JSON_BUILD_VARIANT_ARRAY, ({ JsonVariant **_x = v; _x; }), ({ size_t _y = n; _y; }) #define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, ({ const char *_x = l; _x; }) #define JSON_BUILD_STRV(l) _JSON_BUILD_STRV, ({ char **_x = l; _x; }) +#define JSON_BUILD_BASE64(p, n) _JSON_BUILD_BASE64, ({ const void *_x = p; _x; }), ({ size_t _y = n; _y; }) int json_build(JsonVariant **ret, ...); int json_buildv(JsonVariant **ret, va_list ap); @@ -213,10 +254,11 @@ typedef enum JsonDispatchFlags { JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this property? */ JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */ JSON_LOG = 1 << 2, /* Should the parser log about errors? */ + JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */ /* The following two may be passed into log_json() in addition to the three above */ - JSON_DEBUG = 1 << 3, /* Indicates that this log message is a debug message */ - JSON_WARNING = 1 << 4, /* Indicates that this log message is a warning message */ + JSON_DEBUG = 1 << 4, /* Indicates that this log message is a debug message */ + JSON_WARNING = 1 << 5, /* Indicates that this log message is a warning message */ } JsonDispatchFlags; typedef int (*JsonDispatchCallback)(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); @@ -232,6 +274,7 @@ typedef struct JsonDispatch { int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata); int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_const_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); @@ -240,6 +283,10 @@ int json_dispatch_integer(const char *name, JsonVariant *variant, JsonDispatchFl int json_dispatch_unsigned(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_uid_gid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_unsupported(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); assert_cc(sizeof(uintmax_t) == sizeof(uint64_t)); #define json_dispatch_uint64 json_dispatch_unsigned @@ -275,6 +322,9 @@ int json_log_internal(JsonVariant *variant, int level, int error, const char *fi : -ERRNO_VALUE(_e); \ }) +#define json_log_oom(variant, flags) \ + json_log(variant, flags, SYNTHETIC_ERRNO(ENOMEM), "Out of memory.") + #define JSON_VARIANT_STRING_CONST(x) _JSON_VARIANT_STRING_CONST(UNIQ, (x)) #define _JSON_VARIANT_STRING_CONST(xq, x) \ @@ -284,5 +334,7 @@ int json_log_internal(JsonVariant *variant, int level, int error, const char *fi (JsonVariant*) ((uintptr_t) UNIQ_T(json_string_const, xq) + 1); \ }) +int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size); + const char *json_variant_type_to_string(JsonVariantType t); JsonVariantType json_variant_type_from_string(const char *s); diff --git a/src/shared/varlink.c b/src/shared/varlink.c index ee4fb9e843..e1d956e649 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -577,7 +577,7 @@ static int varlink_parse_message(Varlink *v) { varlink_log(v, "New incoming message: %s", begin); - r = json_parse(begin, &v->current, NULL, NULL); + r = json_parse(begin, 0, &v->current, NULL, NULL); if (r < 0) return r; diff --git a/src/test/test-json.c b/src/test/test-json.c index a6613043b9..c72cd74274 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -82,7 +82,7 @@ static void test_variant(const char *data, Test test) { _cleanup_free_ char *s = NULL; int r; - r = json_parse(data, &v, NULL, NULL); + r = json_parse(data, 0, &v, NULL, NULL); assert_se(r == 0); assert_se(v); @@ -93,7 +93,7 @@ static void test_variant(const char *data, Test test) { log_info("formatted normally: %s\n", s); - r = json_parse(data, &w, NULL, NULL); + r = json_parse(data, JSON_PARSE_SENSITIVE, &w, NULL, NULL); assert_se(r == 0); assert_se(w); assert_se(json_variant_has_type(v, json_variant_type(w))); @@ -110,7 +110,7 @@ static void test_variant(const char *data, Test test) { log_info("formatted prettily:\n%s", s); - r = json_parse(data, &w, NULL, NULL); + r = json_parse(data, 0, &w, NULL, NULL); assert_se(r == 0); assert_se(w); @@ -302,7 +302,7 @@ static void test_build(void) { assert_se(json_variant_format(a, 0, &s) >= 0); log_info("GOT: %s\n", s); - assert_se(json_parse(s, &b, NULL, NULL) >= 0); + assert_se(json_parse(s, 0, &b, NULL, NULL) >= 0); assert_se(json_variant_equal(a, b)); a = json_variant_unref(a); @@ -313,7 +313,7 @@ static void test_build(void) { s = mfree(s); assert_se(json_variant_format(a, 0, &s) >= 0); log_info("GOT: %s\n", s); - assert_se(json_parse(s, &b, NULL, NULL) >= 0); + assert_se(json_parse(s, 0, &b, NULL, NULL) >= 0); assert_se(json_variant_format(b, 0, &t) >= 0); log_info("GOT: %s\n", t); @@ -365,7 +365,7 @@ static void test_source(void) { assert_se(f = fmemopen_unlocked((void*) data, strlen(data), "r")); - assert_se(json_parse_file(f, "waldo", &v, NULL, NULL) >= 0); + assert_se(json_parse_file(f, "waldo", 0, &v, NULL, NULL) >= 0); printf("--- non-pretty begin ---\n"); json_variant_dump(v, 0, stdout, NULL); @@ -415,6 +415,93 @@ static void test_depth(void) { fputs("\n", stdout); } +static void test_normalize(void) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + _cleanup_free_ char *t = NULL; + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("b", JSON_BUILD_STRING("x")), + JSON_BUILD_PAIR("c", JSON_BUILD_STRING("y")), + JSON_BUILD_PAIR("a", JSON_BUILD_STRING("z")))) >= 0); + + assert_se(!json_variant_is_sorted(v)); + assert_se(!json_variant_is_normalized(v)); + + assert_se(json_variant_format(v, 0, &t) >= 0); + assert_se(streq(t, "{\"b\":\"x\",\"c\":\"y\",\"a\":\"z\"}")); + t = mfree(t); + + assert_se(json_build(&w, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("bar", JSON_BUILD_STRING("zzz")), + JSON_BUILD_PAIR("foo", JSON_BUILD_VARIANT(v)))) >= 0); + + assert_se(json_variant_is_sorted(w)); + assert_se(!json_variant_is_normalized(w)); + + assert_se(json_variant_format(w, 0, &t) >= 0); + assert_se(streq(t, "{\"bar\":\"zzz\",\"foo\":{\"b\":\"x\",\"c\":\"y\",\"a\":\"z\"}}")); + t = mfree(t); + + assert_se(json_variant_sort(&v) >= 0); + assert_se(json_variant_is_sorted(v)); + assert_se(json_variant_is_normalized(v)); + + assert_se(json_variant_format(v, 0, &t) >= 0); + assert_se(streq(t, "{\"a\":\"z\",\"b\":\"x\",\"c\":\"y\"}")); + t = mfree(t); + + assert_se(json_variant_normalize(&w) >= 0); + assert_se(json_variant_is_sorted(w)); + assert_se(json_variant_is_normalized(w)); + + assert_se(json_variant_format(w, 0, &t) >= 0); + assert_se(streq(t, "{\"bar\":\"zzz\",\"foo\":{\"a\":\"z\",\"b\":\"x\",\"c\":\"y\"}}")); + t = mfree(t); +} + +static void test_bisect(void) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + char c; + + /* Tests the bisection logic in json_variant_by_key() */ + + for (c = 'z'; c >= 'a'; c--) { + + if ((c % 3) == 0) + continue; + + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + assert_se(json_variant_new_stringn(&w, (char[4]) { '<', c, c, '>' }, 4) >= 0); + assert_se(json_variant_set_field(&v, (char[2]) { c, 0 }, w) >= 0); + } + + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + assert_se(!json_variant_is_sorted(v)); + assert_se(!json_variant_is_normalized(v)); + assert_se(json_variant_normalize(&v) >= 0); + assert_se(json_variant_is_sorted(v)); + assert_se(json_variant_is_normalized(v)); + + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + for (c = 'a'; c <= 'z'; c++) { + JsonVariant *k; + const char *z; + + k = json_variant_by_key(v, (char[2]) { c, 0 }); + assert_se(!k == ((c % 3) == 0)); + + if (!k) + continue; + + assert_se(json_variant_is_string(k)); + + z = (char[5]){ '<', c, c, '>', 0}; + assert_se(streq(json_variant_string(k), z)); + } +} + int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); @@ -462,10 +549,11 @@ int main(int argc, char *argv[]) { test_variant("[ 0, -0, 0.0, -0.0, 0.000, -0.000, 0e0, -0e0, 0e+0, -0e-0, 0e-0, -0e000, 0e+000 ]", test_zeroes); test_build(); - test_source(); - test_depth(); + test_normalize(); + test_bisect(); + return 0; } |