mirror of
https://github.com/nlohmann/json.git
synced 2026-10-25 15:28:54 +00:00
* Add shared detail helpers for the #5891 cleanup Add detail::is_primitive(value_t), detail::hex4_lower(), detail::utf8_shape(), detail::input_format_name(), detail::empty_input_message(), the exception ids 116, 319, 320 and 416 to detail::exception_id, and make detail::is_ascii_copyable() constexpr. Later commits switch the duplicated code over to them. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.4: BSON writers share one write-pass UTF-8 sanitizer (#5891) write_bson_entry_header and write_bson_string re-derived the error-handler policy differently. Both now use sanitize_utf8_for_bson_write, which treats strict like keep (the size pass already validated) and sanitizes replace/ignore into the bytes the size pass measured. No context plumbing is needed. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.1: one MessagePack container prefix helper (#5891) Array and object prefixes differed only in the fix marker and the 16/32-bit markers; both now delegate to write_msgpack_container_prefix. Output is byte-identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.2: one iterative walker for CBOR and MessagePack (#5891) write_cbor_iterative and write_msgpack_iterative (and their *_value_or_push helpers) were identical but for the head, scalar, and key writers. They are now one template over a small policy struct, so everything inlines. BON8 keeps its own walker (open string run, closing marker), noted in a comment. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.3, 2.1.8: one UBJSON container start helper, shared frame base (#5891) write_ubjson_start_array/_object are now write_ubjson_start_container<IsArray>, keeping the array-only begin()+1 scan and excessive_valueless rule. ubjson_frame derives from binary_container_frame instead of duplicating it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.4: write_bytes helper for the byte-string write sites (#5891) The 13 oa.write_characters(reinterpret_cast<const CharType*>(x.data()), n) sites now call write_bytes(const void*, size_t); copy_characters sites and the buffer-based writes are untouched. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.5: shared object-key writer for CBOR and MessagePack (#5891) write_cbor_key and write_msgpack_key were the same SFINAE pair apart from the string/scalar writers; both now delegate to write_object_key<Walk>. UBJSON key and to_format_length stay as they are. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.10: writer-side format names from input_format_name (#5891) The CBOR/MessagePack/UBJSON/BSON/BJData/BON8 literals in throw_on_discarded and throw_integer_out_of_range calls now come from detail::input_format_name; the texts are identical, and UBJSON vs BJData still follows use_bjdata. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * serializer: constexpr ascii table, hex4_lower, dedupe helpers (#5891) 1.1: build ascii_copyable() at compile time from is_ascii_copyable and test find_ascii_copyable_run against it for every byte. 1.2/2.5.4: use detail::hex4_lower in both \uXXXX writers. 2.1.6: factor the repeated flush/emit blocks in dump_escaped_impl into always-inline helpers. 2.1.7: comment why the escape copy does not use put_buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.6: use string_t-aware to_string/concat in meta() (#5891) Replaces the four std::to_string sites; rendered text unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: use detail::is_primitive in json.hpp switches (#5891) has_no_children, set_parents, max_size and the two diff walkers. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.1: factor insert()/iterator throw helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.2: share swap() overloads via swap_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.4: factor primitive reset and range check in erase()/range ctor (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.6: share push_back array bodies via push_back_array_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.7: route parse/accept/sax_parse through *_impl helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.8: share the vector-returning to_* bodies via to_binary_vector (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.10: merge destroy_string/destroy_binary into destroy_pointer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.1/1.7: exceptions: share what_impl, flatten diagnostics switch (#5891) The four create() bodies build their message through one protected static helper; the diagnostics parent walk handles array/object and ignores all other types. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.2/2.4.3/1.7: json_pointer: share slash and escape checks, drop primitive case lists (#5891) The leading-slash and invalid '~' escape checks are shared helpers; the reference-token walks test object and array first and treat everything else as primitive. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7/2.4.9: iter_impl, iteration_proxy: drop primitive case lists, share compare preamble (#5891) Object and array stay the first branches; everything else is the primitive case. operator== and operator< share the different-container check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5/2.4.6: from_json: share get_number_value and reserve_if_possible (#5891) The three number cases of get_arithmetic_value and the arithmetic from_json share one helper (boolean stays accepted only by the latter); the array and object reserve overload pairs become one. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.8: ordered_map: share erase_impl between the key erase overloads (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: json_pointer: flatten walk drops the primitive case list (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.2/1.3/1.5/2.5.1/2.5.2/2.5.5: share helpers in the view code (#5891) - detail::exception_id at every numeric id; throw_* helpers take it - hex_byte, hex4_lower, utf8_shape replace local hex/table code - throw_array_index_out_of_range/_past_end, throw_key_not_found - has_native_float_format replaces the hand-written IEEE check No change of messages, ids or output. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: use utf8_shape in get_bon8_string, add exhaustive UTF-8 validator test (#5891) get_bon8_string takes the valid range of the second byte from utf8_shape() (0xF5..0xF7 stay errors, unget behavior unchanged). validate_one_utf8 keeps its if-chain (hot bulk path); the new test checks decode, validate_one_utf8, utf8_shape, json::accept and the BON8 reader against each other for every lead and second byte. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.1: get_cbor_sized_bytes for the CBOR chunk getters (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.2: get_string_with_length for the plain-order string sites (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.3, 2.3.4, 2.3.10: unsupported_key_type, empty_input_message, input_format_name in the readers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.7, 2.3.9: read_object_key and leave_container_and_check for CBOR/MessagePack/BON8 (#5891) UBJSON keys (different getter signature) and its container tail stay as is. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.5: json_sax_dom_error_state base for the DOM SAX parsers (#5891) Holds errored, allow_exceptions, parse_error() and is_errored(). The scalar handlers stay in each class (parse hot path). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.6: table of control-character messages in the lexer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.8: finish_input helper for the strict-EOF block in the parser (#5891) sax_parse keeps its if (result) guard and return value. Also adds the clang-tidy NOLINT for the 2.3.6 message table. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: share parse_array_index between json_pointer and json_view (#5891) Move json_pointer::parse_array_index() and its status enum to namespace detail (json_pointer keeps a forwarding member), and let the json_view pointer resolution use it instead of its own copy of the RFC 6901 index grammar. Both accepted the same tokens with the same outcome. Also update the comment on detail::decode(), which listed four of the five UTF-8 validators and now points to the test that keeps them in sync. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5: keep the inlined number cases in from_json(ArithmeticType&) (#5891) Calling get_number_value() from from_json(ArithmeticType&) made GetArray<std::array<int, 1000>> 2.4x slower (it changes the inlining of the std::array conversion), so that overload keeps its own switch, with a comment. get_arithmetic_value() still uses the shared helper. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: force-inline detail::parse_array_index (#5891) Moving parse_array_index out of json_pointer stopped clang from inlining it into the pointer resolution (JsonPointerAt/citm_catalog +17%, JsonPointerAt/twitter +8%); JSON_HEDLEY_ALWAYS_INLINE restores the baseline. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: keep get_bon8_string's own UTF-8 ranges (#5891) Taking the second-byte range from utf8_shape() made BON8 decoding 2-3% slower (FromBinaryShape/scalars, FromBinaryBuffer, alignment-pinned builds), so the byte path keeps its hand-written ranges. The exhaustive validator test still checks that it accepts the same sequences as the other validators. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI failures: constexpr, NRVO, cppcheck, IWYU (#5891) - json_sax_dom_error_state::is_errored() cannot be constexpr: the class is not a literal type (GCC 4.8, GCC -Wpedantic, Clang 3.5). - to_binary_vector: destroy the vector writer before returning, as the output_vector_sink sets the size of the vector in its destructor. Without NRVO (MSVC Debug in C++14 mode), the vector was moved out before it was resized. - parser::sax_parse: return false instead of result after the early exit (cppcheck identicalConditionAfterEarlyExit). - json.hpp: export detail/exceptions.hpp for IWYU, so json_view.hpp can name exception_id without including a detail header. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: throw helpers take input_format_t, simpler type checks - throw_on_discarded() and throw_integer_out_of_range() take an input_format_t and call input_format_name() themselves - has_no_children(), set_parents() and max_size() check array, then object, instead of calling is_primitive() first - parse_impl() chains like accept_impl(); sax_parse_impl() uses if/else - std::array for the control character messages; negation<> in write_object_key(); clarify the is_primitive() comment Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_view: use exception_id in the value() overloads Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_sax: assert the invariant of place_container for the static analyzer Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
397 lines
14 KiB
C++
397 lines
14 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2008, 2009 Björn Hoehrmann <bjoern@hoehrmann.de>
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <array> // array
|
|
#include <cstddef> // size_t
|
|
#include <cstdint> // uint8_t, uint32_t
|
|
#include <string> // string, to_string
|
|
|
|
#include <nlohmann/detail/abi_macros.hpp>
|
|
#include <nlohmann/detail/macro_scope.hpp>
|
|
#include <nlohmann/detail/output/error_handler.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
|
|
template<typename StringType>
|
|
void int_to_string(StringType& target, std::size_t value)
|
|
{
|
|
// For ADL
|
|
using std::to_string;
|
|
target = to_string(value);
|
|
}
|
|
|
|
template<typename StringType>
|
|
StringType to_string(std::size_t value)
|
|
{
|
|
StringType result;
|
|
int_to_string(result, value);
|
|
return result;
|
|
}
|
|
|
|
/// @return a byte as two uppercase hexadecimal digits
|
|
inline std::string hex_byte(const std::uint8_t byte)
|
|
{
|
|
std::string result = "00";
|
|
constexpr const char* nibble_to_hex = "0123456789ABCDEF";
|
|
result[0] = nibble_to_hex[byte / 16];
|
|
result[1] = nibble_to_hex[byte % 16];
|
|
return result;
|
|
}
|
|
|
|
/*!
|
|
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
|
|
|
|
The lowercase counterpart of hex_byte(), used for the `\\uXXXX` escapes of the
|
|
serializers, whose output is lowercase. Writes exactly four characters to
|
|
@a out and returns the pointer past them.
|
|
*/
|
|
JSON_HEDLEY_ALWAYS_INLINE char* hex4_lower(const std::uint16_t codeunit, char* out) noexcept
|
|
{
|
|
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
|
const unsigned int unit = codeunit;
|
|
out[0] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
|
out[1] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
|
out[2] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
|
out[3] = nibble_to_hex[unit & 0x0Fu];
|
|
return out + 4;
|
|
}
|
|
|
|
///////////////////
|
|
// UTF-8 encoding //
|
|
///////////////////
|
|
|
|
/*!
|
|
@brief encode a Unicode code point as UTF-8
|
|
|
|
Used to turn a decoded code point back into bytes: by the wide-string input
|
|
adapters in input_adapters.hpp (one code point per UTF-32 unit, per UTF-16
|
|
unit outside the surrogate range, and per valid UTF-16 surrogate pair), and
|
|
by the lexer's handling of u-escapes and surrogate pairs in lexer.hpp. Passing a
|
|
code point above U+10FFFF, or one in the surrogate range U+D800..U+DFFF, is
|
|
undefined behavior; callers are expected to have rejected those already
|
|
(the wide-string adapters pass malformed units through unencoded instead of
|
|
calling this function, and the lexer rejects unpaired surrogates before
|
|
reaching it).
|
|
|
|
@tparam Out a callable invoked with one byte (as std::uint32_t, 0x00..0xFF)
|
|
at a time, most significant byte first
|
|
@param[in] cp the code point to encode (at most U+10FFFF)
|
|
@param[in] out called once for each byte of the UTF-8 encoding of @a cp
|
|
*/
|
|
template<typename Out>
|
|
void encode_utf8(std::uint32_t cp, const Out& out)
|
|
{
|
|
JSON_ASSERT(cp <= 0x10FFFF);
|
|
|
|
if (cp < 0x80)
|
|
{
|
|
// 1-byte characters: 0xxxxxxx (ASCII)
|
|
out(cp);
|
|
}
|
|
else if (cp <= 0x7FF)
|
|
{
|
|
// 2-byte characters: 110xxxxx 10xxxxxx
|
|
out(0xC0u | (cp >> 6u));
|
|
out(0x80u | (cp & 0x3Fu));
|
|
}
|
|
else if (cp <= 0xFFFF)
|
|
{
|
|
// 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
|
|
out(0xE0u | (cp >> 12u));
|
|
out(0x80u | ((cp >> 6u) & 0x3Fu));
|
|
out(0x80u | (cp & 0x3Fu));
|
|
}
|
|
else
|
|
{
|
|
// 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
|
out(0xF0u | (cp >> 18u));
|
|
out(0x80u | ((cp >> 12u) & 0x3Fu));
|
|
out(0x80u | ((cp >> 6u) & 0x3Fu));
|
|
out(0x80u | (cp & 0x3Fu));
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@brief the shape of a well-formed UTF-8 sequence, by its lead byte
|
|
|
|
@a length is 1 for ASCII, 2 to 4 for a valid lead byte, and 0 for a byte that
|
|
cannot start a well-formed sequence (a continuation byte, 0xC0, 0xC1, or 0xF5
|
|
to 0xFF). For a multibyte sequence, the second byte must lie in
|
|
[@a second_lo, @a second_hi], and every later byte in [0x80, 0xBF]
|
|
(RFC 3629, section 4; Unicode Table 3-7). The narrower second-byte ranges
|
|
exclude overlong forms (0xE0, 0xF0), surrogates (0xED), and code points
|
|
beyond U+10FFFF (0xF4).
|
|
*/
|
|
struct utf8_sequence_shape
|
|
{
|
|
std::uint8_t length;
|
|
std::uint8_t second_lo;
|
|
std::uint8_t second_hi;
|
|
};
|
|
|
|
/// @return the shape of a UTF-8 sequence starting with @a lead, see utf8_sequence_shape
|
|
inline utf8_sequence_shape utf8_shape(const std::uint8_t lead) noexcept
|
|
{
|
|
if (lead < 0x80u)
|
|
{
|
|
return {1, 0, 0};
|
|
}
|
|
if (lead < 0xC2u)
|
|
{
|
|
return {0, 0, 0};
|
|
}
|
|
if (lead <= 0xDFu)
|
|
{
|
|
return {2, 0x80u, 0xBFu};
|
|
}
|
|
if (lead == 0xE0u)
|
|
{
|
|
return {3, 0xA0u, 0xBFu};
|
|
}
|
|
if (lead == 0xEDu)
|
|
{
|
|
return {3, 0x80u, 0x9Fu};
|
|
}
|
|
if (lead <= 0xEFu)
|
|
{
|
|
return {3, 0x80u, 0xBFu};
|
|
}
|
|
if (lead == 0xF0u)
|
|
{
|
|
return {4, 0x90u, 0xBFu};
|
|
}
|
|
if (lead <= 0xF3u)
|
|
{
|
|
return {4, 0x80u, 0xBFu};
|
|
}
|
|
if (lead == 0xF4u)
|
|
{
|
|
return {4, 0x80u, 0x8Fu};
|
|
}
|
|
return {0, 0, 0};
|
|
}
|
|
|
|
///////////////////
|
|
// UTF-8 decoding //
|
|
///////////////////
|
|
|
|
// UTF-8 decoder states used by decode() below
|
|
JSON_INLINE_VARIABLE constexpr std::uint8_t UTF8_ACCEPT = 0;
|
|
JSON_INLINE_VARIABLE constexpr std::uint8_t UTF8_REJECT = 1;
|
|
|
|
/*!
|
|
@brief process a byte of a UTF-8 sequence
|
|
|
|
This is a single-byte step of a "shift-based" UTF-8 decoder originally
|
|
written by Björn Hoehrmann. See
|
|
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
|
|
|
The library checks UTF-8 well-formedness (RFC 3629, section 4) in five
|
|
places, which differ in speed, diagnostics, and how they read the input:
|
|
|
|
- decode() below: the serializer, to escape and, in strict mode, reject
|
|
ill-formed UTF-8 when dumping a string. The CBOR, MessagePack, BSON,
|
|
UBJSON and BJData readers do not use it: none of those specs requires a
|
|
decoder to reject ill-formed UTF-8 in text strings, so the readers keep
|
|
the bytes as is and leave the check to dump() and the binary writers.
|
|
- the per-lead-byte switch in lexer::scan_string(): JSON text, with a
|
|
diagnostic for each kind of error.
|
|
- validate_one_utf8() and valid_utf8_prefix() in string_scan.hpp: the lexer's
|
|
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
|
|
They must accept exactly what the lexer's switch accepts.
|
|
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
|
|
access, and the bytes the bulk path leaves to it. It keeps its own ranges:
|
|
taking them from utf8_shape() made BON8 decoding 2-3% slower (#5891).
|
|
- check_utf8() in detail/view/edit.hpp: strings stored in an editable
|
|
json_document, with the index of the offending byte. It also uses
|
|
utf8_shape().
|
|
|
|
All five must accept the same set of sequences, which the test "UTF-8
|
|
validators accept the same sequences" in unit-class_lexer.cpp checks
|
|
exhaustively against decode() and utf8_shape().
|
|
|
|
@param[in,out] state the current decoder state
|
|
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
|
@param[in] byte next byte to decode
|
|
@return new state
|
|
|
|
@note Original source: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
|
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
|
*/
|
|
inline std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept
|
|
{
|
|
static const std::array<std::uint8_t, 400> utf8d =
|
|
{
|
|
{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
|
|
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
|
|
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
|
|
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
|
|
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
|
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
|
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
|
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
|
|
}
|
|
};
|
|
|
|
JSON_ASSERT(static_cast<std::size_t>(byte) < utf8d.size());
|
|
const std::uint8_t type = utf8d[byte];
|
|
|
|
codep = (state != UTF8_ACCEPT)
|
|
? (byte & 0x3fu) | (codep << 6u)
|
|
: (0xFFu >> type) & (byte);
|
|
|
|
const std::size_t index = 256u + (static_cast<std::size_t>(state) * 16u) + static_cast<std::size_t>(type);
|
|
JSON_ASSERT(index < utf8d.size());
|
|
state = utf8d[index];
|
|
return state;
|
|
}
|
|
|
|
/*!
|
|
@brief check a string for well-formed UTF-8 (RFC 3629, section 4)
|
|
|
|
Used by the binary readers (CBOR, MessagePack, UBJSON, BJData, BSON) when an
|
|
@ref error_handler_t other than `keep` is requested for a text string value
|
|
or object key: none of those formats requires a decoder to reject ill-formed
|
|
UTF-8 on its own, so the check is opt-in there, unlike the JSON lexer and the
|
|
serializer's @ref decode -based escaping, which always run it.
|
|
|
|
@param[in] s the string to check
|
|
@param[in] first the index to start checking at
|
|
@return whether `s.substr(first)` is well-formed UTF-8
|
|
|
|
@sa @ref decode
|
|
*/
|
|
template<typename StringType>
|
|
inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noexcept
|
|
{
|
|
std::uint8_t state = UTF8_ACCEPT;
|
|
std::uint32_t codepoint = 0;
|
|
|
|
for (std::size_t i = first; i < s.size(); ++i)
|
|
{
|
|
decode(state, codepoint, static_cast<std::uint8_t>(s[i]));
|
|
if (state == UTF8_REJECT)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return state == UTF8_ACCEPT;
|
|
}
|
|
|
|
/*!
|
|
@brief sanitize a string with ill-formed UTF-8 for @ref error_handler_t::replace or @ref error_handler_t::ignore
|
|
|
|
Replaces every maximal ill-formed subsequence with U+FFFD (`replace`) or
|
|
drops it (`ignore`), using exactly the same boundaries @ref
|
|
serializer::dump_escaped_impl uses while escaping a string: a byte that does
|
|
not extend the sequence started by the previous byte(s) is reread as the
|
|
start of a new one, instead of being swallowed along with them.
|
|
|
|
@pre @a error_handler is @ref error_handler_t::replace or @ref error_handler_t::ignore
|
|
@note Well-formed input is copied through unchanged, including bytes (e.g.
|
|
control characters or quotes) that @ref serializer::dump_escaped_impl
|
|
would itself escape; this function only concerns itself with
|
|
well-formedness, not with producing valid JSON text.
|
|
|
|
@param[in] s the string to sanitize
|
|
@param[in] error_handler @ref error_handler_t::replace or @ref error_handler_t::ignore
|
|
|
|
@return @a s with every ill-formed subsequence replaced or removed
|
|
|
|
@sa @ref decode
|
|
*/
|
|
template<typename StringType>
|
|
inline StringType sanitize_utf8(const StringType& s, const error_handler_t error_handler)
|
|
{
|
|
JSON_ASSERT(error_handler == error_handler_t::replace || error_handler == error_handler_t::ignore);
|
|
|
|
StringType result;
|
|
result.reserve(s.size());
|
|
|
|
std::uint32_t codepoint = 0;
|
|
std::uint8_t state = UTF8_ACCEPT;
|
|
// length of result after the last accepted code point
|
|
std::size_t result_len_after_last_accept = 0;
|
|
// whether bytes of an as yet unresolved sequence were already appended
|
|
bool pending = false;
|
|
|
|
for (std::size_t i = 0; i < s.size(); ++i)
|
|
{
|
|
switch (decode(state, codepoint, static_cast<std::uint8_t>(s[i])))
|
|
{
|
|
case UTF8_ACCEPT: // decode found a well-formed code point
|
|
{
|
|
result.push_back(s[i]);
|
|
result_len_after_last_accept = result.size();
|
|
pending = false;
|
|
break;
|
|
}
|
|
|
|
case UTF8_REJECT: // decode found an ill-formed byte
|
|
{
|
|
// in case we saw this byte for the first time, read it again,
|
|
// because it may be fine for itself, just not for the
|
|
// sequence that came before it
|
|
if (pending)
|
|
{
|
|
--i;
|
|
}
|
|
|
|
// drop the bytes of the ill-formed sequence buffered below
|
|
result.resize(result_len_after_last_accept);
|
|
|
|
if (error_handler == error_handler_t::replace)
|
|
{
|
|
result.append("\xEF\xBF\xBD");
|
|
result_len_after_last_accept = result.size();
|
|
}
|
|
|
|
pending = false;
|
|
state = UTF8_ACCEPT;
|
|
break;
|
|
}
|
|
|
|
default: // decode found yet incomplete multibyte code point
|
|
{
|
|
result.push_back(s[i]);
|
|
pending = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// the string ended with an incomplete sequence
|
|
if (state != UTF8_ACCEPT)
|
|
{
|
|
result.resize(result_len_after_last_accept);
|
|
|
|
if (error_handler == error_handler_t::replace)
|
|
{
|
|
result.append("\xEF\xBF\xBD");
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|