mirror of
https://github.com/nlohmann/json.git
synced 2026-09-09 17:58:00 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d61ef62fc7 |
@@ -996,23 +996,21 @@ class binary_reader
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR string
|
||||
@brief reads a definite-length CBOR string
|
||||
|
||||
This function first reads starting bytes to determine the expected
|
||||
string length and then copies this number of bytes into a string.
|
||||
Additionally, CBOR's strings with indefinite lengths are supported.
|
||||
Reads everything @ref get_cbor_string accepts except the indefinite-length
|
||||
form, which that function handles itself. The bytes are appended to @a
|
||||
result, so consecutive chunks of an indefinite-length string can be read
|
||||
into the same string.
|
||||
|
||||
@param[out] result created string
|
||||
@param[out] result string the bytes are appended to
|
||||
|
||||
@return whether string creation completed
|
||||
*/
|
||||
bool get_cbor_string(string_t& result)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@pre @a current is not EOF
|
||||
*/
|
||||
bool get_cbor_string_chunk(string_t& result)
|
||||
{
|
||||
switch (current)
|
||||
{
|
||||
// UTF-8 string (0x00..0x17 bytes follow)
|
||||
@@ -1068,20 +1066,6 @@ class binary_reader
|
||||
return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);
|
||||
}
|
||||
|
||||
case 0x7F: // UTF-8 string (indefinite length)
|
||||
{
|
||||
while (get() != 0xFF)
|
||||
{
|
||||
string_t chunk;
|
||||
if (!get_cbor_string(chunk))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
result.append(chunk);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
@@ -1092,23 +1076,82 @@ class binary_reader
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR byte array
|
||||
@brief reads a CBOR string
|
||||
|
||||
This function first reads starting bytes to determine the expected
|
||||
byte array length and then copies this number of bytes into the byte array.
|
||||
Additionally, CBOR's byte arrays with indefinite lengths are supported.
|
||||
string length and then copies this number of bytes into a string.
|
||||
Additionally, CBOR's strings with indefinite lengths are supported.
|
||||
|
||||
@param[out] result created byte array
|
||||
@param[out] result created string
|
||||
|
||||
@return whether string creation completed
|
||||
*/
|
||||
bool get_cbor_string(string_t& result)
|
||||
{
|
||||
// number of indefinite-length strings that have been opened and not
|
||||
// closed yet. RFC 8949, Section 3.2.3 does not permit nesting them,
|
||||
// but this reader has always accepted it, so the open levels are
|
||||
// counted instead of recursed through, which overflowed the stack for
|
||||
// an input of repeated 0x7F bytes (see #5104). Every chunk is appended
|
||||
// to the same result, so no per-level state is needed.
|
||||
std::size_t open = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current == 0x7F) // UTF-8 string (indefinite length)
|
||||
{
|
||||
++open;
|
||||
get();
|
||||
continue;
|
||||
}
|
||||
|
||||
// a break marker closes the innermost indefinite-length string;
|
||||
// outside of one it is not a string and falls through to the error
|
||||
if (open != 0 && current == 0xFF)
|
||||
{
|
||||
if (--open == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
get();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (open == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
get();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a definite-length CBOR byte array
|
||||
|
||||
Reads everything @ref get_cbor_binary accepts except the indefinite-length
|
||||
form, which that function handles itself. The bytes are appended to @a
|
||||
result, so consecutive chunks of an indefinite-length byte array can be
|
||||
read into the same byte array.
|
||||
|
||||
@param[out] result byte array the bytes are appended to
|
||||
|
||||
@return whether byte array creation completed
|
||||
*/
|
||||
bool get_cbor_binary(binary_t& result)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@pre @a current is not EOF
|
||||
*/
|
||||
bool get_cbor_binary_chunk(binary_t& result)
|
||||
{
|
||||
switch (current)
|
||||
{
|
||||
// Binary data (0x00..0x17 bytes follow)
|
||||
@@ -1168,20 +1211,6 @@ class binary_reader
|
||||
get_binary(input_format_t::cbor, len, result);
|
||||
}
|
||||
|
||||
case 0x5F: // Binary data (indefinite length)
|
||||
{
|
||||
while (get() != 0xFF)
|
||||
{
|
||||
binary_t chunk;
|
||||
if (!get_cbor_binary(chunk))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
result.insert(result.end(), chunk.begin(), chunk.end());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
@@ -1191,6 +1220,63 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR byte array
|
||||
|
||||
This function first reads starting bytes to determine the expected
|
||||
byte array length and then copies this number of bytes into the byte array.
|
||||
Additionally, CBOR's byte arrays with indefinite lengths are supported.
|
||||
|
||||
@param[out] result created byte array
|
||||
|
||||
@return whether byte array creation completed
|
||||
*/
|
||||
bool get_cbor_binary(binary_t& result)
|
||||
{
|
||||
// the open indefinite-length byte arrays are counted rather than
|
||||
// recursed through, for the reason given in @ref get_cbor_string
|
||||
std::size_t open = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current == 0x5F) // Binary data (indefinite length)
|
||||
{
|
||||
++open;
|
||||
get();
|
||||
continue;
|
||||
}
|
||||
|
||||
// a break marker closes the innermost indefinite-length byte
|
||||
// array; outside of one it falls through to the error below
|
||||
if (open != 0 && current == 0xFF)
|
||||
{
|
||||
if (--open == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
get();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (open == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
get();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief narrow a definite CBOR array/map length to std::size_t
|
||||
|
||||
|
||||
@@ -11820,23 +11820,21 @@ class binary_reader
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR string
|
||||
@brief reads a definite-length CBOR string
|
||||
|
||||
This function first reads starting bytes to determine the expected
|
||||
string length and then copies this number of bytes into a string.
|
||||
Additionally, CBOR's strings with indefinite lengths are supported.
|
||||
Reads everything @ref get_cbor_string accepts except the indefinite-length
|
||||
form, which that function handles itself. The bytes are appended to @a
|
||||
result, so consecutive chunks of an indefinite-length string can be read
|
||||
into the same string.
|
||||
|
||||
@param[out] result created string
|
||||
@param[out] result string the bytes are appended to
|
||||
|
||||
@return whether string creation completed
|
||||
*/
|
||||
bool get_cbor_string(string_t& result)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@pre @a current is not EOF
|
||||
*/
|
||||
bool get_cbor_string_chunk(string_t& result)
|
||||
{
|
||||
switch (current)
|
||||
{
|
||||
// UTF-8 string (0x00..0x17 bytes follow)
|
||||
@@ -11892,20 +11890,6 @@ class binary_reader
|
||||
return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);
|
||||
}
|
||||
|
||||
case 0x7F: // UTF-8 string (indefinite length)
|
||||
{
|
||||
while (get() != 0xFF)
|
||||
{
|
||||
string_t chunk;
|
||||
if (!get_cbor_string(chunk))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
result.append(chunk);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
@@ -11916,23 +11900,82 @@ class binary_reader
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR byte array
|
||||
@brief reads a CBOR string
|
||||
|
||||
This function first reads starting bytes to determine the expected
|
||||
byte array length and then copies this number of bytes into the byte array.
|
||||
Additionally, CBOR's byte arrays with indefinite lengths are supported.
|
||||
string length and then copies this number of bytes into a string.
|
||||
Additionally, CBOR's strings with indefinite lengths are supported.
|
||||
|
||||
@param[out] result created byte array
|
||||
@param[out] result created string
|
||||
|
||||
@return whether string creation completed
|
||||
*/
|
||||
bool get_cbor_string(string_t& result)
|
||||
{
|
||||
// number of indefinite-length strings that have been opened and not
|
||||
// closed yet. RFC 8949, Section 3.2.3 does not permit nesting them,
|
||||
// but this reader has always accepted it, so the open levels are
|
||||
// counted instead of recursed through, which overflowed the stack for
|
||||
// an input of repeated 0x7F bytes (see #5104). Every chunk is appended
|
||||
// to the same result, so no per-level state is needed.
|
||||
std::size_t open = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current == 0x7F) // UTF-8 string (indefinite length)
|
||||
{
|
||||
++open;
|
||||
get();
|
||||
continue;
|
||||
}
|
||||
|
||||
// a break marker closes the innermost indefinite-length string;
|
||||
// outside of one it is not a string and falls through to the error
|
||||
if (open != 0 && current == 0xFF)
|
||||
{
|
||||
if (--open == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
get();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (open == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
get();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a definite-length CBOR byte array
|
||||
|
||||
Reads everything @ref get_cbor_binary accepts except the indefinite-length
|
||||
form, which that function handles itself. The bytes are appended to @a
|
||||
result, so consecutive chunks of an indefinite-length byte array can be
|
||||
read into the same byte array.
|
||||
|
||||
@param[out] result byte array the bytes are appended to
|
||||
|
||||
@return whether byte array creation completed
|
||||
*/
|
||||
bool get_cbor_binary(binary_t& result)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@pre @a current is not EOF
|
||||
*/
|
||||
bool get_cbor_binary_chunk(binary_t& result)
|
||||
{
|
||||
switch (current)
|
||||
{
|
||||
// Binary data (0x00..0x17 bytes follow)
|
||||
@@ -11992,20 +12035,6 @@ class binary_reader
|
||||
get_binary(input_format_t::cbor, len, result);
|
||||
}
|
||||
|
||||
case 0x5F: // Binary data (indefinite length)
|
||||
{
|
||||
while (get() != 0xFF)
|
||||
{
|
||||
binary_t chunk;
|
||||
if (!get_cbor_binary(chunk))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
result.insert(result.end(), chunk.begin(), chunk.end());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
@@ -12015,6 +12044,63 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR byte array
|
||||
|
||||
This function first reads starting bytes to determine the expected
|
||||
byte array length and then copies this number of bytes into the byte array.
|
||||
Additionally, CBOR's byte arrays with indefinite lengths are supported.
|
||||
|
||||
@param[out] result created byte array
|
||||
|
||||
@return whether byte array creation completed
|
||||
*/
|
||||
bool get_cbor_binary(binary_t& result)
|
||||
{
|
||||
// the open indefinite-length byte arrays are counted rather than
|
||||
// recursed through, for the reason given in @ref get_cbor_string
|
||||
std::size_t open = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current == 0x5F) // Binary data (indefinite length)
|
||||
{
|
||||
++open;
|
||||
get();
|
||||
continue;
|
||||
}
|
||||
|
||||
// a break marker closes the innermost indefinite-length byte
|
||||
// array; outside of one it falls through to the error below
|
||||
if (open != 0 && current == 0xFF)
|
||||
{
|
||||
if (--open == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
get();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (open == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
get();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief narrow a definite CBOR array/map length to std::size_t
|
||||
|
||||
|
||||
@@ -2035,6 +2035,58 @@ TEST_CASE("CBOR definite length equal to the indefinite-length sentinel")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
||||
{
|
||||
// Reading an indefinite-length string or byte array used to call itself
|
||||
// once per chunk, so a payload of repeated 0x7F (or 0x5F) bytes exhausted
|
||||
// the call stack before any of the input was rejected. The open levels are
|
||||
// counted now, and the levels below prove the reader still reads the same
|
||||
// values and reports the same errors at the same byte offsets.
|
||||
json _;
|
||||
|
||||
SECTION("many open levels are reported, not crashed on")
|
||||
{
|
||||
const std::vector<uint8_t> input(200000, 0x7F);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("many open levels are reported, not crashed on (binary)")
|
||||
{
|
||||
const std::vector<uint8_t> input(200000, 0x5F);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("chunks are still concatenated")
|
||||
{
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0xFF})) == json(""));
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x61, 0x61, 0xFF})) == json("a"));
|
||||
// nested indefinite-length strings are concatenated across levels
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x61, 0x61, 0xFF, 0x61, 0x62, 0xFF})) == json("ab"));
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x7F, 0x61, 0x7A, 0xFF, 0xFF, 0xFF})) == json("z"));
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xA1, 0x7F, 0x61, 0x61, 0xFF, 0x01})) == json({{"a", 1}}));
|
||||
}
|
||||
|
||||
SECTION("chunks are still concatenated (binary)")
|
||||
{
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x41, 0x61, 0xFF})) == json::binary({0x61}));
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x41, 0x61, 0xFF, 0x41, 0x62, 0xFF})) == json::binary({0x61, 0x62}));
|
||||
}
|
||||
|
||||
SECTION("a chunk that is not a string is still rejected")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x00", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x00", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("a break marker outside an indefinite-length string is not a string")
|
||||
{
|
||||
// 0xFF only closes a string that was opened; on its own it is not one
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CBOR roundtrips" * doctest::skip())
|
||||
{
|
||||
SECTION("input from flynn")
|
||||
|
||||
Reference in New Issue
Block a user