sd-json: when parsing optionally insist top-level variant is object or array

Typically, the top-level JSON object has to be an object, in any json
document we parse, hence let's add a simple way to enforce that.

Make use of this in various places.

(Note, various other JSON parsers insist on this logic right from the
beginning, but I actually thinking making this insisting optional like
this patch does it is the cleaner approach)
This commit is contained in:
Lennart Poettering
2026-03-20 15:34:56 +01:00
committed by Daan De Meyer
parent bc6380ae07
commit 2c74a91cb8
28 changed files with 405 additions and 53 deletions
+42 -3
View File
@@ -3039,7 +3039,6 @@ static int json_parse_internal(
int r;
assert_return(input, -EINVAL);
assert_return(ret, -EINVAL);
p = *input;
@@ -3111,12 +3110,16 @@ static int json_parse_internal(
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 (n_stack == 1 && FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_ARRAY) && !FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_OBJECT)) {
r = -EINVAL;
goto finish;
}
if (!GREEDY_REALLOC(stack, n_stack+1)) {
r = -ENOMEM;
goto finish;
@@ -3168,6 +3171,11 @@ static int json_parse_internal(
goto finish;
}
if (n_stack == 1 && !FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_ARRAY) && FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_OBJECT)) {
r = -EINVAL;
goto finish;
}
if (!GREEDY_REALLOC(stack, n_stack+1)) {
r = -ENOMEM;
goto finish;
@@ -3217,6 +3225,11 @@ static int json_parse_internal(
goto finish;
}
if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) {
r = -EINVAL;
goto finish;
}
r = sd_json_variant_new_string(&add, string);
if (r < 0)
goto finish;
@@ -3240,6 +3253,11 @@ static int json_parse_internal(
goto finish;
}
if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) {
r = -EINVAL;
goto finish;
}
r = sd_json_variant_new_real(&add, value.real);
if (r < 0)
goto finish;
@@ -3261,6 +3279,11 @@ static int json_parse_internal(
goto finish;
}
if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) {
r = -EINVAL;
goto finish;
}
r = sd_json_variant_new_integer(&add, value.integer);
if (r < 0)
goto finish;
@@ -3282,6 +3305,11 @@ static int json_parse_internal(
goto finish;
}
if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) {
r = -EINVAL;
goto finish;
}
r = sd_json_variant_new_unsigned(&add, value.unsig);
if (r < 0)
goto finish;
@@ -3303,6 +3331,11 @@ static int json_parse_internal(
goto finish;
}
if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) {
r = -EINVAL;
goto finish;
}
r = sd_json_variant_new_boolean(&add, value.boolean);
if (r < 0)
goto finish;
@@ -3324,6 +3357,11 @@ static int json_parse_internal(
goto finish;
}
if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) {
r = -EINVAL;
goto finish;
}
r = sd_json_variant_new_null(&add);
if (r < 0)
goto finish;
@@ -3365,7 +3403,8 @@ done:
assert(n_stack == 1);
assert(stack[0].n_elements == 1);
*ret = sd_json_variant_ref(stack[0].elements[0]);
if (ret)
*ret = sd_json_variant_ref(stack[0].elements[0]);
*input = p;
r = 0;
+2 -2
View File
@@ -1009,14 +1009,14 @@ static int varlink_parse_message(sd_varlink *v) {
sz = e - begin + 1;
r = sd_json_parse(begin, 0, &v->current, NULL, NULL);
r = sd_json_parse(begin, SD_JSON_PARSE_MUST_BE_OBJECT, &v->current, /* reterr_line= */ NULL, /* reterr_column= */ NULL);
if (v->input_sensitive)
explicit_bzero_safe(begin, sz);
if (r < 0) {
/* If we encounter a parse failure flush all data. We cannot possibly recover from this,
* hence drop all buffered data now. */
v->input_buffer_index = v->input_buffer_size = v->input_buffer_unscanned = 0;
return varlink_log_errno(v, r, "Failed to parse JSON: %m");
return varlink_log_errno(v, r, "Failed to parse JSON object: %m");
}
if (v->input_sensitive) {