diff --git a/src/shared/json.c b/src/shared/json.c index 5f897a7df9..06c9e850ea 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -4658,8 +4658,13 @@ int json_dispatch_int64(const char *name, JsonVariant *variant, JsonDispatchFlag assert(variant); + /* Also accept numbers formatted as string, to increase compatibility with less capable JSON + * implementations that cannot do 64bit integers. */ + if (json_variant_is_string(variant) && safe_atoi64(json_variant_string(variant), i) >= 0) + return 0; + if (!json_variant_is_integer(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer, nor one formatted as decimal string.", strna(name)); *i = json_variant_integer(variant); return 0; @@ -4670,8 +4675,16 @@ int json_dispatch_uint64(const char *name, JsonVariant *variant, JsonDispatchFla assert(variant); + /* Since 64bit values (in particular unsigned ones) in JSON are problematic, let's also accept them + * formatted as strings. If this is not desired make sure to set the .type field in JsonDispatch to + * JSON_UNSIGNED rather than _JSON_VARIANT_TYPE_INVALID, so that json_dispatch() already filters out + * the non-matching type. */ + + if (json_variant_is_string(variant) && safe_atou64(json_variant_string(variant), u) >= 0) + return 0; + if (!json_variant_is_unsigned(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer.", strna(name)); + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer, nor one formatted as decimal string.", strna(name)); *u = json_variant_unsigned(variant); return 0; @@ -4679,61 +4692,73 @@ int json_dispatch_uint64(const char *name, JsonVariant *variant, JsonDispatchFla int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { uint32_t *u = ASSERT_PTR(userdata); + uint64_t u64; + int r; assert(variant); - if (!json_variant_is_unsigned(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer.", strna(name)); + r = json_dispatch_uint64(name, variant, flags, &u64); + if (r < 0) + return r; - if (json_variant_unsigned(variant) > UINT32_MAX) + if (u64 > 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); + *u = (uint32_t) u64; return 0; } int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { int32_t *i = ASSERT_PTR(userdata); + int64_t i64; + int r; assert(variant); - if (!json_variant_is_integer(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); + r = json_dispatch_int64(name, variant, flags, &i64); + if (r < 0) + return r; - if (json_variant_integer(variant) < INT32_MIN || json_variant_integer(variant) > INT32_MAX) + if (i64 < INT32_MIN || i64 > 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); + *i = (int32_t) i64; return 0; } int json_dispatch_int16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { int16_t *i = ASSERT_PTR(userdata); + int64_t i64; + int r; assert(variant); - if (!json_variant_is_integer(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); + r = json_dispatch_int64(name, variant, flags, &i64); + if (r < 0) + return r; - if (json_variant_integer(variant) < INT16_MIN || json_variant_integer(variant) > INT16_MAX) + if (i64 < INT16_MIN || i64 > INT16_MAX) return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); - *i = (int16_t) json_variant_integer(variant); + *i = (int16_t) i64; return 0; } int json_dispatch_uint16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { - uint16_t *i = ASSERT_PTR(userdata); + uint16_t *u = ASSERT_PTR(userdata); + uint64_t u64; + int r; assert(variant); - if (!json_variant_is_unsigned(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer.", strna(name)); + r = json_dispatch_uint64(name, variant, flags, &u64); + if (r < 0) + return r; - if (json_variant_unsigned(variant) > UINT16_MAX) + if (u64 > UINT16_MAX) return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); - *i = (uint16_t) json_variant_unsigned(variant); + *u = (uint16_t) u64; return 0; } diff --git a/src/test/test-json.c b/src/test/test-json.c index 46ef5bfa96..c120a702c6 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -751,4 +751,66 @@ TEST(json_array_append_nodup) { assert_se(json_variant_equal(s, nd)); } +TEST(json_dispatch) { + struct foobar { + uint64_t a, b; + int64_t c, d; + uint32_t e, f; + int32_t g, h; + uint16_t i, j; + int16_t k, l; + } foobar = {}; + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("a", JSON_BUILD_UNSIGNED(UINT64_MAX)), + JSON_BUILD_PAIR("b", JSON_BUILD_STRING("18446744073709551615")), + JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)), + JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")), + JSON_BUILD_PAIR("e", JSON_BUILD_UNSIGNED(UINT32_MAX)), + JSON_BUILD_PAIR("f", JSON_BUILD_STRING("4294967295")), + JSON_BUILD_PAIR("g", JSON_BUILD_INTEGER(INT32_MIN)), + JSON_BUILD_PAIR("h", JSON_BUILD_STRING("-2147483648")), + JSON_BUILD_PAIR("i", JSON_BUILD_UNSIGNED(UINT16_MAX)), + JSON_BUILD_PAIR("j", JSON_BUILD_STRING("65535")), + JSON_BUILD_PAIR("k", JSON_BUILD_INTEGER(INT16_MIN)), + JSON_BUILD_PAIR("l", JSON_BUILD_STRING("-32768")))) >= 0); + + assert_se(json_variant_dump(v, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, stdout, /* prefix= */ NULL) >= 0); + + JsonDispatch table[] = { + { "a", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct foobar, a) }, + { "b", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct foobar, b) }, + { "c", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int64, offsetof(struct foobar, c) }, + { "d", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int64, offsetof(struct foobar, d) }, + { "e", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(struct foobar, e) }, + { "f", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(struct foobar, f) }, + { "g", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int32, offsetof(struct foobar, g) }, + { "h", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int32, offsetof(struct foobar, h) }, + { "i", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(struct foobar, i) }, + { "j", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(struct foobar, j) }, + { "k", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int16, offsetof(struct foobar, k) }, + { "l", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int16, offsetof(struct foobar, l) }, + {} + }; + + assert_se(json_dispatch(v, table, JSON_LOG, &foobar) >= 0); + + assert_se(foobar.a == UINT64_MAX); + assert_se(foobar.b == UINT64_MAX); + assert_se(foobar.c == INT64_MIN); + assert_se(foobar.d == INT64_MIN); + + assert_se(foobar.e == UINT32_MAX); + assert_se(foobar.f == UINT32_MAX); + assert_se(foobar.g == INT32_MIN); + assert_se(foobar.h == INT32_MIN); + + assert_se(foobar.i == UINT16_MAX); + assert_se(foobar.j == UINT16_MAX); + assert_se(foobar.k == INT16_MIN); + assert_se(foobar.l == INT16_MIN); +} + DEFINE_TEST_MAIN(LOG_DEBUG);