Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3d39a9c10 | ||
|
|
24ff6eaa43 | ||
|
|
9d9bf927ff | ||
|
|
cc39e1f8da | ||
|
|
5ac95db5a9 | ||
|
|
d1d142633a | ||
|
|
49e9eaac4f | ||
|
|
4b35877ae5 | ||
|
|
34f9be2aa2 | ||
|
|
8c4818d5f7 | ||
|
|
df1bca6dae | ||
|
|
b22cad367c | ||
|
|
95169a63fa |
No files matched your search
@@ -90,8 +90,9 @@ type `#!cpp binary_t*` must be dereferenced.
|
||||
|
||||
- MessagePack
|
||||
- If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1,
|
||||
fixext2, fixext4, fixext8) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The subtype is
|
||||
then added as a signed 8-bit integer.
|
||||
fixext2, fixext4, fixext8, fixext16) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The
|
||||
subtype is then written as the signed 8-bit ext type: subtypes 128 to 255 map to the negative (reserved) ext
|
||||
types, and on reading the ext type is exposed as an unsigned subtype from 0 to 255.
|
||||
- If no subtype is given, the bin family (bin8, bin16, bin32) is used.
|
||||
|
||||
- BSON
|
||||
|
||||
@@ -15,11 +15,13 @@ error
|
||||
: report a parse error in case of a tag (the `from_cbor` overloads throw a `parse_error` exception by default)
|
||||
|
||||
ignore
|
||||
: ignore tags
|
||||
: ignore tags (also in front of a text string map key)
|
||||
|
||||
store
|
||||
: store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are
|
||||
read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored.
|
||||
read as if the tag were ignored. If several tags precede a byte string, only the tag directly in front of it is
|
||||
stored, and only when it has the form 0xd8..0xdb (a tag 0..23 written as 0xc0..0xd7 is skipped). Tags in front of a
|
||||
text string map key are skipped.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -32,8 +32,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
||||
|
||||
- an `std::istream` object
|
||||
- a `FILE` pointer
|
||||
- a C-style array of characters
|
||||
- a pointer to a null-terminated string of single byte characters
|
||||
- a C-style array of characters (its terminating NUL is read as trailing data: `parse_error.110` unless `strict` is
|
||||
`false`)
|
||||
- a pointer to a null-terminated string of single byte characters (read up to the first 0x00 byte, which is a valid
|
||||
CBOR byte)
|
||||
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
||||
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
|
||||
|
||||
@@ -89,8 +91,14 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
|
||||
used in the given input or if the input is not valid CBOR
|
||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of
|
||||
other types are not supported, as JSON object keys are always strings), or if a string value or object key is not
|
||||
valid UTF-8 and `error_handler` is `strict`
|
||||
other types are not supported, as JSON object keys are always strings), if the head of a string or byte string is
|
||||
malformed (including a chunk of the wrong type or an indefinite-length string nested inside an indefinite-length
|
||||
string), or if a string value or object key (or one chunk of an indefinite-length string) is not valid UTF-8 and
|
||||
`error_handler` is `strict`
|
||||
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if an array or map announces
|
||||
a length that `std::size_t` cannot hold, or that equals `std::size_t(-1)` (for example `9B FF FF FF FF FF FF FF FF`)
|
||||
- Throws [out_of_range.406](../../home/exceptions.md#jsonexceptionout_of_range406) if a floating-point number does not
|
||||
fit into `number_float_t`
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -79,11 +79,18 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
- Throws [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or
|
||||
the end of the file was not reached when `strict` was set to true
|
||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs
|
||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if an invalid type marker is read
|
||||
(including a type marker after `$` of an optimized container, even if its count is 0), or if `$` is not followed by `#`
|
||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed
|
||||
successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict`
|
||||
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container
|
||||
or n-dimensional array cannot be represented by `std::size_t`
|
||||
successfully (negative or non-integer length, `C` followed by a byte above 0x7F), if the count of an optimized
|
||||
container is negative, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict`
|
||||
- Throws [parse_error.115](../../home/exceptions.md#jsonexceptionparse_error115) if the text of a high-precision number
|
||||
(`H`) is not a valid JSON number
|
||||
- Throws [out_of_range.406](../../home/exceptions.md#jsonexceptionout_of_range406) if a high-precision number, or a
|
||||
float64 when `number_float_t` is `float`, does not fit into `number_float_t`
|
||||
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the count of an optimized
|
||||
container exceeds the capacity of the container (or of `std::size_t`), or if an optimized array of `Z`, `T`, or `F` has
|
||||
more than 1,048,576 elements
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -108,7 +108,6 @@ is thrown. In any case, the original value is not changed: the patch is applied
|
||||
- Added in version 2.0.0.
|
||||
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
||||
target location has a non-object/non-array parent in version 3.13.0.
|
||||
This includes a `null` parent, which was previously silently converted to an object, in version 3.13.0.
|
||||
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
|
||||
location has a non-object/non-array parent in version 3.13.0.
|
||||
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
|
||||
|
||||
@@ -106,7 +106,6 @@ function throws an exception.
|
||||
- Added in version 3.11.0.
|
||||
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
||||
target location has a non-object/non-array parent in version 3.13.0.
|
||||
This includes a `null` parent, which was previously silently converted to an object, in version 3.13.0.
|
||||
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
|
||||
location has a non-object/non-array parent in version 3.13.0.
|
||||
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
|
||||
|
||||
@@ -42,7 +42,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value. Bytes that were already written to
|
||||
the output adapter are not taken back.
|
||||
|
||||
## Exceptions
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
|
||||
`"MessagePack length 4294967296 exceeds maximum of 4294967295"`
|
||||
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
|
||||
exceeds 255, the maximum of the MessagePack ext type; example:
|
||||
exceeds 255, the largest value the library can store in the one-byte MessagePack ext type; example:
|
||||
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict`
|
||||
|
||||
@@ -11,7 +11,7 @@ extremely small code sizes, fairly small message size, and extensibility without
|
||||
|
||||
## Byte layout
|
||||
|
||||
CBOR encodes every data item as a *head* followed by optional content. The first byte of the head holds the *major type* in its upper three bits (unsigned integer, negative integer, byte string, text string, array, map, tag, or simple/float) and an *additional information* value in its lower five bits. Additional information below 24 is the argument itself (for example, a small integer or a short length); the values 24 to 27 announce that the argument follows in 1, 2, 4, or 8 big-endian bytes. Strings, arrays, and maps are prefixed with their length (or count of elements/pairs), and content follows directly, so a reader always knows how many items to expect without a terminator.
|
||||
CBOR encodes every data item as a *head* followed by optional content. The first byte of the head holds the *major type* in its upper three bits (unsigned integer, negative integer, byte string, text string, array, map, tag, or simple/float) and an *additional information* value in its lower five bits. Additional information below 24 is the argument itself (for example, a small integer or a short length); the values 24 to 27 announce that the argument follows in 1, 2, 4, or 8 big-endian bytes. Strings, arrays, and maps are prefixed with their length (or count of elements/pairs), and content follows directly. A reader of definite-length items knows how many items to expect without a terminator; indefinite-length items (additional information 31) are instead closed by the "break" byte 0xFF, which `from_cbor` reads but `to_cbor` never writes.
|
||||
|
||||
Consider the following JSON value (shown formatted for readability):
|
||||
|
||||
@@ -52,16 +52,16 @@ The library uses the following mapping from JSON values types to CBOR types acco
|
||||
| null | `null` | Null | 0xF6 |
|
||||
| boolean | `true` | True | 0xF5 |
|
||||
| boolean | `false` | False | 0xF4 |
|
||||
| number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B |
|
||||
| number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A |
|
||||
| number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 |
|
||||
| number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 |
|
||||
| number_integer | -9223372036854775808..-4294967297 | Negative integer (8 bytes follow) | 0x3B |
|
||||
| number_integer | -4294967296..-65537 | Negative integer (4 bytes follow) | 0x3A |
|
||||
| number_integer | -65536..-257 | Negative integer (2 bytes follow) | 0x39 |
|
||||
| number_integer | -256..-25 | Negative integer (1 byte follow) | 0x38 |
|
||||
| number_integer | -24..-1 | Negative integer | 0x20..0x37 |
|
||||
| number_integer | 0..23 | Integer | 0x00..0x17 |
|
||||
| number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 |
|
||||
| number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 |
|
||||
| number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A |
|
||||
| number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B |
|
||||
| number_integer | 4294967296..9223372036854775807 | Unsigned integer (8 bytes follow) | 0x1B |
|
||||
| number_unsigned | 0..23 | Integer | 0x00..0x17 |
|
||||
| number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 |
|
||||
| number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 |
|
||||
@@ -95,7 +95,24 @@ see "binary" cells in the table above.
|
||||
|
||||
!!! success "Complete mapping"
|
||||
|
||||
The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value.
|
||||
The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value, with one
|
||||
exception: an integer outside the range of CBOR integers (only possible with a number type wider than 64 bits)
|
||||
throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407).
|
||||
|
||||
!!! info "Not a deterministic encoding"
|
||||
|
||||
`to_cbor` does not produce the deterministic encoding of
|
||||
[RFC 8949, Section 4.2.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-4.2.1), and not always the preferred
|
||||
serialization of Section 4.1:
|
||||
|
||||
- floating-point numbers are written as single precision (0xFA) when that is exact and as double precision (0xFB)
|
||||
otherwise; half precision (0xF9) is used only for `NaN` and `Infinity`/`-Infinity` (`1.0` is `fa 3f 80 00 00`);
|
||||
- a binary subtype 0..23 is written as `d8 xx` instead of the one-byte tag 0xC0..0xD7 (`d8 01 41 01`);
|
||||
- object members are written in the order of `object_t` (for the default `std::map`: sorted by the key string, not
|
||||
by the encoded key bytes).
|
||||
|
||||
Integers and lengths use the shortest form, and indefinite-length items are never written. Decoders must accept all
|
||||
of this; the RFC does not require the preferred serialization.
|
||||
|
||||
!!! info "NaN/infinity handling"
|
||||
|
||||
@@ -105,9 +122,8 @@ see "binary" cells in the table above.
|
||||
|
||||
!!! note
|
||||
|
||||
Prior to version 3.13.0, NaN and Infinity were instead serialized as a CBOR double-precision float (type 0xFB,
|
||||
9 bytes total), because the check used to select a smaller encoding compared magnitudes with NaN, which is
|
||||
always `false` and caused the intended half-precision path to be skipped.
|
||||
Prior to version 3.8.0, NaN and Infinity were instead serialized as a CBOR double-precision float (type 0xFB,
|
||||
9 bytes total).
|
||||
|
||||
!!! info "Unused CBOR types"
|
||||
|
||||
@@ -124,7 +140,6 @@ see "binary" cells in the table above.
|
||||
- expected conversions (0xD5..0xD7)
|
||||
- simple values (0xE0..0xF3, 0xF8)
|
||||
- undefined (0xF7)
|
||||
- half-precision floats (0xF9)
|
||||
- break (0xFF)
|
||||
|
||||
!!! info "Tagged items"
|
||||
@@ -226,10 +241,20 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https://
|
||||
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
|
||||
general-purpose CBOR library instead.
|
||||
|
||||
A tag in front of a text string key is handled like a tag in front of a value: it is skipped with
|
||||
`cbor_tag_handler_t::ignore` or `cbor_tag_handler_t::store` and rejected with the default `error`. A tag does not
|
||||
turn a key of another type into a text string.
|
||||
|
||||
A map with duplicate keys is well-formed, but invalid under
|
||||
[RFC 8949, Section 5.3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1). `from_cbor` accepts it and the
|
||||
last value wins; the [SAX interface](../parsing/sax_interface.md) reports every occurrence.
|
||||
|
||||
!!! warning "Ill-formed UTF-8 in text strings"
|
||||
|
||||
[RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings (major
|
||||
type 3) to be valid UTF-8, but leaves it up to the decoder whether to enforce this, so checking is opt-in: with the
|
||||
type 3) to be valid UTF-8 (a string with ill-formed UTF-8 is "well-formed but invalid"), and
|
||||
[Section 5.3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1) leaves it up to the decoder whether to
|
||||
verify this, so checking is opt-in: with the
|
||||
[`error_handler`](../../api/basic_json/from_cbor.md) parameter left at `keep` (the default), `from_cbor()` accepts a
|
||||
text string (object keys included) whose bytes are not valid UTF-8 and hands them back unchanged. Passing
|
||||
`error_handler_t::strict` makes `from_cbor()` check and throw
|
||||
@@ -242,9 +267,14 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https://
|
||||
[`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception
|
||||
instead. Byte strings (major type 2) are unaffected, since they are not required to hold text.
|
||||
|
||||
The chunks of an indefinite-length text string are checked one by one, as required by
|
||||
[RFC 8949, Section 3.2.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3): a code point cannot be spread
|
||||
over two chunks. With `error_handler_t::strict` such a string throws `parse_error.113`; `replace` and `ignore`
|
||||
sanitize each chunk on its own.
|
||||
|
||||
!!! warning "Tagged items"
|
||||
|
||||
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. Passing `cbor_tag_handler_t::store` to function `from_cbor` stores tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
|
||||
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. Passing `cbor_tag_handler_t::store` to function `from_cbor` stores tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the tag directly in front of it is stored, and only when it has the form 0xD8..0xDB (a tag 0..23 written as 0xC0..0xD7 is skipped); for example, `D8 05 C1 41 AA` yields subtype `null`, whereas `C1 D8 05 41 AA` yields 5. Tags in front of a text string map key are skipped as well. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
|
||||
|
||||
??? example "Example: deserialize a JSON value from CBOR"
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
| BON8 | incomplete: no unsigned integers above int64 | complete |
|
||||
| BSON | incomplete: top-level value must be an object | incomplete, but all JSON types are supported |
|
||||
| CBOR | complete | incomplete, but all JSON types are supported |
|
||||
| MessagePack | complete | complete |
|
||||
| MessagePack | complete | incomplete, but all JSON types are supported |
|
||||
| UBJSON | complete | complete |
|
||||
|
||||
### Binary values
|
||||
|
||||
@@ -11,7 +11,7 @@ extra byte in addition to the strings themselves.
|
||||
|
||||
## Byte layout
|
||||
|
||||
MessagePack encodes each value as a one-byte *format marker*, optionally followed by a length or the payload. The marker range is partitioned to keep common values small: small non-negative integers (0 to 127) and small negative integers (-32 to -1) are the marker byte itself; short strings, arrays, and maps (up to 31 or 15 elements) hide their length in the marker's lower bits. Larger values use dedicated markers that announce the width (8, 16, 32, or 64 bits) of the length or number that follows, in big-endian byte order. Like CBOR, containers are length-prefixed, so no terminator is needed.
|
||||
MessagePack encodes each value as a one-byte *format marker*, optionally followed by a length or the payload. The marker range is partitioned to keep common values small: small non-negative integers (0 to 127) and small negative integers (-32 to -1) are the marker byte itself; short strings, arrays, and maps (up to 31 bytes for strings and up to 15 elements for arrays and maps) hide their length in the marker's lower bits. Larger values use dedicated markers that announce the width (8, 16, 32, or 64 bits) of the length or number that follows, in big-endian byte order. Like CBOR, containers are length-prefixed, so no terminator is needed.
|
||||
|
||||
Consider the following JSON value (shown formatted for readability):
|
||||
|
||||
@@ -109,8 +109,9 @@ specification:
|
||||
!!! note
|
||||
|
||||
Prior to version 3.13.0, NaN and Infinity were instead serialized as a MessagePack float 64 (type 0xCB, 9 bytes
|
||||
total), because the check used to select the smaller float 32 encoding compared magnitudes with NaN, which is
|
||||
always `false` and caused the float 32 path to be skipped.
|
||||
total), because the check used to select the smaller float 32 encoding failed for NaN (all comparisons are `false`)
|
||||
and for Infinity and -Infinity (outside the float range checked against `std::numeric_limits<float>::lowest()` and
|
||||
`max()`), so the float 32 path was skipped.
|
||||
|
||||
??? example "Example: serialize a JSON value to MessagePack"
|
||||
|
||||
@@ -135,6 +136,7 @@ The library maps MessagePack types to JSON value types as follows:
|
||||
| fixarray | array | 0x90..0x9F |
|
||||
| fixstr | string | 0xA0..0xBF |
|
||||
| nil | `null` | 0xC0 |
|
||||
| (never used) | – | 0xC1 |
|
||||
| false | `false` | 0xC2 |
|
||||
| true | `true` | 0xC3 |
|
||||
| float 32 | number_float | 0xCA |
|
||||
@@ -167,6 +169,30 @@ The library maps MessagePack types to JSON value types as follows:
|
||||
| fixext 16 | binary | 0xD8 |
|
||||
| negative fixint | number_integer | 0xE0-0xFF |
|
||||
|
||||
The byte 0xC1 is reserved as "never used" by the specification. As a value it is rejected with
|
||||
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) (`invalid byte: 0xC1`); as a map key it is
|
||||
rejected with [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) and the generic "expected length
|
||||
specification" message, because the key is read as a string.
|
||||
|
||||
Duplicate keys in a map are accepted; the last value wins.
|
||||
|
||||
### Extension types (ext, fixext)
|
||||
|
||||
MessagePack ext and fixext values are read as binary values, and the ext type byte is stored as the binary subtype.
|
||||
Although the specification defines the type as a signed 8-bit integer (0 to 127 for applications, -128 to -1 reserved,
|
||||
with -1 being the Timestamp extension), the library reads it as an unsigned byte: a negative ext type `n` becomes
|
||||
subtype `256+n`, so ext type -1 is read as subtype 255 and -128 as subtype 128. The Timestamp extension is not
|
||||
interpreted; it is returned as binary with subtype 255. Use
|
||||
[`has_subtype()`](../../api/byte_container_with_subtype/has_subtype.md) to tell a `bin` value (no subtype) from an ext
|
||||
value (subtype, even if it is 0).
|
||||
|
||||
When serializing, a binary value with a subtype is always written as fixext (1, 2, 4, 8, or 16 bytes) or ext, even
|
||||
for subtype 0 (`d6 00 01 02 03 04`); a binary value without a subtype is written as bin. Subtypes 128 to 255 are
|
||||
written as the reserved negative ext types (255 as -1), so `json::binary({1,2,3,4}, 255)` becomes
|
||||
`d6 ff 01 02 03 04`, which other MessagePack libraries decode as a 32-bit timestamp. A subtype above 255 throws
|
||||
[`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415). The payload length is limited to
|
||||
4294967295 bytes like all other lengths.
|
||||
|
||||
!!! info
|
||||
|
||||
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
||||
@@ -177,7 +203,8 @@ The library maps MessagePack types to JSON value types as follows:
|
||||
JSON-compatible [profile](https://github.com/msgpack/msgpack/blob/master/spec.md#profile) sketched in the
|
||||
MessagePack specification, this library restricts map keys to `str` values. Maps with keys of any other type are
|
||||
rejected with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
|
||||
`allow_exceptions` set to `false`, a discarded value) naming the type of the key that was found, for instance:
|
||||
`allow_exceptions` set to `false`, a discarded value). For most key types the message names the type that was
|
||||
found, for instance:
|
||||
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
|
||||
@@ -189,7 +216,7 @@ The library maps MessagePack types to JSON value types as follows:
|
||||
!!! warning "Ill-formed UTF-8 in string values"
|
||||
|
||||
The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain
|
||||
a byte sequence that is not valid UTF-8, and expects a deserializer to hand the original bytes back unchanged.
|
||||
a byte sequence that is not valid UTF-8, and recommends that a deserializer provide a way to obtain the original bytes.
|
||||
This library follows that by default: with its
|
||||
[`error_handler`](../../api/basic_json/from_msgpack.md) parameter left at `keep` (the default),
|
||||
`from_msgpack()` reads `str` bytes (object keys included) as-is, without validating them, so such a value
|
||||
|
||||
@@ -5,11 +5,11 @@ achieve the generality of JSON, combined with being much easier to process than
|
||||
|
||||
!!! abstract "References"
|
||||
|
||||
- [UBJSON Website](http://ubjson.org)
|
||||
- [UBJSON Website](https://ubjson.org)
|
||||
|
||||
## Byte layout
|
||||
|
||||
UBJSON is built from just a one-byte, human-readable ASCII *type marker* (for example, `i` for int8, `S` for string, `[` for array) followed by the payload of that type. Strings and the like are prefixed with their length, which is itself a UBJSON integer value including its marker. Numbers are stored in big-endian byte order. Containers can be written in two ways: as an *unoptimized* container that is opened with `[` or `{` and closed with `]` or `}`, or as an *optimized* container that declares an element type (`$`) and/or a count (`#`) after the opening marker, which saves per-element markers and (for a count) the closing marker.
|
||||
UBJSON is built from just a one-byte, human-readable ASCII *type marker* (for example, `i` for int8, `S` for string, `[` for array) followed by the payload of that type. Strings and the like are prefixed with their length, which is itself a UBJSON integer value including its marker. Numbers are stored in big-endian byte order. Containers can be written in two ways: as an *unoptimized* container that is opened with `[` or `{` and closed with `]` or `}`, or as an *optimized* container that declares a count (`#`) after the opening marker, optionally preceded by an element type (`$`); an element type is only allowed together with a count. An optimized container saves the closing marker and, with a type, the per-element markers.
|
||||
|
||||
Consider the following JSON value (shown formatted for readability):
|
||||
|
||||
@@ -44,29 +44,29 @@ Consider the following JSON value (shown formatted for readability):
|
||||
|
||||
The library uses the following mapping from JSON values types to UBJSON types according to the UBJSON specification:
|
||||
|
||||
| JSON value type | value/range | UBJSON type | marker |
|
||||
|-----------------|-------------------------------------------|----------------|--------|
|
||||
| null | `null` | null | `Z` |
|
||||
| boolean | `true` | true | `T` |
|
||||
| boolean | `false` | false | `F` |
|
||||
| number_integer | -9223372036854775808..-2147483649 | int64 | `L` |
|
||||
| number_integer | -2147483648..-32769 | int32 | `l` |
|
||||
| number_integer | -32768..-129 | int16 | `I` |
|
||||
| number_integer | -128..127 | int8 | `i` |
|
||||
| number_integer | 128..255 | uint8 | `U` |
|
||||
| number_integer | 256..32767 | int16 | `I` |
|
||||
| number_integer | 32768..2147483647 | int32 | `l` |
|
||||
| number_integer | 2147483648..9223372036854775807 | int64 | `L` |
|
||||
| number_unsigned | 0..127 | int8 | `i` |
|
||||
| number_unsigned | 128..255 | uint8 | `U` |
|
||||
| number_unsigned | 256..32767 | int16 | `I` |
|
||||
| number_unsigned | 32768..2147483647 | int32 | `l` |
|
||||
| number_unsigned | 2147483648..9223372036854775807 | int64 | `L` |
|
||||
| number_unsigned | 9223372036854775808..18446744073709551615 | high-precision | `H` |
|
||||
| number_float | *any value* | float64 | `D` |
|
||||
| string | *with shortest length indicator* | string | `S` |
|
||||
| array | *see notes on optimized format* | array | `[` |
|
||||
| object | *see notes on optimized format* | map | `{` |
|
||||
| JSON value type | value/range | UBJSON type | marker |
|
||||
|-----------------|-------------------------------------------|--------------------------------------------------|-----------|
|
||||
| null | `null` | null | `Z` |
|
||||
| boolean | `true` | true | `T` |
|
||||
| boolean | `false` | false | `F` |
|
||||
| number_integer | -9223372036854775808..-2147483649 | int64 | `L` |
|
||||
| number_integer | -2147483648..-32769 | int32 | `l` |
|
||||
| number_integer | -32768..-129 | int16 | `I` |
|
||||
| number_integer | -128..127 | int8 | `i` |
|
||||
| number_integer | 128..255 | uint8 | `U` |
|
||||
| number_integer | 256..32767 | int16 | `I` |
|
||||
| number_integer | 32768..2147483647 | int32 | `l` |
|
||||
| number_integer | 2147483648..9223372036854775807 | int64 | `L` |
|
||||
| number_unsigned | 0..127 | int8 | `i` |
|
||||
| number_unsigned | 128..255 | uint8 | `U` |
|
||||
| number_unsigned | 256..32767 | int16 | `I` |
|
||||
| number_unsigned | 32768..2147483647 | int32 | `l` |
|
||||
| number_unsigned | 2147483648..9223372036854775807 | int64 | `L` |
|
||||
| number_unsigned | 9223372036854775808..18446744073709551615 | high-precision | `H` |
|
||||
| number_float | *any value* | float64 (float32 if `number_float_t` is `float`) | `D` (`d`) |
|
||||
| string | *with shortest length indicator* | string | `S` |
|
||||
| array | *see notes on optimized format* | array | `[` |
|
||||
| object | *see notes on optimized format* | object | `{` |
|
||||
|
||||
!!! success "Complete mapping"
|
||||
|
||||
@@ -93,20 +93,27 @@ The library uses the following mapping from JSON values types to UBJSON types ac
|
||||
|
||||
The following markers are not used in the conversion:
|
||||
|
||||
- `Z`: no-op values are not created.
|
||||
- `N`: no-op values are never written (they are skipped when reading).
|
||||
- `C`: single-byte strings are serialized with `S` markers.
|
||||
|
||||
!!! info "NaN/infinity handling"
|
||||
|
||||
If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the
|
||||
[`dump()`](../../api/basic_json/dump.md) function which serializes NaN or Infinity to `null`.
|
||||
NaN and infinity are written as IEEE 754 float64 values (`D`) and read back unchanged. This differs from
|
||||
[`dump()`](../../api/basic_json/dump.md), which serializes them to `null`, and from the UBJSON specification, which
|
||||
encodes infinity as `null` (it does not mention NaN); other UBJSON readers may therefore not accept these values.
|
||||
|
||||
!!! info "Optimized formats"
|
||||
|
||||
The optimized formats for containers are supported: Parameter `use_size` adds size information to the beginning of a
|
||||
container and removes the closing marker. Parameter `use_type` further checks whether all elements of a container
|
||||
have the same type and adds the type marker to the beginning of the container. The `use_type` parameter must only be
|
||||
used together with `use_size = true`.
|
||||
used together with `use_size = true`: combining `use_type` with `use_size = false` throws
|
||||
[`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if the value contains a non-empty array,
|
||||
object, or binary value.
|
||||
|
||||
Two elements have the same type if they are written with the same type marker. For example, `[1, 2]` is written with
|
||||
the type `i`, but `[1, 200]` (`i`, `U`), `[true, false]` (`T`, `F`), and `[1, 2.5]` (`i`, `D`) are not; an empty
|
||||
container is never given a `$` annotation of its own (an array of empty arrays is still typed `[`).
|
||||
|
||||
Note that `use_size = true` alone may result in larger representations - the benefit of this parameter is that the
|
||||
receiving side is immediately informed on the number of elements of the container.
|
||||
@@ -115,8 +122,9 @@ The library uses the following mapping from JSON values types to UBJSON types ac
|
||||
already is the value. Its declared count is therefore the only thing that decides how much memory the receiving side
|
||||
allocates, and a handful of bytes can describe billions of elements. `from_ubjson` rejects such an array with
|
||||
[`out_of_range.408`](../../home/exceptions.md#jsonexceptionout_of_range408) when the count exceeds 1,048,576
|
||||
(`1 << 20`), and `to_ubjson` writes longer arrays of these types without the annotation, so any value it produces
|
||||
can be read back.
|
||||
(`1 << 20`), and `to_ubjson` writes longer arrays of these types in the counted but untyped form: the `$` type marker is
|
||||
dropped, the `#` count is kept, and every element is written with its own marker (`[#l 1048577 Z Z ...`), so any value
|
||||
it produces can be read back. Objects and untyped counted arrays are not subject to this limit.
|
||||
|
||||
!!! info "Binary values"
|
||||
|
||||
@@ -140,27 +148,54 @@ The library uses the following mapping from JSON values types to UBJSON types ac
|
||||
|
||||
The library maps UBJSON types to JSON value types as follows:
|
||||
|
||||
| UBJSON type | JSON value type | marker |
|
||||
|-------------|-----------------------------------------|--------|
|
||||
| no-op | *no value, next value is read* | `N` |
|
||||
| null | `null` | `Z` |
|
||||
| false | `false` | `F` |
|
||||
| true | `true` | `T` |
|
||||
| float32 | number_float | `d` |
|
||||
| float64 | number_float | `D` |
|
||||
| uint8 | number_unsigned | `U` |
|
||||
| int8 | number_integer | `i` |
|
||||
| int16 | number_integer | `I` |
|
||||
| int32 | number_integer | `l` |
|
||||
| int64 | number_integer | `L` |
|
||||
| string | string | `S` |
|
||||
| char | string | `C` |
|
||||
| array | array (optimized values are supported) | `[` |
|
||||
| object | object (optimized values are supported) | `{` |
|
||||
| UBJSON type | JSON value type | marker |
|
||||
|----------------|--------------------------------------------------|--------|
|
||||
| no-op | *no value, next value is read* | `N` |
|
||||
| null | `null` | `Z` |
|
||||
| false | `false` | `F` |
|
||||
| true | `true` | `T` |
|
||||
| float32 | number_float | `d` |
|
||||
| float64 | number_float | `D` |
|
||||
| uint8 | number_unsigned | `U` |
|
||||
| int8 | number_integer | `i` |
|
||||
| int16 | number_integer | `I` |
|
||||
| int32 | number_integer | `l` |
|
||||
| int64 | number_integer | `L` |
|
||||
| string | string | `S` |
|
||||
| char | string | `C` |
|
||||
| high-precision | number_unsigned, number_integer, or number_float | `H` |
|
||||
| array | array (optimized values are supported) | `[` |
|
||||
| object | object (optimized values are supported) | `{` |
|
||||
|
||||
!!! info "High-precision numbers"
|
||||
|
||||
The payload of `H` is a length-prefixed string that is parsed as a JSON number (surrounding whitespace is ignored).
|
||||
Integer text in the range of `number_unsigned_t` (non-negative) or `number_integer_t` (negative) is stored as such;
|
||||
any other valid number text (larger or smaller integers, fractions, and exponents such as `1e2`) is stored as
|
||||
`number_float`, so precision may be lost. Text that is not a valid JSON number raises
|
||||
[`parse_error.115`](../../home/exceptions.md#jsonexceptionparse_error115), a number outside the range of
|
||||
`number_float_t` (`1e999`) raises [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406).
|
||||
|
||||
!!! success "Complete mapping"
|
||||
|
||||
The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value.
|
||||
Every well-formed UBJSON value can be converted to a JSON value, with these exceptions: a high-precision number
|
||||
outside the range of `number_float_t` ([`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406)),
|
||||
an optimized array of `Z`, `T`, or `F` with more than 1,048,576 elements, and a count that exceeds the capacity of the
|
||||
container ([`out_of_range.408`](../../home/exceptions.md#jsonexceptionout_of_range408)). High-precision numbers that
|
||||
do not fit an integer type are stored as floating-point numbers and may lose precision. Ill-formed input is rejected
|
||||
with a parse error.
|
||||
|
||||
!!! info "Duplicate object keys"
|
||||
|
||||
If an object contains the same key more than once, the last value is kept (the UBJSON specification does not
|
||||
address duplicate keys).
|
||||
|
||||
!!! info "Type marker of optimized containers"
|
||||
|
||||
The type marker after `$` must be one of `Z`, `N`, `T`, `F`, `i`, `U`, `I`, `l`, `L`, `d`, `D`, `H`, `C`, `S`, `[`, or
|
||||
`{`. It is checked even if the count is 0; any other marker raises
|
||||
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112). A counted object typed `N` (`{$N#i1 ...`)
|
||||
is not supported.
|
||||
|
||||
!!! warning "Ill-formed UTF-8 in string values and object keys"
|
||||
|
||||
|
||||
@@ -301,6 +301,8 @@ representation using the length of the byte array.
|
||||
[`json::from_cbor`](../api/basic_json/from_cbor.md) (see
|
||||
[`cbor_tag_handler_t`](../api/basic_json/cbor_tag_handler_t.md)).
|
||||
|
||||
With `ignore`, the subtype is dropped (`"subtype": null`); with `store`, the tag is kept (`"subtype": 42`):
|
||||
|
||||
```json
|
||||
{
|
||||
"binary": {
|
||||
@@ -310,11 +312,21 @@ representation using the length of the byte array.
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"binary": {
|
||||
"bytes": [202, 254, 186, 190],
|
||||
"subtype": 42
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MessagePack
|
||||
|
||||
[MessagePack](binary_formats/messagepack.md) supports binary values and subtypes. If a subtype is given, the ext family
|
||||
is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, ext8, ext16, and
|
||||
ext32. The subtype is then added as a signed 8-bit integer.
|
||||
is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, fixext16, ext8,
|
||||
ext16, and ext32. The subtype is then written as the signed 8-bit ext type: subtypes 128 to 255 map to the negative
|
||||
(reserved) ext types, and on reading the ext type is exposed as an unsigned subtype from 0 to 255.
|
||||
|
||||
If no subtype is given, the bin family (bin8, bin16, bin32) is used.
|
||||
|
||||
|
||||
@@ -368,8 +368,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
||||
`#!cpp std::from_chars` is not available or declines the token, and `std::strtold` reads up to the null character. A type whose `data()`
|
||||
is not null-terminated does not fail to compile; with such a `NumberFloatType` it can silently misparse
|
||||
floating-point numbers.
|
||||
- `append(const char*, size_type)`, used by [`dump`](../../api/basic_json/dump.md), and `append(const StringType&)`,
|
||||
used by the CBOR reader for indefinite-length strings. The library's internal string concatenation additionally has
|
||||
- `append(const char*, size_type)`, used by [`dump`](../../api/basic_json/dump.md) and by the binary readers. The library's internal string concatenation additionally has
|
||||
to append a `#!cpp char` and a `#!cpp const char*`; for each it selects between `append(arg)`, `#!cpp operator+=`,
|
||||
`append(first, last)`, and `append(data, size)`.
|
||||
- The comparison operator `==` against another `StringType`, and `<` for use as a key of the chosen
|
||||
@@ -678,8 +677,7 @@ using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;
|
||||
[`StringType`](#stringtype), whose non-`#!cpp const` `data()` is C++17 and later only.
|
||||
- Default-constructible, copy-constructible, and move-constructible.
|
||||
- Member functions `size()`, `empty()`, `data()`, `resize()`, `operator[]`, `back()`, `begin()`, `end()`, `cbegin()`,
|
||||
and `cend()` with random-access iterators, and `insert(pos, first, last)`, which the CBOR reader uses to join the
|
||||
chunks of an indefinite-length byte string. `push_back()` is **not** required.
|
||||
and `cend()` with random-access iterators. `push_back()` is **not** required.
|
||||
- Comparison operators: `==` is used by
|
||||
[`byte_container_with_subtype`](../../api/byte_container_with_subtype/index.md), the relational operators by
|
||||
[`basic_json`'s comparison operators](../../api/basic_json/operator_le.md).
|
||||
|
||||
@@ -330,7 +330,7 @@ where they cannot be resolved.
|
||||
### json.exception.parse_error.110
|
||||
|
||||
When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has
|
||||
been read.
|
||||
been read, or (with `strict = true`) further bytes follow the value.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
@@ -1045,8 +1045,8 @@ or the encoded length for [CBOR](../features/binary_formats/cbor.md).
|
||||
The exception is also thrown for a [UBJSON](../features/binary_formats/ubjson.md) array of a type that is encoded by its
|
||||
marker alone (`Z`, `T` or `F`) whose declared count exceeds 1,048,576 (`1 << 20`). Such an array has no payload, so its
|
||||
count alone decides how much memory is allocated, and a handful of bytes would otherwise describe billions of values.
|
||||
[`to_ubjson`](../api/basic_json/to_ubjson.md) writes longer arrays of these types without the size and type annotation,
|
||||
so any value it produces can still be read back.
|
||||
[`to_ubjson`](../api/basic_json/to_ubjson.md) writes longer arrays of these types without the `$` type annotation (the `#`
|
||||
count and one marker per element are kept), so any value it produces can still be read back.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
@@ -1100,8 +1100,6 @@ A JSON Patch `add` operation cannot be applied because the target location's par
|
||||
|
||||
This exception was added in version 3.13.0. Before that, this situation hit an internal assertion (aborting the program in debug builds) or was silently ignored when assertions were disabled.
|
||||
|
||||
A `null` parent is rejected as well. Before version 3.13.0, an `add` into a `null` parent silently converted it to an object (e.g., `add` at `/foo/bar` turned `{"foo":null}` into `{"foo":{"bar":1}}`).
|
||||
|
||||
### json.exception.out_of_range.412
|
||||
|
||||
BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer, and MessagePack
|
||||
@@ -1155,7 +1153,7 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa
|
||||
### json.exception.out_of_range.415
|
||||
|
||||
MessagePack's ext type and BSON's binary subtype are each stored in a single byte. This exception is thrown when serializing a
|
||||
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255.
|
||||
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255, the largest value the library can store in that byte.
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
@@ -1231,7 +1229,8 @@ A JSON Patch operation 'test' failed. The unsuccessful operation is also printed
|
||||
### json.exception.other_error.502
|
||||
|
||||
[`to_ubjson`](../api/basic_json/to_ubjson.md) and [`to_bjdata`](../api/basic_json/to_bjdata.md) were called with
|
||||
`use_type = true` but `use_size = false`. UBJSON requires a size marker (`#`) after a type marker (`$`).
|
||||
`use_type = true` but `use_size = false` for a value that contains a non-empty array, object, or binary value. UBJSON
|
||||
requires a size marker (`#`) after a type marker (`$`); scalars and empty containers do not throw.
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
|
||||
@@ -1218,6 +1218,10 @@ class binary_reader
|
||||
// level as required by RFC 8949, Section 3.2.3
|
||||
bool indefinite = false;
|
||||
|
||||
// the chunk currently being validated; only used for the chunks of an
|
||||
// indefinite-length string when the UTF-8 check can do something
|
||||
string_t chunk;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string")))
|
||||
@@ -1237,26 +1241,82 @@ class binary_reader
|
||||
}
|
||||
|
||||
// a break marker closes the indefinite-length string; outside
|
||||
// of one it falls through to the error below
|
||||
// of one it falls through to the error below. Every chunk has
|
||||
// been checked on its own, so there is nothing left to check.
|
||||
if (indefinite && current == 0xFF)
|
||||
{
|
||||
return check_string_utf8(result, context);
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!indefinite)
|
||||
{
|
||||
return check_string_utf8(result, context);
|
||||
return get_cbor_string_chunk(result, false) && check_string_utf8(result, context);
|
||||
}
|
||||
|
||||
if (error_handler == error_handler_t::keep)
|
||||
{
|
||||
// nothing to check: read the chunk directly into the result
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, true)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RFC 8949, Section 3.2.3: each chunk is a text string of its
|
||||
// own and must be valid UTF-8 by itself, i.e., a code point
|
||||
// cannot be spread over two chunks
|
||||
chunk.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(chunk, true) || !check_string_utf8(chunk, context)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
result.append(chunk.data(), chunk.size());
|
||||
}
|
||||
|
||||
get();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skips the tags in front of a CBOR object key
|
||||
|
||||
A tag may wrap any data item, so it may also wrap a key. Like in front of a
|
||||
value, the tags are skipped for the handlers ignore and store; with the
|
||||
handler error they are left to @ref get_cbor_object_key, which rejects the
|
||||
key. The tag loop is iterative, so the number of tags is not limited by the
|
||||
recursion depth.
|
||||
|
||||
@param[in] tag_handler how CBOR tags should be treated
|
||||
|
||||
@return whether the tags could be skipped (false on EOF in a tag argument)
|
||||
|
||||
@pre @a current is the first byte of the key
|
||||
*/
|
||||
bool skip_cbor_key_tags(const cbor_tag_handler_t tag_handler)
|
||||
{
|
||||
if (tag_handler == cbor_tag_handler_t::error)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0xC0..0xDB: tag with the tag number in the head (0xC0..0xD7) or in
|
||||
// the 1, 2, 4, or 8 bytes that follow (0xD8..0xDB)
|
||||
while (current >= 0xC0 && current <= 0xDB)
|
||||
{
|
||||
if (current >= 0xD8)
|
||||
{
|
||||
std::uint64_t tag_to_ignore{};
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_argument(tag_to_ignore)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
get();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR object key
|
||||
|
||||
@@ -1623,6 +1683,10 @@ class binary_reader
|
||||
|
||||
if (top.is_object)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
|
||||
{
|
||||
return false;
|
||||
@@ -2936,6 +3000,14 @@ class binary_reader
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate the type marker up front: otherwise it would only be
|
||||
// checked when an element is read, and a container with count 0
|
||||
// would accept any marker
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_valid_optimized_type(result.second)))
|
||||
{
|
||||
return optimized_type_error();
|
||||
}
|
||||
|
||||
get_ignore_noop();
|
||||
if (JSON_HEDLEY_UNLIKELY(current != '#'))
|
||||
{
|
||||
@@ -4130,9 +4202,9 @@ class binary_reader
|
||||
// sequence), UBJSON, BJData, or BSON requires a decoder to reject
|
||||
// ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict,
|
||||
// rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via
|
||||
// @ref error_handler, applied once the whole string (all chunks of
|
||||
// an indefinite-length CBOR string included) has been assembled, by
|
||||
// @ref check_string_utf8 at the call site.
|
||||
// @ref error_handler, applied by @ref check_string_utf8 at the call
|
||||
// site to the whole string or, for an indefinite-length CBOR string,
|
||||
// to each chunk on its own (RFC 8949, Section 3.2.3).
|
||||
return get_bytes(len, "string", result);
|
||||
}
|
||||
|
||||
@@ -4432,6 +4504,44 @@ class binary_reader
|
||||
std::size_t bon8_pushback_size = 0;
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
/*!
|
||||
@brief check whether @a marker is a type marker that may follow '$'
|
||||
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C
|
||||
(BJData's excluded markers are rejected separately).
|
||||
*/
|
||||
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
||||
{
|
||||
switch (marker)
|
||||
{
|
||||
case 'i':
|
||||
case 'U':
|
||||
case 'I':
|
||||
case 'l':
|
||||
case 'L':
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'C':
|
||||
return true;
|
||||
case 'Z':
|
||||
case 'N':
|
||||
case 'T':
|
||||
case 'F':
|
||||
case 'H':
|
||||
case 'S':
|
||||
case '[':
|
||||
case '{':
|
||||
return input_format != input_format_t::bjdata;
|
||||
case 'B':
|
||||
case 'u':
|
||||
case 'm':
|
||||
case 'M':
|
||||
case 'h':
|
||||
return input_format == input_format_t::bjdata;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether @a marker is excluded from BJData's optimized ND-array types
|
||||
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
|
||||
|
||||
@@ -7003,6 +7003,7 @@ public:
|
||||
|
||||
switch (parent.m_data.m_type)
|
||||
{
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
{
|
||||
// use operator[] to add value
|
||||
@@ -7034,7 +7035,6 @@ public:
|
||||
|
||||
// the parent of an "add" target must be an object or array
|
||||
// (see #4292)
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
|
||||
Generated
+122
-12
@@ -18685,6 +18685,10 @@ class binary_reader
|
||||
// level as required by RFC 8949, Section 3.2.3
|
||||
bool indefinite = false;
|
||||
|
||||
// the chunk currently being validated; only used for the chunks of an
|
||||
// indefinite-length string when the UTF-8 check can do something
|
||||
string_t chunk;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string")))
|
||||
@@ -18704,26 +18708,82 @@ class binary_reader
|
||||
}
|
||||
|
||||
// a break marker closes the indefinite-length string; outside
|
||||
// of one it falls through to the error below
|
||||
// of one it falls through to the error below. Every chunk has
|
||||
// been checked on its own, so there is nothing left to check.
|
||||
if (indefinite && current == 0xFF)
|
||||
{
|
||||
return check_string_utf8(result, context);
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!indefinite)
|
||||
{
|
||||
return check_string_utf8(result, context);
|
||||
return get_cbor_string_chunk(result, false) && check_string_utf8(result, context);
|
||||
}
|
||||
|
||||
if (error_handler == error_handler_t::keep)
|
||||
{
|
||||
// nothing to check: read the chunk directly into the result
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, true)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RFC 8949, Section 3.2.3: each chunk is a text string of its
|
||||
// own and must be valid UTF-8 by itself, i.e., a code point
|
||||
// cannot be spread over two chunks
|
||||
chunk.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(chunk, true) || !check_string_utf8(chunk, context)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
result.append(chunk.data(), chunk.size());
|
||||
}
|
||||
|
||||
get();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skips the tags in front of a CBOR object key
|
||||
|
||||
A tag may wrap any data item, so it may also wrap a key. Like in front of a
|
||||
value, the tags are skipped for the handlers ignore and store; with the
|
||||
handler error they are left to @ref get_cbor_object_key, which rejects the
|
||||
key. The tag loop is iterative, so the number of tags is not limited by the
|
||||
recursion depth.
|
||||
|
||||
@param[in] tag_handler how CBOR tags should be treated
|
||||
|
||||
@return whether the tags could be skipped (false on EOF in a tag argument)
|
||||
|
||||
@pre @a current is the first byte of the key
|
||||
*/
|
||||
bool skip_cbor_key_tags(const cbor_tag_handler_t tag_handler)
|
||||
{
|
||||
if (tag_handler == cbor_tag_handler_t::error)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0xC0..0xDB: tag with the tag number in the head (0xC0..0xD7) or in
|
||||
// the 1, 2, 4, or 8 bytes that follow (0xD8..0xDB)
|
||||
while (current >= 0xC0 && current <= 0xDB)
|
||||
{
|
||||
if (current >= 0xD8)
|
||||
{
|
||||
std::uint64_t tag_to_ignore{};
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_argument(tag_to_ignore)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
get();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR object key
|
||||
|
||||
@@ -19090,6 +19150,10 @@ class binary_reader
|
||||
|
||||
if (top.is_object)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
|
||||
{
|
||||
return false;
|
||||
@@ -20403,6 +20467,14 @@ class binary_reader
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate the type marker up front: otherwise it would only be
|
||||
// checked when an element is read, and a container with count 0
|
||||
// would accept any marker
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_valid_optimized_type(result.second)))
|
||||
{
|
||||
return optimized_type_error();
|
||||
}
|
||||
|
||||
get_ignore_noop();
|
||||
if (JSON_HEDLEY_UNLIKELY(current != '#'))
|
||||
{
|
||||
@@ -21597,9 +21669,9 @@ class binary_reader
|
||||
// sequence), UBJSON, BJData, or BSON requires a decoder to reject
|
||||
// ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict,
|
||||
// rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via
|
||||
// @ref error_handler, applied once the whole string (all chunks of
|
||||
// an indefinite-length CBOR string included) has been assembled, by
|
||||
// @ref check_string_utf8 at the call site.
|
||||
// @ref error_handler, applied by @ref check_string_utf8 at the call
|
||||
// site to the whole string or, for an indefinite-length CBOR string,
|
||||
// to each chunk on its own (RFC 8949, Section 3.2.3).
|
||||
return get_bytes(len, "string", result);
|
||||
}
|
||||
|
||||
@@ -21899,6 +21971,44 @@ class binary_reader
|
||||
std::size_t bon8_pushback_size = 0;
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
/*!
|
||||
@brief check whether @a marker is a type marker that may follow '$'
|
||||
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C
|
||||
(BJData's excluded markers are rejected separately).
|
||||
*/
|
||||
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
||||
{
|
||||
switch (marker)
|
||||
{
|
||||
case 'i':
|
||||
case 'U':
|
||||
case 'I':
|
||||
case 'l':
|
||||
case 'L':
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'C':
|
||||
return true;
|
||||
case 'Z':
|
||||
case 'N':
|
||||
case 'T':
|
||||
case 'F':
|
||||
case 'H':
|
||||
case 'S':
|
||||
case '[':
|
||||
case '{':
|
||||
return input_format != input_format_t::bjdata;
|
||||
case 'B':
|
||||
case 'u':
|
||||
case 'm':
|
||||
case 'M':
|
||||
case 'h':
|
||||
return input_format == input_format_t::bjdata;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether @a marker is excluded from BJData's optimized ND-array types
|
||||
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
|
||||
@@ -42793,6 +42903,7 @@ public:
|
||||
|
||||
switch (parent.m_data.m_type)
|
||||
{
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
{
|
||||
// use operator[] to add value
|
||||
@@ -42824,7 +42935,6 @@ public:
|
||||
|
||||
// the parent of an "add" target must be an object or array
|
||||
// (see #4292)
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
|
||||
@@ -3134,7 +3134,7 @@ TEST_CASE("BJData")
|
||||
std::vector<uint8_t> const v1 = {'[', '#', 'i', 0xF1};
|
||||
std::vector<uint8_t> const v2 = {'[', '$', 'I', '#', 'i', 0xF2};
|
||||
std::vector<uint8_t> const v3 = {'[', '$', 'I', '#', '[', 'i', 0xF4, 'i', 0x02, ']'};
|
||||
std::vector<uint8_t> const v4 = {'[', '$', 0xF6, '#', 'i', 0xF7};
|
||||
std::vector<uint8_t> const v4 = {'[', '$', 'l', '#', 'i', 0xF7};
|
||||
std::vector<uint8_t> const v5 = {'[', '$', 'I', '#', '[', 'i', 0xF5, 'i', 0xF1, ']'};
|
||||
std::vector<uint8_t> const v6 = {'[', '#', '[', 'i', 0xF3, 'i', 0x02, ']'};
|
||||
|
||||
@@ -3455,7 +3455,7 @@ TEST_CASE("BJData")
|
||||
CHECK(json::from_bjdata(v0, true, false).is_discarded());
|
||||
|
||||
std::vector<uint8_t> const vi = {'[', '$', '#'};
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x23 is not a permitted optimized array type", json::parse_error&);
|
||||
CHECK(json::from_bjdata(vi, true, false).is_discarded());
|
||||
|
||||
std::vector<uint8_t> const vU = {'[', '$', 'U'};
|
||||
@@ -4622,3 +4622,60 @@ TEST_CASE("BJData large strings and binaries (chunked reader)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData optimized container type marker is validated for count 0")
|
||||
{
|
||||
// the '$' type marker must be a legal marker even if no element follows
|
||||
SECTION("invalid marker")
|
||||
{
|
||||
// 'Z', 'T', 'F', 'N', 'S', 'H', '[' and '{' are excluded in BJData
|
||||
for (const char marker :
|
||||
{'x', 'E', 'Z', 'T', 'F', 'N', 'S', 'H', '[', '{'
|
||||
})
|
||||
{
|
||||
for (const char open :
|
||||
{'[', '{'
|
||||
})
|
||||
{
|
||||
for (const int count :
|
||||
{
|
||||
0, 1
|
||||
})
|
||||
{
|
||||
CAPTURE(marker)
|
||||
CAPTURE(open)
|
||||
CAPTURE(count)
|
||||
const std::vector<std::uint8_t> v = {static_cast<std::uint8_t>(open), '$', static_cast<std::uint8_t>(marker), '#', 'i', static_cast<std::uint8_t>(count)};
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&);
|
||||
CHECK(json::from_bjdata(v, true, false).is_discarded());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("error message")
|
||||
{
|
||||
const std::vector<std::uint8_t> v = {'[', '$', 'x', '#', 'i', 0};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x78 is not a permitted optimized array type", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("valid marker")
|
||||
{
|
||||
for (const char marker :
|
||||
{'i', 'U', 'I', 'u', 'l', 'm', 'L', 'M', 'h', 'd', 'D', 'C'
|
||||
})
|
||||
{
|
||||
CAPTURE(marker)
|
||||
const std::vector<std::uint8_t> va = {'[', '$', static_cast<std::uint8_t>(marker), '#', 'i', 0};
|
||||
const std::vector<std::uint8_t> vo = {'{', '$', static_cast<std::uint8_t>(marker), '#', 'i', 0};
|
||||
CHECK(json::from_bjdata(va) == json::array());
|
||||
CHECK(json::from_bjdata(vo) == json::object());
|
||||
}
|
||||
|
||||
// an optimized array of bytes is read as a binary value
|
||||
const std::vector<std::uint8_t> vb = {'[', '$', 'B', '#', 'i', 0};
|
||||
CHECK(json::from_bjdata(vb) == json::binary({}));
|
||||
}
|
||||
}
|
||||
+148
-3
@@ -1807,6 +1807,93 @@ TEST_CASE("CBOR")
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("tag in front of a key")
|
||||
{
|
||||
const auto ignore = json::cbor_tag_handler_t::ignore;
|
||||
const auto store = json::cbor_tag_handler_t::store;
|
||||
const json expected = {{"a", 10}};
|
||||
|
||||
// the tags are skipped in front of a text string key, like in
|
||||
// front of a value
|
||||
const std::vector<std::vector<std::uint8_t>> tagged =
|
||||
{
|
||||
{0xA1, 0xC0, 0x61, 0x61, 0x0A}, // tag in the head
|
||||
{0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A}, // 1-byte tag number
|
||||
{0xA1, 0xD9, 0x01, 0x2C, 0x61, 0x61, 0x0A}, // 2-byte tag number
|
||||
{0xA1, 0xDA, 0x00, 0x01, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 4-byte tag number
|
||||
{0xA1, 0xDB, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 8-byte tag number
|
||||
{0xA1, 0xC0, 0xD8, 0x05, 0xC1, 0x61, 0x61, 0x0A}, // chained tags
|
||||
{0xBF, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // indefinite-length map
|
||||
{0xBF, 0x61, 0x62, 0x01, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // second key tagged
|
||||
{0xA1, 0xC0, 0x7F, 0x61, 0x61, 0xFF, 0x0A}, // indefinite-length text key
|
||||
{0xA1, 0xC0, 0x61, 0x61, 0xC0, 0x0A}, // tagged key and value
|
||||
};
|
||||
|
||||
for (const auto& input : tagged)
|
||||
{
|
||||
CAPTURE(input)
|
||||
json const j_ignore = json::from_cbor(input, true, true, ignore);
|
||||
json const j_store = json::from_cbor(input, true, true, store);
|
||||
CHECK(j_ignore.at("a") == 10);
|
||||
CHECK(j_store.at("a") == 10);
|
||||
CHECK(j_ignore == j_store);
|
||||
}
|
||||
CHECK(json::from_cbor(tagged[0], true, true, ignore) == expected);
|
||||
CHECK(json::from_cbor(tagged[1], true, true, store) == expected);
|
||||
|
||||
// nested maps
|
||||
CHECK(json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0, 0x61, 0x61, 0xA1, 0xD8, 0x05, 0x61, 0x62, 0x01}), true, true, ignore) == json{{"a", {{"b", 1}}}});
|
||||
|
||||
// the tag handler error still rejects every tag
|
||||
for (const auto& input : tagged)
|
||||
{
|
||||
CAPTURE(input)
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(input), json::parse_error&);
|
||||
}
|
||||
{
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a tag; last byte: 0xD8", json::parse_error&);
|
||||
}
|
||||
|
||||
// a tag does not turn a key of another type into a text string
|
||||
{
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0, 0x01, 0x01}), true, true, ignore), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xD8, 0x05, 0x41, 0x61, 0x01}), true, true, store), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR object key: only string keys are supported, but found a byte string; last byte: 0x41", json::parse_error&);
|
||||
CHECK(json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0, 0x01, 0x01}), true, false, ignore).is_discarded());
|
||||
}
|
||||
|
||||
// end of input after the tag, or inside the tag number
|
||||
{
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0}), true, true, ignore), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xD8}), true, true, ignore), json::parse_error&);
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xDB, 0x00, 0x00}), true, true, store), json::parse_error&);
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xBF, 0xC0}), true, true, store), json::parse_error&);
|
||||
}
|
||||
|
||||
// SAX: the key event is reported without the tag (start_object,
|
||||
// key, number_unsigned, end_object are four events)
|
||||
{
|
||||
const std::vector<std::uint8_t> input = {0xA1, 0xC0, 0x61, 0x61, 0x0A};
|
||||
SaxCountdown four(4);
|
||||
CHECK(json::sax_parse(input, &four, json::input_format_t::cbor, true, false, false, ignore));
|
||||
SaxCountdown three(3);
|
||||
CHECK(!json::sax_parse(input, &three, json::input_format_t::cbor, true, false, false, ignore));
|
||||
SaxCountdown error_handler(4);
|
||||
CHECK(!json::sax_parse(input, &error_handler, json::input_format_t::cbor));
|
||||
}
|
||||
|
||||
// a long chain of tags needs no recursion
|
||||
{
|
||||
std::vector<std::uint8_t> input = {0xA1};
|
||||
input.insert(input.end(), 100000, 0xC0);
|
||||
input.insert(input.end(), {0x61, 0x61, 0x0A});
|
||||
CHECK(json::from_cbor(input, true, true, ignore) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("non-string key (see #2766 and #3381)")
|
||||
{
|
||||
// only text strings map to JSON object keys; any other key is
|
||||
@@ -1915,9 +2002,10 @@ TEST_CASE("CBOR")
|
||||
{
|
||||
json _;
|
||||
|
||||
// the chunks are concatenated as is, without checking that each
|
||||
// chunk is valid UTF-8 on its own (RFC 8949, Section 3.2.3), so
|
||||
// a code point split across two chunks yields a valid string
|
||||
// with the default error handler keep, the chunks are concatenated
|
||||
// as is, without checking that each chunk is valid UTF-8 on its
|
||||
// own (RFC 8949, Section 3.2.3), so a code point split across two
|
||||
// chunks yields a valid string
|
||||
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff})));
|
||||
CHECK(_ == "\xc3\xa9");
|
||||
CHECK(_.dump() == "\"\xc3\xa9\"");
|
||||
@@ -1937,6 +2025,63 @@ TEST_CASE("CBOR")
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6");
|
||||
}
|
||||
|
||||
SECTION("ill-formed UTF-8 in a chunk of an indefinite-length string (RFC 8949, Section 3.2.3)")
|
||||
{
|
||||
const auto strict = json::error_handler_t::strict;
|
||||
const auto replace = json::error_handler_t::replace;
|
||||
const auto ignore = json::error_handler_t::ignore;
|
||||
const auto keep = json::error_handler_t::keep;
|
||||
const auto tags = json::cbor_tag_handler_t::error;
|
||||
|
||||
// a code point split over two chunks
|
||||
const std::vector<std::uint8_t> split = {0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff};
|
||||
// a 4-byte code point split over two chunks
|
||||
const std::vector<std::uint8_t> split_emoji = {0x7f, 0x62, 0xf0, 0x9f, 0x62, 0x98, 0x80, 0xff};
|
||||
// the same in an object key
|
||||
const std::vector<std::uint8_t> split_key = {0xa1, 0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff, 0x01};
|
||||
// a valid chunk followed by one with a truncated code point
|
||||
const std::vector<std::uint8_t> truncated = {0x7f, 0x62, 0x41, 0xc3, 0x61, 0xa9, 0xff};
|
||||
|
||||
// strict: the string is invalid
|
||||
for (const auto& input :
|
||||
{
|
||||
split, split_emoji, split_key, truncated
|
||||
})
|
||||
{
|
||||
CAPTURE(input)
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(input, true, true, tags, strict), json::parse_error&);
|
||||
CHECK(json::from_cbor(input, true, false, tags, strict).is_discarded());
|
||||
}
|
||||
{
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(split, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(split_key, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
}
|
||||
|
||||
// replace and ignore: every chunk is sanitized on its own
|
||||
CHECK(json::from_cbor(split, true, true, tags, replace) == "\xEF\xBF\xBD\xEF\xBF\xBD");
|
||||
CHECK(json::from_cbor(split, true, true, tags, ignore) == "");
|
||||
CHECK(json::from_cbor(truncated, true, true, tags, replace) == "A\xEF\xBF\xBD\xEF\xBF\xBD");
|
||||
CHECK(json::from_cbor(truncated, true, true, tags, ignore) == "A");
|
||||
CHECK(json::from_cbor(split_key, true, true, tags, replace) == json{{"\xEF\xBF\xBD\xEF\xBF\xBD", 1}});
|
||||
|
||||
// keep: unchanged, the chunks are concatenated as is
|
||||
CHECK(json::from_cbor(split, true, true, tags, keep) == "\xc3\xa9");
|
||||
CHECK(json::from_cbor(split_emoji, true, true, tags, keep) == "\xf0\x9f\x98\x80");
|
||||
|
||||
// conforming input is unaffected by the error handler
|
||||
const std::vector<std::uint8_t> valid = {0x7f, 0x62, 0xc3, 0xa9, 0x64, 0xf0, 0x9f, 0x98, 0x80, 0x60, 0xff};
|
||||
for (const auto handler :
|
||||
{
|
||||
strict, replace, ignore, keep
|
||||
})
|
||||
{
|
||||
CHECK(json::from_cbor(valid, true, true, tags, handler) == "\xc3\xa9\xf0\x9f\x98\x80");
|
||||
CHECK(json::from_cbor(std::vector<std::uint8_t>({0x7f, 0xff}), true, true, tags, handler) == "");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("many chunks in indefinite-length string")
|
||||
{
|
||||
constexpr std::size_t chunks = 100000;
|
||||
|
||||
@@ -1515,24 +1515,6 @@ TEST_CASE("JSON patch - add to a primitive parent (regression #4292)")
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("null parent")
|
||||
{
|
||||
json const doc = {{"foo", nullptr}};
|
||||
json const patch = {{{"op", "add"}, {"path", "/foo/bar"}, {"value", 1}}};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] (/foo) cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("null document")
|
||||
{
|
||||
json const doc = nullptr;
|
||||
json const patch = {{{"op", "add"}, {"path", "/-"}, {"value", 1}}};
|
||||
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("number parent")
|
||||
{
|
||||
json const doc = {{"foo", 1}};
|
||||
|
||||
@@ -2023,7 +2023,7 @@ TEST_CASE("UBJSON")
|
||||
CHECK(json::from_ubjson(v0, true, false).is_discarded());
|
||||
|
||||
std::vector<uint8_t> const vi = {'[', '$', '#'};
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x23 is not a permitted optimized array type", json::parse_error&);
|
||||
CHECK(json::from_ubjson(vi, true, false).is_discarded());
|
||||
|
||||
std::vector<uint8_t> const vT = {'[', '$', 'T'};
|
||||
@@ -3348,3 +3348,55 @@ TEST_CASE("UBJSON large strings (chunked reader)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("UBJSON optimized container type marker is validated for count 0")
|
||||
{
|
||||
// the '$' type marker must be a legal marker even if no element follows
|
||||
SECTION("invalid marker")
|
||||
{
|
||||
for (const char marker :
|
||||
{'x', 'E', 'B', 'u', 'm', 'M', 'h'
|
||||
})
|
||||
{
|
||||
for (const char open :
|
||||
{'[', '{'
|
||||
})
|
||||
{
|
||||
for (const int count :
|
||||
{
|
||||
0, 1
|
||||
})
|
||||
{
|
||||
CAPTURE(marker)
|
||||
CAPTURE(open)
|
||||
CAPTURE(count)
|
||||
const std::vector<std::uint8_t> v = {static_cast<std::uint8_t>(open), '$', static_cast<std::uint8_t>(marker), '#', 'i', static_cast<std::uint8_t>(count)};
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_ubjson(v), json::parse_error&);
|
||||
CHECK(json::from_ubjson(v, true, false).is_discarded());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("error message")
|
||||
{
|
||||
const std::vector<std::uint8_t> v = {'[', '$', 'x', '#', 'i', 0};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x78 is not a permitted optimized array type", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("valid marker")
|
||||
{
|
||||
for (const char marker :
|
||||
{'Z', 'N', 'T', 'F', 'i', 'U', 'I', 'l', 'L', 'd', 'D', 'H', 'C', 'S', '[', '{'
|
||||
})
|
||||
{
|
||||
CAPTURE(marker)
|
||||
const std::vector<std::uint8_t> va = {'[', '$', static_cast<std::uint8_t>(marker), '#', 'i', 0};
|
||||
const std::vector<std::uint8_t> vo = {'{', '$', static_cast<std::uint8_t>(marker), '#', 'i', 0};
|
||||
CHECK(json::from_ubjson(va) == json::array());
|
||||
CHECK(json::from_ubjson(vo) == json::object());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user