diff --git a/docs/mkdocs/docs/api/basic_json/from_bon8.md b/docs/mkdocs/docs/api/basic_json/from_bon8.md index c3cf9f18a..5387037ce 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bon8.md +++ b/docs/mkdocs/docs/api/basic_json/from_bon8.md @@ -69,8 +69,10 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va ## Exceptions -- Throws [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or - the end of the file was not reached when `strict` was set to true +- Throws [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely, + if the end of the file was not reached when `strict` was set to true, or if a string at the end of the value is not + terminated by 0xFF although a byte follows it (also when `strict` is false: the byte that ends the string is + taken from the input, so the next value would be lost) - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs, for instance an invalid byte, a string that is not valid UTF-8, or an object key that is not a string diff --git a/docs/mkdocs/docs/features/binary_formats/bon8.md b/docs/mkdocs/docs/features/binary_formats/bon8.md index c6db009db..f5d31f8c6 100644 --- a/docs/mkdocs/docs/features/binary_formats/bon8.md +++ b/docs/mkdocs/docs/features/binary_formats/bon8.md @@ -181,7 +181,10 @@ of the message are reported as [parse_error.110](../../home/exceptions.md#jsonex arrays and objects with up to four elements that are terminated by 0xFE, unsorted object keys, or a 0xFF after a string that would also end without it, are accepted. A second 0xFF is not a terminator but an empty string. - Strings must be valid UTF-8, and a string at the very end of a message must be terminated by 0xFF. + Strings must be valid UTF-8, and a string at the very end of a message must be terminated by 0xFF. This also + holds when `strict` is false and more data follows the value: the byte that would end the string belongs to the + next value, and reading it would remove it from the input, so the missing 0xFF is reported as + [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110). Duplicate object keys are accepted; the last value wins (`ordered_json` keeps the position of the first occurrence). The specification does not say how to treat them; the HikoGUI reference decoder keeps the first value. diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 81a3ebd42..3df2867a1 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -177,6 +177,17 @@ class binary_reader JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } + // BON8: a string ends at the first byte that cannot continue it. If + // that byte follows the value, the reader has already taken it from + // the input and holds it in bon8_pushback. Without the end-of-input + // check below it would be dropped, and the next read would start + // behind it, so report the missing 0xFF instead. + if (result && !strict && input_format == input_format_t::bon8 && bon8_pushback_size != 0) + { + get_bon8(); + return last_byte_error(exception_id::unexpected_end_of_input, "string at the end of the value is not terminated by 0xFF", "value", ""); + } + // strict mode: next byte must be EOF if (result && strict) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 102eb94ad..ee382adb5 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18568,6 +18568,17 @@ class binary_reader JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } + // BON8: a string ends at the first byte that cannot continue it. If + // that byte follows the value, the reader has already taken it from + // the input and holds it in bon8_pushback. Without the end-of-input + // check below it would be dropped, and the next read would start + // behind it, so report the missing 0xFF instead. + if (result && !strict && input_format == input_format_t::bon8 && bon8_pushback_size != 0) + { + get_bon8(); + return last_byte_error(exception_id::unexpected_end_of_input, "string at the end of the value is not terminated by 0xFF", "value", ""); + } + // strict mode: next byte must be EOF if (result && strict) { diff --git a/tests/src/unit-bon8.cpp b/tests/src/unit-bon8.cpp index 85c9fa821..36336fa0a 100644 --- a/tests/src/unit-bon8.cpp +++ b/tests/src/unit-bon8.cpp @@ -608,14 +608,26 @@ TEST_CASE("BON8") CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 value: expected end of input; last byte: 0x90", json::parse_error&); CHECK(json::from_bon8(vec, true, false).is_discarded()); - // the byte after a string that ends a container + // the byte after a string that ends a container: without strict, the + // missing 0xFF is reported, as the byte has been taken from the input const bytes vec2 = {0x81, 'a', 0x90}; - CHECK(json::from_bon8(vec2, false) == json({"a"})); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec2, false), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: string at the end of the value is not terminated by 0xFF; last byte: 0x90", json::parse_error&); + CHECK(json::from_bon8(vec2, false, false).is_discarded()); CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec2), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: expected end of input; last byte: 0x90", json::parse_error&); const bytes vec3 = {0x81, 'a', 0xC2, 0x00}; - CHECK(json::from_bon8(vec3, false) == json({"a"})); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec3, false), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: string at the end of the value is not terminated by 0xFF; last byte: 0xC2", json::parse_error&); CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec3), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: expected end of input; last byte: 0xC2", json::parse_error&); + + // with a terminated string, strict = false leaves the next value in the stream + std::istringstream terminated(std::string("\x61\xFF\x90", 3)); + CHECK(json::from_bon8(terminated, false) == "a"); + CHECK(terminated.tellg() == 2); + CHECK(json::from_bon8(terminated, false) == 0); + + // without the terminator, the next value would be lost + std::istringstream unterminated(std::string("\x61\x90", 2)); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(unterminated, false), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 value: string at the end of the value is not terminated by 0xFF; last byte: 0x90", json::parse_error&); } }