forked from bblanchon/ArduinoJson
Fix lax parsing of true
, false
, and null
(fixes #1781)
This commit is contained in:
@ -7,6 +7,7 @@ HEAD
|
|||||||
* Add `JsonVariant::shallowCopy()` (issue #1343)
|
* Add `JsonVariant::shallowCopy()` (issue #1343)
|
||||||
* Fix `9.22337e+18 is outside the range of representable values of type 'long'`
|
* Fix `9.22337e+18 is outside the range of representable values of type 'long'`
|
||||||
* Fix comparison operators for `JsonArray`, `JsonArrayConst`, `JsonObject`, and `JsonObjectConst`
|
* Fix comparison operators for `JsonArray`, `JsonArrayConst`, `JsonObject`, and `JsonObjectConst`
|
||||||
|
* Fix lax parsing of `true`, `false`, and `null` (issue #1781)
|
||||||
* Remove undocumented `accept()` functions
|
* Remove undocumented `accept()` functions
|
||||||
* Rename `addElement()` to `add()`
|
* Rename `addElement()` to `add()`
|
||||||
* Remove `getElement()`, `getOrAddElement()`, `getMember()`, and `getOrAddMember()`
|
* Remove `getElement()`, `getOrAddElement()`, `getMember()`, and `getOrAddMember()`
|
||||||
|
@ -71,6 +71,15 @@ TEST_CASE("Filtering") {
|
|||||||
"{\"example\":null}",
|
"{\"example\":null}",
|
||||||
JSON_OBJECT_SIZE(1) + 8
|
JSON_OBJECT_SIZE(1) + 8
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Member is a number, but filter wants an array
|
||||||
|
"{\"example\":42}",
|
||||||
|
"{\"example\":[true]}",
|
||||||
|
10,
|
||||||
|
DeserializationError::Ok,
|
||||||
|
"{\"example\":null}",
|
||||||
|
JSON_OBJECT_SIZE(1) + 8
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Input is an array, but filter wants an object
|
// Input is an array, but filter wants an object
|
||||||
"[\"hello\",\"world\"]",
|
"[\"hello\",\"world\"]",
|
||||||
@ -117,7 +126,7 @@ TEST_CASE("Filtering") {
|
|||||||
JSON_OBJECT_SIZE(1) + 8
|
JSON_OBJECT_SIZE(1) + 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// can skip a boolean
|
// skip false
|
||||||
"{\"a_bool\":false,example:42}",
|
"{\"a_bool\":false,example:42}",
|
||||||
"{\"example\":true}",
|
"{\"example\":true}",
|
||||||
10,
|
10,
|
||||||
@ -125,6 +134,24 @@ TEST_CASE("Filtering") {
|
|||||||
"{\"example\":42}",
|
"{\"example\":42}",
|
||||||
JSON_OBJECT_SIZE(1) + 8
|
JSON_OBJECT_SIZE(1) + 8
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// skip true
|
||||||
|
"{\"a_bool\":true,example:42}",
|
||||||
|
"{\"example\":true}",
|
||||||
|
10,
|
||||||
|
DeserializationError::Ok,
|
||||||
|
"{\"example\":42}",
|
||||||
|
JSON_OBJECT_SIZE(1) + 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// skip null
|
||||||
|
"{\"a_bool\":null,example:42}",
|
||||||
|
"{\"example\":true}",
|
||||||
|
10,
|
||||||
|
DeserializationError::Ok,
|
||||||
|
"{\"example\":42}",
|
||||||
|
JSON_OBJECT_SIZE(1) + 8
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// can skip a double-quoted string
|
// can skip a double-quoted string
|
||||||
"{\"a_double_quoted_string\":\"hello\",example:42}",
|
"{\"a_double_quoted_string\":\"hello\",example:42}",
|
||||||
@ -618,7 +645,7 @@ TEST_CASE("Filtering") {
|
|||||||
0
|
0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// incomplete after after key of a skipped object
|
// incomplete comment after key of a skipped object
|
||||||
"{\"example\"/*:2}",
|
"{\"example\"/*:2}",
|
||||||
"false",
|
"false",
|
||||||
10,
|
10,
|
||||||
@ -636,7 +663,7 @@ TEST_CASE("Filtering") {
|
|||||||
0
|
0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// incomplete after after value of a skipped object
|
// incomplete comment after value of a skipped object
|
||||||
"{\"example\":2/*}",
|
"{\"example\":2/*}",
|
||||||
"false",
|
"false",
|
||||||
10,
|
10,
|
||||||
@ -644,6 +671,15 @@ TEST_CASE("Filtering") {
|
|||||||
"null",
|
"null",
|
||||||
0
|
0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// incomplete comment after comma in skipped object
|
||||||
|
"{\"example\":2,/*}",
|
||||||
|
"false",
|
||||||
|
10,
|
||||||
|
DeserializationError::IncompleteInput,
|
||||||
|
"null",
|
||||||
|
0
|
||||||
|
},
|
||||||
}; // clang-format on
|
}; // clang-format on
|
||||||
|
|
||||||
for (size_t i = 0; i < sizeof(testCases) / sizeof(testCases[0]); i++) {
|
for (size_t i = 0; i < sizeof(testCases) / sizeof(testCases[0]); i++) {
|
||||||
|
@ -9,7 +9,8 @@
|
|||||||
TEST_CASE("Invalid JSON input") {
|
TEST_CASE("Invalid JSON input") {
|
||||||
const char* testCases[] = {"'\\u'", "'\\u000g'", "'\\u000'", "'\\u000G'",
|
const char* testCases[] = {"'\\u'", "'\\u000g'", "'\\u000'", "'\\u000G'",
|
||||||
"'\\u000/'", "\\x1234", "6a9", "1,",
|
"'\\u000/'", "\\x1234", "6a9", "1,",
|
||||||
"2]", "3}"};
|
"nulL", "tru3", "fals3", "2]",
|
||||||
|
"3}"};
|
||||||
const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
|
const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
|
||||||
|
|
||||||
DynamicJsonDocument doc(4096);
|
DynamicJsonDocument doc(4096);
|
||||||
@ -23,9 +24,6 @@ TEST_CASE("Invalid JSON input") {
|
|||||||
|
|
||||||
TEST_CASE("Invalid JSON input that should pass") {
|
TEST_CASE("Invalid JSON input that should pass") {
|
||||||
const char* testCases[] = {
|
const char* testCases[] = {
|
||||||
"nulL",
|
|
||||||
"tru3",
|
|
||||||
"fals3",
|
|
||||||
"'\\ud83d'", // leading surrogate without a trailing surrogate
|
"'\\ud83d'", // leading surrogate without a trailing surrogate
|
||||||
"'\\udda4'", // trailing surrogate without a leading surrogate
|
"'\\udda4'", // trailing surrogate without a leading surrogate
|
||||||
"'\\ud83d\\ud83d'", // two leading surrogates
|
"'\\ud83d\\ud83d'", // two leading surrogates
|
||||||
|
@ -85,7 +85,22 @@ class JsonDeserializer {
|
|||||||
if (filter.allowValue())
|
if (filter.allowValue())
|
||||||
return parseStringValue(variant);
|
return parseStringValue(variant);
|
||||||
else
|
else
|
||||||
return skipString();
|
return skipQuotedString();
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
if (filter.allowValue())
|
||||||
|
variant.setBoolean(true);
|
||||||
|
return skipKeyword("true");
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
if (filter.allowValue())
|
||||||
|
variant.setBoolean(false);
|
||||||
|
return skipKeyword("false");
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
// the variant should already by null, except if the same object key was
|
||||||
|
// used twice, as in {"a":1,"a":null}
|
||||||
|
return skipKeyword("null");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (filter.allowValue())
|
if (filter.allowValue())
|
||||||
@ -111,7 +126,16 @@ class JsonDeserializer {
|
|||||||
|
|
||||||
case '\"':
|
case '\"':
|
||||||
case '\'':
|
case '\'':
|
||||||
return skipString();
|
return skipQuotedString();
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
return skipKeyword("true");
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
return skipKeyword("false");
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
return skipKeyword("null");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return skipNumericValue();
|
return skipNumericValue();
|
||||||
@ -310,7 +334,7 @@ class JsonDeserializer {
|
|||||||
// Read each key value pair
|
// Read each key value pair
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Skip key
|
// Skip key
|
||||||
err = skipVariant(nestingLimit.decrement());
|
err = skipKey();
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
@ -338,6 +362,10 @@ class JsonDeserializer {
|
|||||||
return DeserializationError::Ok;
|
return DeserializationError::Ok;
|
||||||
if (!eat(','))
|
if (!eat(','))
|
||||||
return DeserializationError::InvalidInput;
|
return DeserializationError::InvalidInput;
|
||||||
|
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,7 +466,15 @@ class JsonDeserializer {
|
|||||||
return DeserializationError::Ok;
|
return DeserializationError::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeserializationError::Code skipString() {
|
DeserializationError::Code skipKey() {
|
||||||
|
if (isQuote(current())) {
|
||||||
|
return skipQuotedString();
|
||||||
|
} else {
|
||||||
|
return skipNonQuotedString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipQuotedString() {
|
||||||
const char stopChar = current();
|
const char stopChar = current();
|
||||||
|
|
||||||
move();
|
move();
|
||||||
@ -458,37 +494,26 @@ class JsonDeserializer {
|
|||||||
return DeserializationError::Ok;
|
return DeserializationError::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipNonQuotedString() {
|
||||||
|
char c = current();
|
||||||
|
while (canBeInNonQuotedString(c)) {
|
||||||
|
move();
|
||||||
|
c = current();
|
||||||
|
}
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
DeserializationError::Code parseNumericValue(VariantData &result) {
|
DeserializationError::Code parseNumericValue(VariantData &result) {
|
||||||
uint8_t n = 0;
|
uint8_t n = 0;
|
||||||
|
|
||||||
char c = current();
|
char c = current();
|
||||||
while (canBeInNonQuotedString(c) && n < 63) {
|
while (canBeInNumber(c) && n < 63) {
|
||||||
move();
|
move();
|
||||||
_buffer[n++] = c;
|
_buffer[n++] = c;
|
||||||
c = current();
|
c = current();
|
||||||
}
|
}
|
||||||
_buffer[n] = 0;
|
_buffer[n] = 0;
|
||||||
|
|
||||||
c = _buffer[0];
|
|
||||||
if (c == 't') { // true
|
|
||||||
result.setBoolean(true);
|
|
||||||
if (n != 4)
|
|
||||||
return DeserializationError::IncompleteInput;
|
|
||||||
return DeserializationError::Ok;
|
|
||||||
}
|
|
||||||
if (c == 'f') { // false
|
|
||||||
result.setBoolean(false);
|
|
||||||
if (n != 5)
|
|
||||||
return DeserializationError::IncompleteInput;
|
|
||||||
return DeserializationError::Ok;
|
|
||||||
}
|
|
||||||
if (c == 'n') { // null
|
|
||||||
// the variant is already null
|
|
||||||
if (n != 4)
|
|
||||||
return DeserializationError::IncompleteInput;
|
|
||||||
return DeserializationError::Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parseNumber(_buffer, result))
|
if (!parseNumber(_buffer, result))
|
||||||
return DeserializationError::InvalidInput;
|
return DeserializationError::InvalidInput;
|
||||||
|
|
||||||
@ -497,7 +522,7 @@ class JsonDeserializer {
|
|||||||
|
|
||||||
DeserializationError::Code skipNumericValue() {
|
DeserializationError::Code skipNumericValue() {
|
||||||
char c = current();
|
char c = current();
|
||||||
while (canBeInNonQuotedString(c)) {
|
while (canBeInNumber(c)) {
|
||||||
move();
|
move();
|
||||||
c = current();
|
c = current();
|
||||||
}
|
}
|
||||||
@ -523,9 +548,18 @@ class JsonDeserializer {
|
|||||||
return min <= c && c <= max;
|
return min <= c && c <= max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool canBeInNumber(char c) {
|
||||||
|
return isBetween(c, '0', '9') || c == '+' || c == '-' || c == '.' ||
|
||||||
|
#if ARDUINOJSON_ENABLE_NAN || ARDUINOJSON_ENABLE_INFINITY
|
||||||
|
isBetween(c, 'A', 'Z') || isBetween(c, 'a', 'z');
|
||||||
|
#else
|
||||||
|
c == 'e' || c == 'E';
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool canBeInNonQuotedString(char c) {
|
static inline bool canBeInNonQuotedString(char c) {
|
||||||
return isBetween(c, '0', '9') || isBetween(c, '_', 'z') ||
|
return isBetween(c, '0', '9') || isBetween(c, '_', 'z') ||
|
||||||
isBetween(c, 'A', 'Z') || c == '+' || c == '-' || c == '.';
|
isBetween(c, 'A', 'Z');
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool isQuote(char c) {
|
static inline bool isQuote(char c) {
|
||||||
@ -605,6 +639,19 @@ class JsonDeserializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipKeyword(const char *s) {
|
||||||
|
while (*s) {
|
||||||
|
char c = current();
|
||||||
|
if (c == '\0')
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
if (*s != c)
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
++s;
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
TStringStorage _stringStorage;
|
TStringStorage _stringStorage;
|
||||||
bool _foundSomething;
|
bool _foundSomething;
|
||||||
Latch<TReader> _latch;
|
Latch<TReader> _latch;
|
||||||
|
Reference in New Issue
Block a user