mirror of
https://github.com/nlohmann/json.git
synced 2026-10-26 15:58:55 +00:00
Write and read the inner length of BSON binary subtype 0x02
The BSON specification defines the payload of the "binary (old)" subtype 0x02 as an int32 followed by that many bytes. to_bson wrote the plain payload instead, so decoders such as pymongo and libbson-based readers rejected the bytes, and from_bson returned the inner length as part of the payload (documented as lenient behavior in gh-5333). The writer now emits the outer length (payload + 4), the subtype, the inner int32 holding the payload size, and the payload. The reader requires an outer length of at least 4 and an inner length equal to the outer length minus 4 (the cases the bson-corpus lists as decode errors), reports parse error 112 otherwise, and returns only the payload. Both directions change together so that the round trip keeps working. The single-reservation fast path for scalar entries declines subtype 0x02 because it has no room for the inner length. This is a deliberate exception to the opt-in rule for behavior changes: the previous output was invalid BSON. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+139
-13
No files matched your search
@@ -584,9 +584,46 @@ class binary_reader
|
||||
}
|
||||
result.set_subtype(subtype);
|
||||
|
||||
// the "old binary" subtype 0x02 starts with an int32 that repeats the
|
||||
// size of the payload following it; the payload does not include it
|
||||
if (subtype == 0x02)
|
||||
{
|
||||
return get_bson_old_binary(len, result);
|
||||
}
|
||||
|
||||
return get_binary(len, result);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Parses the payload of a BSON binary value with subtype 0x02.
|
||||
@param[in] len The length of the binary value, including the inner int32.
|
||||
@param[in,out] result A reference to the binary variable where the payload
|
||||
(without the inner int32) is to be stored.
|
||||
@pre len >= 0
|
||||
@return `true` if the inner length was present and matches @a len
|
||||
*/
|
||||
template<typename NumberType>
|
||||
bool get_bson_old_binary(const NumberType len, binary_t& result)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(len < 4))
|
||||
{
|
||||
return bson_length_error("length of binary subtype 0x02 must be at least 4, is ", len, "binary");
|
||||
}
|
||||
|
||||
std::int32_t inner_length{};
|
||||
if (JSON_HEDLEY_UNLIKELY((!get_number<std::int32_t, true>(inner_length))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(static_cast<std::int64_t>(inner_length) != static_cast<std::int64_t>(len) - 4))
|
||||
{
|
||||
return bson_length_error("inner length of binary subtype 0x02 does not match the payload size; inner length is ", inner_length, "binary");
|
||||
}
|
||||
|
||||
return get_binary(inner_length, result);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Read a BSON document element of the given @a element_type.
|
||||
@param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html
|
||||
|
||||
@@ -1684,7 +1684,15 @@ class binary_writer
|
||||
throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j);
|
||||
}
|
||||
|
||||
return sizeof(std::int32_t) + value.size() + 1ul;
|
||||
// the "old binary" subtype 0x02 repeats the payload size in an inner
|
||||
// int32 in front of the payload
|
||||
return sizeof(std::int32_t) + (is_bson_old_binary(value) ? sizeof(std::int32_t) : 0ul) + value.size() + 1ul;
|
||||
}
|
||||
|
||||
/// @return whether @a value has the BSON subtype 0x02 ("binary (old)")
|
||||
static bool is_bson_old_binary(const binary_t& value) noexcept
|
||||
{
|
||||
return value.has_subtype() && value.subtype() == 0x02;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1697,10 +1705,18 @@ class binary_writer
|
||||
{
|
||||
write_bson_entry_header(name, 0x05);
|
||||
|
||||
write_number<std::int32_t>(to_bson_length(value.size()), true);
|
||||
const bool old_binary = is_bson_old_binary(value);
|
||||
|
||||
// the length field counts the inner int32 of the old binary subtype
|
||||
write_number<std::int32_t>(to_bson_length(value.size() + (old_binary ? sizeof(std::int32_t) : 0ul)), true);
|
||||
|
||||
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
||||
|
||||
if (old_binary)
|
||||
{
|
||||
write_number<std::int32_t>(to_bson_length(value.size()), true);
|
||||
}
|
||||
|
||||
write_bytes(value.data(), value.size());
|
||||
}
|
||||
|
||||
@@ -1860,7 +1876,8 @@ class binary_writer
|
||||
(or the header of a string or binary value) in one reservation
|
||||
@return whether the entry was written; false for a discarded value, a
|
||||
string or binary value whose length does not fit the length
|
||||
field, in which case nothing was written
|
||||
field, or a binary value with subtype 0x02, in which case nothing
|
||||
was written
|
||||
@pre @a name is at most bson_scalar_max_name_size characters long and is
|
||||
not changed by the error handler
|
||||
*/
|
||||
@@ -1921,7 +1938,9 @@ class binary_writer
|
||||
if (j.is_binary())
|
||||
{
|
||||
const binary_t& value = *j.m_data.m_value.binary;
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size())))
|
||||
// the old binary subtype 0x02 has an inner length that the single
|
||||
// reservation has no room for; write_bson_binary writes it
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size()) || is_bson_old_binary(value)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user