Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c588321c5 | ||
|
|
cff8548496 |
No files matched your search
@@ -465,15 +465,17 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t element_type_parse_position = chars_read;
|
const std::size_t element_type_parse_position = chars_read;
|
||||||
|
// an array's elements are named "0", "1", ... in the wire format,
|
||||||
|
// and those names are not passed on: they are skipped unless
|
||||||
|
// error_handler_t::strict must reject ill-formed UTF-8 in them
|
||||||
|
const bool is_object = container_stack.back().is_object;
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// an array's elements are named "0", "1", ... in the wire format,
|
if (is_object && !sax->key(key))
|
||||||
// and those names are not passed on
|
|
||||||
if (container_stack.back().is_object && !sax->key(key))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -489,14 +491,18 @@ class binary_reader
|
|||||||
@brief Parses a C-style string from the BSON input.
|
@brief Parses a C-style string from the BSON input.
|
||||||
@param[in,out] result A reference to the string variable where the read
|
@param[in,out] result A reference to the string variable where the read
|
||||||
string is to be stored.
|
string is to be stored.
|
||||||
|
@param[in] store whether the characters are needed; if not (the names of
|
||||||
|
an array's elements), they are skipped and @a result may
|
||||||
|
be left unchanged, and they are not checked against
|
||||||
|
@ref error_handler
|
||||||
@return `true` if the \\x00-byte indicating the end of the string was
|
@return `true` if the \\x00-byte indicating the end of the string was
|
||||||
encountered before the EOF; false` indicates an unexpected EOF.
|
encountered before the EOF; false` indicates an unexpected EOF.
|
||||||
*/
|
*/
|
||||||
bool get_bson_cstr(string_t& result)
|
bool get_bson_cstr(string_t& result, const bool store = true)
|
||||||
{
|
{
|
||||||
if (get_bson_cstr_bulk(result, std::integral_constant<bool, bulk_scan> {}))
|
if (get_bson_cstr_bulk(result, store, std::integral_constant<bool, bulk_scan> {}))
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, "key");
|
return !store || check_string_utf8(result, "key");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto out = std::back_inserter(result);
|
auto out = std::back_inserter(result);
|
||||||
@@ -509,7 +515,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
if (current == 0x00)
|
if (current == 0x00)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, "key");
|
return !store || check_string_utf8(result, "key");
|
||||||
}
|
}
|
||||||
*out++ = static_cast<typename string_t::value_type>(current);
|
*out++ = static_cast<typename string_t::value_type>(current);
|
||||||
}
|
}
|
||||||
@@ -519,10 +525,11 @@ class binary_reader
|
|||||||
@brief read a C-style string from contiguous input in one step
|
@brief read a C-style string from contiguous input in one step
|
||||||
|
|
||||||
@param[in,out] result the string to append to
|
@param[in,out] result the string to append to
|
||||||
|
@param[in] store whether to append the characters to @a result
|
||||||
@return whether the string was read; if the input has no \\x00-byte, nothing
|
@return whether the string was read; if the input has no \\x00-byte, nothing
|
||||||
is read, and @ref get_bson_cstr reports the end of the input
|
is read, and @ref get_bson_cstr reports the end of the input
|
||||||
*/
|
*/
|
||||||
bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/)
|
bool get_bson_cstr_bulk(string_t& result, const bool store, std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
if (remaining == 0)
|
if (remaining == 0)
|
||||||
@@ -530,18 +537,15 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
// a plain loop rather than std::memchr: most keys are short (array
|
const std::size_t length = find_zero_byte(data, remaining);
|
||||||
// indices are keys, too), and the call would cost more than it saves
|
|
||||||
std::size_t length = 0;
|
|
||||||
while (length < remaining && data[length] != 0x00)
|
|
||||||
{
|
|
||||||
++length;
|
|
||||||
}
|
|
||||||
if (length == remaining)
|
if (length == remaining)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
if (store)
|
||||||
|
{
|
||||||
|
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||||
|
}
|
||||||
// consume the string and its \x00-byte, as the byte-wise path does
|
// consume the string and its \x00-byte, as the byte-wise path does
|
||||||
ia.bulk_skip(length + 1);
|
ia.bulk_skip(length + 1);
|
||||||
chars_read += length + 1;
|
chars_read += length + 1;
|
||||||
@@ -550,7 +554,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// input that is not contiguous: C-style strings are read byte by byte
|
/// input that is not contiguous: C-style strings are read byte by byte
|
||||||
bool get_bson_cstr_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept
|
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,34 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return the index of the first 0x00 byte in [data, data+n), or n if there is
|
||||||
|
// none; scans 8 bytes at a time (BSON's C-style strings)
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_zero_byte(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
const std::uint64_t v = read_eight_bytes(data + i);
|
||||||
|
const std::uint64_t zero = (v - ones) & ~v & high;
|
||||||
|
if (zero != 0)
|
||||||
|
{
|
||||||
|
// the lowest flagged byte is the first zero byte (see
|
||||||
|
// find_string_special())
|
||||||
|
return i + (static_cast<std::size_t>(count_trailing_zeros(zero)) / 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (data[i] == 0x00)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
// classify a byte as one the serializer must NOT copy verbatim when
|
// classify a byte as one the serializer must NOT copy verbatim when
|
||||||
// ensure_ascii is requested: the closing quote, an escape, a control character
|
// ensure_ascii is requested: the closing quote, an escape, a control character
|
||||||
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
||||||
|
|||||||
@@ -2088,8 +2088,7 @@ class binary_writer
|
|||||||
/// arrays: the first index with one more digit; only maintained by
|
/// arrays: the first index with one more digit; only maintained by
|
||||||
/// @ref calc_bson_sizes
|
/// @ref calc_bson_sizes
|
||||||
std::uint64_t index_boundary = 10;
|
std::uint64_t index_boundary = 10;
|
||||||
/// @ref calc_bson_sizes: where its size goes in the table; @ref
|
/// @ref calc_bson_sizes only: where its size goes in the table
|
||||||
/// write_bson_document_single_pass: the position of its size field
|
|
||||||
std::size_t size_slot;
|
std::size_t size_slot;
|
||||||
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
||||||
std::size_t entries_size = 0;
|
std::size_t entries_size = 0;
|
||||||
@@ -2340,197 +2339,6 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename SinkType>
|
|
||||||
using sink_written_t = decltype(std::declval<const SinkType&>().written());
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief whether BSON documents can be written in a single pass: the sink
|
|
||||||
can overwrite what it has written (output_vector_sink), and every
|
|
||||||
integer fits the 64 bits BSON has, so that writing an array of
|
|
||||||
numbers needs no range check
|
|
||||||
*/
|
|
||||||
using bson_single_pass_sink = std::integral_constant < bool,
|
|
||||||
is_detected<sink_written_t, OutputSinkType>::value
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::is_specialized
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::is_specialized
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::digits <= std::numeric_limits<std::uint64_t>::digits >;
|
|
||||||
|
|
||||||
/// write a placeholder for the size of an object or array whose entries
|
|
||||||
/// follow; @return its position, for @ref patch_bson_size
|
|
||||||
std::size_t write_bson_size_placeholder()
|
|
||||||
{
|
|
||||||
const std::size_t pos = oa.written();
|
|
||||||
write_number<std::int32_t>(0, true);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// end the object or array whose size is at @a pos and write its size
|
|
||||||
/// there: everything from that field to the terminating null byte
|
|
||||||
/// @return false (and the size is not written) if it does not fit
|
|
||||||
bool close_bson_container(const std::size_t pos)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x00));
|
|
||||||
const std::size_t size = oa.written() - pos;
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::array<CharType, sizeof(std::int32_t)> bytes{};
|
|
||||||
store_little_endian<std::int32_t>(bytes.data(), static_cast<std::int32_t>(size));
|
|
||||||
oa.overwrite(pos, bytes.data(), bytes.size());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief check the value @a j of a BSON entry as @ref calc_bson_value_size
|
|
||||||
does, for @ref write_bson_document_single_pass
|
|
||||||
@return at least the size of the value
|
|
||||||
A number, boolean, or null needs no check there (its integers have at most
|
|
||||||
64 bits) and has at most 8 characters; a discarded value is rejected with
|
|
||||||
the same exception when it is written.
|
|
||||||
*/
|
|
||||||
std::size_t check_bson_value(const BasicJsonType& j)
|
|
||||||
{
|
|
||||||
return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether an entry of @a entry_size characters keeps the document that
|
|
||||||
/// begins at @a start, closed by @a open_containers null bytes, within
|
|
||||||
/// the 32-bit size of BSON
|
|
||||||
bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept
|
|
||||||
{
|
|
||||||
const std::size_t limit = static_cast<std::size_t>((std::numeric_limits<std::int32_t>::max)());
|
|
||||||
const std::size_t written = oa.written() - start;
|
|
||||||
return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Serializes the JSON object @a document as a BSON document in a
|
|
||||||
single pass, writing the size of each object and array once it is
|
|
||||||
complete
|
|
||||||
|
|
||||||
Checks every key and value as @ref calc_bson_sizes does, in the same
|
|
||||||
order, so the same exception is thrown; the output written up to then is
|
|
||||||
discarded with the vector it was written to.
|
|
||||||
|
|
||||||
@return false as soon as the document or a string, binary value, object,
|
|
||||||
or array in it would be larger than the 32-bit BSON sizes allow;
|
|
||||||
nothing large has been written then, and the caller writes the
|
|
||||||
document in two passes, which reports that error
|
|
||||||
*/
|
|
||||||
template < typename SinkType = OutputSinkType,
|
|
||||||
typename std::enable_if < is_detected<sink_written_t, SinkType>::value, int >::type = 0 >
|
|
||||||
bool write_bson_document_single_pass(const BasicJsonType& document)
|
|
||||||
{
|
|
||||||
const std::size_t start = oa.written();
|
|
||||||
// the object or array whose entries are being written (with the
|
|
||||||
// position of its size), and the ones it is in
|
|
||||||
bson_frame current(&document, write_bson_size_placeholder());
|
|
||||||
std::vector<bson_frame> parents;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
const BasicJsonType* nested = nullptr;
|
|
||||||
std::size_t nested_size_pos = 0;
|
|
||||||
if (current.value->is_object())
|
|
||||||
{
|
|
||||||
const auto& object = *current.value->m_data.m_value.object;
|
|
||||||
while (nested == nullptr && current.member != object.cend())
|
|
||||||
{
|
|
||||||
const auto& el = *current.member;
|
|
||||||
++current.member;
|
|
||||||
const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second);
|
|
||||||
const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (el.second.is_structured())
|
|
||||||
{
|
|
||||||
write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04);
|
|
||||||
nested_size_pos = write_bson_size_placeholder();
|
|
||||||
nested = &el.second;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_bson_value(el.first, el.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto& array = *current.value->m_data.m_value.array;
|
|
||||||
while (nested == nullptr && current.index < array.size())
|
|
||||||
{
|
|
||||||
if (is_bson_fixed_size(array[current.index]))
|
|
||||||
{
|
|
||||||
write_bson_array_fixed_entries(current);
|
|
||||||
if (current.index == array.size())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a string, binary value, object or array
|
|
||||||
const BasicJsonType& el = array[current.index];
|
|
||||||
const string_t index_name(current.index_name.data(), current.index_name_size);
|
|
||||||
current.next_index();
|
|
||||||
const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (el.is_structured())
|
|
||||||
{
|
|
||||||
write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04);
|
|
||||||
nested_size_pos = write_bson_size_placeholder();
|
|
||||||
nested = ⪙
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_bson_value(index_name, el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nested != nullptr)
|
|
||||||
{
|
|
||||||
if (nested->is_array())
|
|
||||||
{
|
|
||||||
// an array of booleans, numbers and null is written without a frame on the stack
|
|
||||||
bson_frame leaf(nested, nested_size_pos);
|
|
||||||
write_bson_array_fixed_entries(leaf);
|
|
||||||
if (leaf.index == nested->m_data.m_value.array->size())
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parents.push_back(std::move(current));
|
|
||||||
current = std::move(leaf);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parents.push_back(std::move(current));
|
|
||||||
current = bson_frame(nested, nested_size_pos);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (parents.empty())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
current = std::move(parents.back());
|
|
||||||
parents.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Serializes the JSON object @a document as a BSON document
|
@brief Serializes the JSON object @a document as a BSON document
|
||||||
|
|
||||||
@@ -2542,39 +2350,6 @@ class binary_writer
|
|||||||
@pre document.type() == value_t::object
|
@pre document.type() == value_t::object
|
||||||
*/
|
*/
|
||||||
void write_bson_document(const BasicJsonType& document)
|
void write_bson_document(const BasicJsonType& document)
|
||||||
{
|
|
||||||
write_bson_document(document, bson_single_pass_sink());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the sink can overwrite: one pass, unless the error handler replaces or
|
|
||||||
/// drops ill-formed UTF-8, which changes the sizes the checks compute
|
|
||||||
void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/)
|
|
||||||
{
|
|
||||||
const std::size_t start = oa.written();
|
|
||||||
if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)
|
|
||||||
&& write_bson_document_single_pass(document))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// a length does not fit: start over, so that it is reported as the
|
|
||||||
// two passes report it, before anything is written
|
|
||||||
oa.rewind(start);
|
|
||||||
write_bson_document_two_pass(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/)
|
|
||||||
{
|
|
||||||
write_bson_document_two_pass(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Serializes the JSON object @a document as a BSON document: the
|
|
||||||
sizes of all objects and arrays first, then the entries
|
|
||||||
|
|
||||||
@param[in] document the JSON object to serialize
|
|
||||||
@pre document.type() == value_t::object
|
|
||||||
*/
|
|
||||||
void write_bson_document_two_pass(const BasicJsonType& document)
|
|
||||||
{
|
{
|
||||||
std::vector<std::size_t> nested_sizes;
|
std::vector<std::size_t> nested_sizes;
|
||||||
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
||||||
|
|||||||
@@ -227,29 +227,6 @@ class output_vector_sink
|
|||||||
void flush() noexcept
|
void flush() noexcept
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// the number of characters written so far, the position of the next one
|
|
||||||
std::size_t written() const noexcept
|
|
||||||
{
|
|
||||||
return position();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief replace @a length characters written before, from position @a pos
|
|
||||||
///
|
|
||||||
/// Lets a writer put a length in front of data it has written since, such
|
|
||||||
/// as the size of a BSON document, without computing it in advance.
|
|
||||||
void overwrite(const std::size_t pos, const CharType* s, const std::size_t length) noexcept
|
|
||||||
{
|
|
||||||
JSON_ASSERT(pos + length <= position());
|
|
||||||
std::memcpy(v->data() + pos, s, length * sizeof(CharType));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// drop the characters written from position @a pos on
|
|
||||||
void rewind(const std::size_t pos) noexcept
|
|
||||||
{
|
|
||||||
JSON_ASSERT(pos <= position());
|
|
||||||
cursor = v->data() + pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// number of characters of *v written so far
|
/// number of characters of *v written so far
|
||||||
std::size_t position() const noexcept
|
std::size_t position() const noexcept
|
||||||
|
|||||||
Generated
+51
-267
@@ -12458,6 +12458,34 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return the index of the first 0x00 byte in [data, data+n), or n if there is
|
||||||
|
// none; scans 8 bytes at a time (BSON's C-style strings)
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_zero_byte(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
const std::uint64_t v = read_eight_bytes(data + i);
|
||||||
|
const std::uint64_t zero = (v - ones) & ~v & high;
|
||||||
|
if (zero != 0)
|
||||||
|
{
|
||||||
|
// the lowest flagged byte is the first zero byte (see
|
||||||
|
// find_string_special())
|
||||||
|
return i + (static_cast<std::size_t>(count_trailing_zeros(zero)) / 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (data[i] == 0x00)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
// classify a byte as one the serializer must NOT copy verbatim when
|
// classify a byte as one the serializer must NOT copy verbatim when
|
||||||
// ensure_ascii is requested: the closing quote, an escape, a control character
|
// ensure_ascii is requested: the closing quote, an escape, a control character
|
||||||
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
||||||
@@ -18241,15 +18269,17 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t element_type_parse_position = chars_read;
|
const std::size_t element_type_parse_position = chars_read;
|
||||||
|
// an array's elements are named "0", "1", ... in the wire format,
|
||||||
|
// and those names are not passed on: they are skipped unless
|
||||||
|
// error_handler_t::strict must reject ill-formed UTF-8 in them
|
||||||
|
const bool is_object = container_stack.back().is_object;
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// an array's elements are named "0", "1", ... in the wire format,
|
if (is_object && !sax->key(key))
|
||||||
// and those names are not passed on
|
|
||||||
if (container_stack.back().is_object && !sax->key(key))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -18265,14 +18295,18 @@ class binary_reader
|
|||||||
@brief Parses a C-style string from the BSON input.
|
@brief Parses a C-style string from the BSON input.
|
||||||
@param[in,out] result A reference to the string variable where the read
|
@param[in,out] result A reference to the string variable where the read
|
||||||
string is to be stored.
|
string is to be stored.
|
||||||
|
@param[in] store whether the characters are needed; if not (the names of
|
||||||
|
an array's elements), they are skipped and @a result may
|
||||||
|
be left unchanged, and they are not checked against
|
||||||
|
@ref error_handler
|
||||||
@return `true` if the \\x00-byte indicating the end of the string was
|
@return `true` if the \\x00-byte indicating the end of the string was
|
||||||
encountered before the EOF; false` indicates an unexpected EOF.
|
encountered before the EOF; false` indicates an unexpected EOF.
|
||||||
*/
|
*/
|
||||||
bool get_bson_cstr(string_t& result)
|
bool get_bson_cstr(string_t& result, const bool store = true)
|
||||||
{
|
{
|
||||||
if (get_bson_cstr_bulk(result, std::integral_constant<bool, bulk_scan> {}))
|
if (get_bson_cstr_bulk(result, store, std::integral_constant<bool, bulk_scan> {}))
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, "key");
|
return !store || check_string_utf8(result, "key");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto out = std::back_inserter(result);
|
auto out = std::back_inserter(result);
|
||||||
@@ -18285,7 +18319,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
if (current == 0x00)
|
if (current == 0x00)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, "key");
|
return !store || check_string_utf8(result, "key");
|
||||||
}
|
}
|
||||||
*out++ = static_cast<typename string_t::value_type>(current);
|
*out++ = static_cast<typename string_t::value_type>(current);
|
||||||
}
|
}
|
||||||
@@ -18295,10 +18329,11 @@ class binary_reader
|
|||||||
@brief read a C-style string from contiguous input in one step
|
@brief read a C-style string from contiguous input in one step
|
||||||
|
|
||||||
@param[in,out] result the string to append to
|
@param[in,out] result the string to append to
|
||||||
|
@param[in] store whether to append the characters to @a result
|
||||||
@return whether the string was read; if the input has no \\x00-byte, nothing
|
@return whether the string was read; if the input has no \\x00-byte, nothing
|
||||||
is read, and @ref get_bson_cstr reports the end of the input
|
is read, and @ref get_bson_cstr reports the end of the input
|
||||||
*/
|
*/
|
||||||
bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/)
|
bool get_bson_cstr_bulk(string_t& result, const bool store, std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
if (remaining == 0)
|
if (remaining == 0)
|
||||||
@@ -18306,18 +18341,15 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
// a plain loop rather than std::memchr: most keys are short (array
|
const std::size_t length = find_zero_byte(data, remaining);
|
||||||
// indices are keys, too), and the call would cost more than it saves
|
|
||||||
std::size_t length = 0;
|
|
||||||
while (length < remaining && data[length] != 0x00)
|
|
||||||
{
|
|
||||||
++length;
|
|
||||||
}
|
|
||||||
if (length == remaining)
|
if (length == remaining)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
if (store)
|
||||||
|
{
|
||||||
|
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||||
|
}
|
||||||
// consume the string and its \x00-byte, as the byte-wise path does
|
// consume the string and its \x00-byte, as the byte-wise path does
|
||||||
ia.bulk_skip(length + 1);
|
ia.bulk_skip(length + 1);
|
||||||
chars_read += length + 1;
|
chars_read += length + 1;
|
||||||
@@ -18326,7 +18358,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// input that is not contiguous: C-style strings are read byte by byte
|
/// input that is not contiguous: C-style strings are read byte by byte
|
||||||
bool get_bson_cstr_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept
|
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -26870,29 +26902,6 @@ class output_vector_sink
|
|||||||
void flush() noexcept
|
void flush() noexcept
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// the number of characters written so far, the position of the next one
|
|
||||||
std::size_t written() const noexcept
|
|
||||||
{
|
|
||||||
return position();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief replace @a length characters written before, from position @a pos
|
|
||||||
///
|
|
||||||
/// Lets a writer put a length in front of data it has written since, such
|
|
||||||
/// as the size of a BSON document, without computing it in advance.
|
|
||||||
void overwrite(const std::size_t pos, const CharType* s, const std::size_t length) noexcept
|
|
||||||
{
|
|
||||||
JSON_ASSERT(pos + length <= position());
|
|
||||||
std::memcpy(v->data() + pos, s, length * sizeof(CharType));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// drop the characters written from position @a pos on
|
|
||||||
void rewind(const std::size_t pos) noexcept
|
|
||||||
{
|
|
||||||
JSON_ASSERT(pos <= position());
|
|
||||||
cursor = v->data() + pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// number of characters of *v written so far
|
/// number of characters of *v written so far
|
||||||
std::size_t position() const noexcept
|
std::size_t position() const noexcept
|
||||||
@@ -29337,8 +29346,7 @@ class binary_writer
|
|||||||
/// arrays: the first index with one more digit; only maintained by
|
/// arrays: the first index with one more digit; only maintained by
|
||||||
/// @ref calc_bson_sizes
|
/// @ref calc_bson_sizes
|
||||||
std::uint64_t index_boundary = 10;
|
std::uint64_t index_boundary = 10;
|
||||||
/// @ref calc_bson_sizes: where its size goes in the table; @ref
|
/// @ref calc_bson_sizes only: where its size goes in the table
|
||||||
/// write_bson_document_single_pass: the position of its size field
|
|
||||||
std::size_t size_slot;
|
std::size_t size_slot;
|
||||||
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
||||||
std::size_t entries_size = 0;
|
std::size_t entries_size = 0;
|
||||||
@@ -29589,197 +29597,6 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename SinkType>
|
|
||||||
using sink_written_t = decltype(std::declval<const SinkType&>().written());
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief whether BSON documents can be written in a single pass: the sink
|
|
||||||
can overwrite what it has written (output_vector_sink), and every
|
|
||||||
integer fits the 64 bits BSON has, so that writing an array of
|
|
||||||
numbers needs no range check
|
|
||||||
*/
|
|
||||||
using bson_single_pass_sink = std::integral_constant < bool,
|
|
||||||
is_detected<sink_written_t, OutputSinkType>::value
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::is_specialized
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::is_specialized
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::digits <= std::numeric_limits<std::uint64_t>::digits >;
|
|
||||||
|
|
||||||
/// write a placeholder for the size of an object or array whose entries
|
|
||||||
/// follow; @return its position, for @ref patch_bson_size
|
|
||||||
std::size_t write_bson_size_placeholder()
|
|
||||||
{
|
|
||||||
const std::size_t pos = oa.written();
|
|
||||||
write_number<std::int32_t>(0, true);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// end the object or array whose size is at @a pos and write its size
|
|
||||||
/// there: everything from that field to the terminating null byte
|
|
||||||
/// @return false (and the size is not written) if it does not fit
|
|
||||||
bool close_bson_container(const std::size_t pos)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x00));
|
|
||||||
const std::size_t size = oa.written() - pos;
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::array<CharType, sizeof(std::int32_t)> bytes{};
|
|
||||||
store_little_endian<std::int32_t>(bytes.data(), static_cast<std::int32_t>(size));
|
|
||||||
oa.overwrite(pos, bytes.data(), bytes.size());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief check the value @a j of a BSON entry as @ref calc_bson_value_size
|
|
||||||
does, for @ref write_bson_document_single_pass
|
|
||||||
@return at least the size of the value
|
|
||||||
A number, boolean, or null needs no check there (its integers have at most
|
|
||||||
64 bits) and has at most 8 characters; a discarded value is rejected with
|
|
||||||
the same exception when it is written.
|
|
||||||
*/
|
|
||||||
std::size_t check_bson_value(const BasicJsonType& j)
|
|
||||||
{
|
|
||||||
return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether an entry of @a entry_size characters keeps the document that
|
|
||||||
/// begins at @a start, closed by @a open_containers null bytes, within
|
|
||||||
/// the 32-bit size of BSON
|
|
||||||
bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept
|
|
||||||
{
|
|
||||||
const std::size_t limit = static_cast<std::size_t>((std::numeric_limits<std::int32_t>::max)());
|
|
||||||
const std::size_t written = oa.written() - start;
|
|
||||||
return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Serializes the JSON object @a document as a BSON document in a
|
|
||||||
single pass, writing the size of each object and array once it is
|
|
||||||
complete
|
|
||||||
|
|
||||||
Checks every key and value as @ref calc_bson_sizes does, in the same
|
|
||||||
order, so the same exception is thrown; the output written up to then is
|
|
||||||
discarded with the vector it was written to.
|
|
||||||
|
|
||||||
@return false as soon as the document or a string, binary value, object,
|
|
||||||
or array in it would be larger than the 32-bit BSON sizes allow;
|
|
||||||
nothing large has been written then, and the caller writes the
|
|
||||||
document in two passes, which reports that error
|
|
||||||
*/
|
|
||||||
template < typename SinkType = OutputSinkType,
|
|
||||||
typename std::enable_if < is_detected<sink_written_t, SinkType>::value, int >::type = 0 >
|
|
||||||
bool write_bson_document_single_pass(const BasicJsonType& document)
|
|
||||||
{
|
|
||||||
const std::size_t start = oa.written();
|
|
||||||
// the object or array whose entries are being written (with the
|
|
||||||
// position of its size), and the ones it is in
|
|
||||||
bson_frame current(&document, write_bson_size_placeholder());
|
|
||||||
std::vector<bson_frame> parents;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
const BasicJsonType* nested = nullptr;
|
|
||||||
std::size_t nested_size_pos = 0;
|
|
||||||
if (current.value->is_object())
|
|
||||||
{
|
|
||||||
const auto& object = *current.value->m_data.m_value.object;
|
|
||||||
while (nested == nullptr && current.member != object.cend())
|
|
||||||
{
|
|
||||||
const auto& el = *current.member;
|
|
||||||
++current.member;
|
|
||||||
const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second);
|
|
||||||
const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (el.second.is_structured())
|
|
||||||
{
|
|
||||||
write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04);
|
|
||||||
nested_size_pos = write_bson_size_placeholder();
|
|
||||||
nested = &el.second;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_bson_value(el.first, el.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto& array = *current.value->m_data.m_value.array;
|
|
||||||
while (nested == nullptr && current.index < array.size())
|
|
||||||
{
|
|
||||||
if (is_bson_fixed_size(array[current.index]))
|
|
||||||
{
|
|
||||||
write_bson_array_fixed_entries(current);
|
|
||||||
if (current.index == array.size())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a string, binary value, object or array
|
|
||||||
const BasicJsonType& el = array[current.index];
|
|
||||||
const string_t index_name(current.index_name.data(), current.index_name_size);
|
|
||||||
current.next_index();
|
|
||||||
const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (el.is_structured())
|
|
||||||
{
|
|
||||||
write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04);
|
|
||||||
nested_size_pos = write_bson_size_placeholder();
|
|
||||||
nested = ⪙
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_bson_value(index_name, el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nested != nullptr)
|
|
||||||
{
|
|
||||||
if (nested->is_array())
|
|
||||||
{
|
|
||||||
// an array of booleans, numbers and null is written without a frame on the stack
|
|
||||||
bson_frame leaf(nested, nested_size_pos);
|
|
||||||
write_bson_array_fixed_entries(leaf);
|
|
||||||
if (leaf.index == nested->m_data.m_value.array->size())
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parents.push_back(std::move(current));
|
|
||||||
current = std::move(leaf);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parents.push_back(std::move(current));
|
|
||||||
current = bson_frame(nested, nested_size_pos);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (parents.empty())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
current = std::move(parents.back());
|
|
||||||
parents.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Serializes the JSON object @a document as a BSON document
|
@brief Serializes the JSON object @a document as a BSON document
|
||||||
|
|
||||||
@@ -29791,39 +29608,6 @@ class binary_writer
|
|||||||
@pre document.type() == value_t::object
|
@pre document.type() == value_t::object
|
||||||
*/
|
*/
|
||||||
void write_bson_document(const BasicJsonType& document)
|
void write_bson_document(const BasicJsonType& document)
|
||||||
{
|
|
||||||
write_bson_document(document, bson_single_pass_sink());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the sink can overwrite: one pass, unless the error handler replaces or
|
|
||||||
/// drops ill-formed UTF-8, which changes the sizes the checks compute
|
|
||||||
void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/)
|
|
||||||
{
|
|
||||||
const std::size_t start = oa.written();
|
|
||||||
if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)
|
|
||||||
&& write_bson_document_single_pass(document))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// a length does not fit: start over, so that it is reported as the
|
|
||||||
// two passes report it, before anything is written
|
|
||||||
oa.rewind(start);
|
|
||||||
write_bson_document_two_pass(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/)
|
|
||||||
{
|
|
||||||
write_bson_document_two_pass(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Serializes the JSON object @a document as a BSON document: the
|
|
||||||
sizes of all objects and arrays first, then the entries
|
|
||||||
|
|
||||||
@param[in] document the JSON object to serialize
|
|
||||||
@pre document.type() == value_t::object
|
|
||||||
*/
|
|
||||||
void write_bson_document_two_pass(const BasicJsonType& document)
|
|
||||||
{
|
{
|
||||||
std::vector<std::size_t> nested_sizes;
|
std::vector<std::size_t> nested_sizes;
|
||||||
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
||||||
|
|||||||
+35
-131
@@ -1164,7 +1164,13 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
|||||||
// contiguous input reads a key up to its \x00-byte in one step, a stream
|
// contiguous input reads a key up to its \x00-byte in one step, a stream
|
||||||
// reads it byte by byte; both must give the same value or error for the
|
// reads it byte by byte; both must give the same value or error for the
|
||||||
// complete document and for every truncation of it
|
// complete document and for every truncation of it
|
||||||
const json j = {{"", true}, {"k", {1, 2, 3}}, {std::string(40, 'x'), {{"nested key", "value"}}}};
|
json j = {{"", true}, {"k", {1, 2, 3}}, {std::string(40, 'x'), {{"nested key", "value"}}}};
|
||||||
|
// keys and array element names ("0" to "11") that end at every position of
|
||||||
|
// an 8-byte word, which contiguous input scans at a time
|
||||||
|
for (std::size_t length = 1; length <= 17; ++length)
|
||||||
|
{
|
||||||
|
j[std::string(length, static_cast<char>('a' + length))] = json::array({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, {11}});
|
||||||
|
}
|
||||||
const std::vector<std::uint8_t> bson = json::to_bson(j);
|
const std::vector<std::uint8_t> bson = json::to_bson(j);
|
||||||
CHECK(json::from_bson(bson) == j);
|
CHECK(json::from_bson(bson) == j);
|
||||||
|
|
||||||
@@ -1184,7 +1190,7 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::istringstream stream(std::string(input.begin(), input.end()));
|
std::istringstream stream(std::string(reinterpret_cast<const char*>(input.data()), input.size()));
|
||||||
from_stream = json::from_bson(stream).dump();
|
from_stream = json::from_bson(stream).dump();
|
||||||
}
|
}
|
||||||
catch (const json::parse_error& e)
|
catch (const json::parse_error& e)
|
||||||
@@ -1194,6 +1200,33 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
|||||||
CHECK(from_vector == from_stream);
|
CHECK(from_vector == from_stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BSON array element names")
|
||||||
|
{
|
||||||
|
// an array's element names are not part of the value, so they are not
|
||||||
|
// passed on; error_handler_t::strict still rejects ill-formed UTF-8 in them
|
||||||
|
const std::vector<std::uint8_t> bson =
|
||||||
|
{
|
||||||
|
0x1D, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x04, 'a', 0x00, // entry: array "a"
|
||||||
|
0x15, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x08, 0xFF, 0x00, 0x01, // entry: boolean named "\xFF", true
|
||||||
|
0x08, '1', '2', '3', '4', '5', '6', '7', '8', '9', 0x00, 0x00, // entry: boolean named "123456789", false
|
||||||
|
0x00, // end of the array
|
||||||
|
0x00 // end of the document
|
||||||
|
};
|
||||||
|
const json expected = {{"a", {true, false}}};
|
||||||
|
|
||||||
|
CHECK(json::from_bson(bson) == expected);
|
||||||
|
CHECK(json::from_bson(bson, true, true, json::error_handler_t::replace) == expected);
|
||||||
|
CHECK(json::from_bson(bson, true, true, json::error_handler_t::ignore) == expected);
|
||||||
|
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(bson, true, true, json::error_handler_t::strict), "[json.exception.parse_error.113] parse error at byte 14: syntax error while parsing BSON key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||||
|
|
||||||
|
std::istringstream stream(std::string(reinterpret_cast<const char*>(bson.data()), bson.size()));
|
||||||
|
CHECK(json::from_bson(stream) == expected);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST_CASE("Negative size of binary value")
|
TEST_CASE("Negative size of binary value")
|
||||||
@@ -2267,132 +2300,3 @@ TEST_CASE("BSON large strings and binaries (chunked reader)")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BSON written in one pass and in two passes")
|
|
||||||
{
|
|
||||||
// to_bson() into a returned vector writes each size once the object or
|
|
||||||
// array is complete; through an output adapter, all sizes are computed
|
|
||||||
// first. Both must produce the same bytes and throw the same exceptions.
|
|
||||||
const auto two_pass = [](const json & j, const json::error_handler_t handler)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> result;
|
|
||||||
json::to_bson(j, result, handler);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto check_same = [&](const json & j, const json::error_handler_t handler)
|
|
||||||
{
|
|
||||||
CAPTURE(j.dump(-1, ' ', true, json::error_handler_t::replace))
|
|
||||||
CAPTURE(static_cast<int>(handler))
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
std::string one_pass_error;
|
|
||||||
std::string two_pass_error;
|
|
||||||
std::vector<std::uint8_t> one;
|
|
||||||
std::vector<std::uint8_t> two;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
one = json::to_bson(j, handler);
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
one_pass_error = e.what();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
two = two_pass(j, handler);
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
two_pass_error = e.what();
|
|
||||||
}
|
|
||||||
CHECK(one_pass_error == two_pass_error);
|
|
||||||
CHECK(one == two);
|
|
||||||
#else
|
|
||||||
CHECK(json::to_bson(j, handler) == two_pass(j, handler));
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
const json::error_handler_t handlers[] = {json::error_handler_t::keep, json::error_handler_t::strict,
|
|
||||||
json::error_handler_t::replace, json::error_handler_t::ignore
|
|
||||||
};
|
|
||||||
|
|
||||||
SECTION("corpus")
|
|
||||||
{
|
|
||||||
for (const auto& value : utils::round_trip_corpus::values())
|
|
||||||
{
|
|
||||||
const json document = value.is_object() ? value : json{{"value", value}};
|
|
||||||
for (const auto handler : handlers)
|
|
||||||
{
|
|
||||||
check_same(document, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("nesting and arrays of every length class")
|
|
||||||
{
|
|
||||||
json document = json::object();
|
|
||||||
document["empty object"] = json::object();
|
|
||||||
document["empty array"] = json::array();
|
|
||||||
for (const std::size_t n :
|
|
||||||
{
|
|
||||||
std::size_t(1), std::size_t(9), std::size_t(10), std::size_t(11), std::size_t(99), std::size_t(100), std::size_t(101), std::size_t(1000)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
json numbers = json::array();
|
|
||||||
json mixed = json::array();
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
numbers.push_back(i % 3 == 0 ? json(static_cast<double>(i) / 3) : json(static_cast<std::int64_t>(i) * 1000003));
|
|
||||||
mixed.push_back(i % 4 == 0 ? json("s" + std::to_string(i)) : i % 4 == 1 ? json::array({i, json::object({{"k", i}})}) : i % 4 == 2 ? json(nullptr) : json(i % 2 == 0));
|
|
||||||
}
|
|
||||||
document["numbers " + std::to_string(n)] = numbers;
|
|
||||||
document["mixed " + std::to_string(n)] = mixed;
|
|
||||||
document["nested " + std::to_string(n)] = json::object({{"a", numbers}, {"b", json::array({mixed, json::object()})}});
|
|
||||||
}
|
|
||||||
document["binary"] = json::binary({1, 2, 3}, 0x80);
|
|
||||||
document["binary old"] = json::binary({1, 2, 3}, 0x02);
|
|
||||||
for (const auto handler : handlers)
|
|
||||||
{
|
|
||||||
check_same(document, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("errors at every position")
|
|
||||||
{
|
|
||||||
// an entry that cannot be written, at each place among valid ones
|
|
||||||
const std::string bad_utf8 = "\xC3\x28";
|
|
||||||
const json bad_entries[] =
|
|
||||||
{
|
|
||||||
json::object({{std::string("a\0b", 3), 1}}),
|
|
||||||
json::object({{"s", bad_utf8}}),
|
|
||||||
json::object({{bad_utf8, 1}}),
|
|
||||||
json::array({1, 2, bad_utf8}),
|
|
||||||
json::object({{"b", json::binary({1}, 0x100)}}),
|
|
||||||
json::object({{"d", json(json::value_t::discarded)}}),
|
|
||||||
json::array({json(json::value_t::discarded)}),
|
|
||||||
};
|
|
||||||
for (const auto& bad : bad_entries)
|
|
||||||
{
|
|
||||||
for (std::size_t pos = 0; pos < 4; ++pos)
|
|
||||||
{
|
|
||||||
json document = json::object();
|
|
||||||
json list = json::array();
|
|
||||||
for (std::size_t i = 0; i < 4; ++i)
|
|
||||||
{
|
|
||||||
if (i == pos)
|
|
||||||
{
|
|
||||||
document["x" + std::to_string(i)] = bad;
|
|
||||||
list.push_back(bad);
|
|
||||||
}
|
|
||||||
document["n" + std::to_string(i)] = json::array({i, "str", json::object({{"k", i}})});
|
|
||||||
list.push_back(i);
|
|
||||||
}
|
|
||||||
document["list"] = list;
|
|
||||||
for (const auto handler : handlers)
|
|
||||||
{
|
|
||||||
check_same(document, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user