diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 929ac7573..4c8d6ed0f 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -3000,6 +3000,14 @@ class binary_reader return false; } + // validate the type marker up front: otherwise it would only be + // checked when an element is read, and a container with count 0 + // would accept any marker + if (JSON_HEDLEY_UNLIKELY(!is_valid_optimized_type(result.second))) + { + return optimized_type_error(); + } + get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { @@ -4496,6 +4504,44 @@ class binary_reader std::size_t bon8_pushback_size = 0; JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief check whether @a marker is a type marker that may follow '$' + UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C + (BJData's excluded markers are rejected separately). + */ + bool is_valid_optimized_type(const char_int_type marker) const noexcept + { + switch (marker) + { + case 'i': + case 'U': + case 'I': + case 'l': + case 'L': + case 'd': + case 'D': + case 'C': + return true; + case 'Z': + case 'N': + case 'T': + case 'F': + case 'H': + case 'S': + case '[': + case '{': + return input_format != input_format_t::bjdata; + case 'B': + case 'u': + case 'm': + case 'M': + case 'h': + return input_format == input_format_t::bjdata; + default: + return false; + } + } + /*! @brief whether @a marker is excluded from BJData's optimized ND-array types @return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{' diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 67e9e0a05..af92b48d1 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -3134,7 +3134,7 @@ TEST_CASE("BJData") std::vector const v1 = {'[', '#', 'i', 0xF1}; std::vector const v2 = {'[', '$', 'I', '#', 'i', 0xF2}; std::vector const v3 = {'[', '$', 'I', '#', '[', 'i', 0xF4, 'i', 0x02, ']'}; - std::vector const v4 = {'[', '$', 0xF6, '#', 'i', 0xF7}; + std::vector const v4 = {'[', '$', 'l', '#', 'i', 0xF7}; std::vector const v5 = {'[', '$', 'I', '#', '[', 'i', 0xF5, 'i', 0xF1, ']'}; std::vector const v6 = {'[', '#', '[', 'i', 0xF3, 'i', 0x02, ']'}; @@ -3455,7 +3455,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v0, true, false).is_discarded()); std::vector const vi = {'[', '$', '#'}; - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x23 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(vi, true, false).is_discarded()); std::vector const vU = {'[', '$', 'U'}; @@ -4622,3 +4622,51 @@ TEST_CASE("BJData large strings and binaries (chunked reader)") } } } + +TEST_CASE("BJData optimized container type marker is validated for count 0") +{ + // the '$' type marker must be a legal marker even if no element follows + SECTION("invalid marker") + { + // 'Z', 'T', 'F', 'N', 'S', 'H', '[' and '{' are excluded in BJData + for (const char marker : {'x', 'E', 'Z', 'T', 'F', 'N', 'S', 'H', '[', '{'}) + { + for (const char open : {'[', '{'}) + { + for (const std::uint8_t count : {0, 1}) + { + CAPTURE(marker) + CAPTURE(open) + CAPTURE(count) + const std::vector v = {static_cast(open), '$', static_cast(marker), '#', 'i', count}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK(json::from_bjdata(v, true, false).is_discarded()); + } + } + } + } + + SECTION("error message") + { + const std::vector v = {'[', '$', 'x', '#', 'i', 0}; + json _; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x78 is not a permitted optimized array type", json::parse_error&); + } + + SECTION("valid marker") + { + for (const char marker : {'i', 'U', 'I', 'u', 'l', 'm', 'L', 'M', 'h', 'd', 'D', 'C'}) + { + CAPTURE(marker) + const std::vector va = {'[', '$', static_cast(marker), '#', 'i', 0}; + const std::vector vo = {'{', '$', static_cast(marker), '#', 'i', 0}; + CHECK(json::from_bjdata(va) == json::array()); + CHECK(json::from_bjdata(vo) == json::object()); + } + + // an optimized array of bytes is read as a binary value + const std::vector vb = {'[', '$', 'B', '#', 'i', 0}; + CHECK(json::from_bjdata(vb) == json::binary({})); + } +} diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 4cce74dff..db6ac0f3a 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2023,7 +2023,7 @@ TEST_CASE("UBJSON") CHECK(json::from_ubjson(v0, true, false).is_discarded()); std::vector const vi = {'[', '$', '#'}; - CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x23 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_ubjson(vi, true, false).is_discarded()); std::vector const vT = {'[', '$', 'T'}; @@ -3348,3 +3348,46 @@ TEST_CASE("UBJSON large strings (chunked reader)") } } } + +TEST_CASE("UBJSON optimized container type marker is validated for count 0") +{ + // the '$' type marker must be a legal marker even if no element follows + SECTION("invalid marker") + { + for (const char marker : {'x', 'E', 'B', 'u', 'm', 'M', 'h'}) + { + for (const char open : {'[', '{'}) + { + for (const std::uint8_t count : {0, 1}) + { + CAPTURE(marker) + CAPTURE(open) + CAPTURE(count) + const std::vector v = {static_cast(open), '$', static_cast(marker), '#', 'i', count}; + json _; + CHECK_THROWS_AS(_ = json::from_ubjson(v), json::parse_error&); + CHECK(json::from_ubjson(v, true, false).is_discarded()); + } + } + } + } + + SECTION("error message") + { + const std::vector v = {'[', '$', 'x', '#', 'i', 0}; + json _; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x78 is not a permitted optimized array type", json::parse_error&); + } + + SECTION("valid marker") + { + for (const char marker : {'Z', 'N', 'T', 'F', 'i', 'U', 'I', 'l', 'L', 'd', 'D', 'H', 'C', 'S', '[', '{'}) + { + CAPTURE(marker) + const std::vector va = {'[', '$', static_cast(marker), '#', 'i', 0}; + const std::vector vo = {'{', '$', static_cast(marker), '#', 'i', 0}; + CHECK(json::from_ubjson(va) == json::array()); + CHECK(json::from_ubjson(vo) == json::object()); + } + } +}