diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 397683744..f5d209b88 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -1988,7 +1988,11 @@ class binary_reader { if (get_char) { - get(); // TODO(niels): may we ignore N here? + // no get_ignore_noop() here: the byte read next must be a string + // length type specification, and a no-op ('N') is not valid in + // that position. No-ops at positions where a value may appear are + // already consumed by the callers via get_ignore_noop(). + get(); } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index cca1b3666..c1b178038 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -12586,7 +12586,11 @@ class binary_reader { if (get_char) { - get(); // TODO(niels): may we ignore N here? + // no get_ignore_noop() here: the byte read next must be a string + // length type specification, and a no-op ('N') is not valid in + // that position. No-ops at positions where a value may appear are + // already consumed by the callers via get_ignore_noop(). + get(); } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index d5668833d..e1e327563 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -1713,6 +1713,44 @@ TEST_CASE("UBJSON") CHECK(json::to_ubjson(json::from_ubjson(s_L)) == s_i); } + SECTION("no-op markers") + { + // A no-op ('N') is valid wherever a value may start; it is consumed + // by get_ignore_noop() before the value is read. It is not valid + // where a string length type specification is expected. + + SECTION("accepted where a value may start") + { + // at top level, also repeated + CHECK(json::from_ubjson(std::vector({'N', 'i', 1})) == json(1)); + CHECK(json::from_ubjson(std::vector({'N', 'N', 'N', 'i', 1})) == json(1)); + + // inside an array of unknown size, before and after an element + CHECK(json::from_ubjson(std::vector({'[', 'N', 'i', 1, ']'})) == json({1})); + CHECK(json::from_ubjson(std::vector({'[', 'i', 1, 'N', ']'})) == json({1})); + + // inside an object of unknown size: before a key, between key + // and value, and before the closing '}' + CHECK(json::from_ubjson(std::vector({'{', 'N', 'U', 1, 'a', 'i', 1, '}'})) == json({{"a", 1}})); + CHECK(json::from_ubjson(std::vector({'{', 'U', 1, 'a', 'N', 'i', 1, '}'})) == json({{"a", 1}})); + CHECK(json::from_ubjson(std::vector({'{', 'U', 1, 'a', 'i', 1, 'N', '}'})) == json({{"a", 1}})); + } + + SECTION("rejected where a length type specification is expected") + { + json _; + + // after the 'S' marker of a string value + std::vector const v_S = {'S', 'N', 'U', 1, 'a'}; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v_S), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x4E", json::parse_error&); + + // as the key length of an object with a known size, where + // no-ops are not permitted in the first place + std::vector const v_key = {'{', '#', 'i', 1, 'N', 'U', 1, 'a', 'i', 1}; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v_key), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x4E", json::parse_error&); + } + } + SECTION("number") { SECTION("float")