mirror of
				https://github.com/eledio-devices/thirdparty-ArduinoJson.git
				synced 2025-10-31 16:14:11 +01:00 
			
		
		
		
	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