diff --git a/CHANGELOG.md b/CHANGELOG.md index a368a6a9..cc8ebffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ ArduinoJson: change log ======================= +HEAD +---- + +* Disabled lazy number deserialization (issue #772) + +> ### BREAKING CHANGES +> +> Non quoted strings are now forbidden in values, but they are still allowed in keys. +> For example, `{key:"value"}` is accepted, but `{key:value}` is not. + v6.1.0-beta ----------- @@ -15,7 +25,7 @@ v6.1.0-beta > JsonObject& obj = doc.to(); > JsonArray& arr = obj.createNestedArray("key"); > if (!arr.success()) { -> Serial.println("No enough memory"); +> Serial.println("Not enough memory"); > return; > } > ``` @@ -26,7 +36,7 @@ v6.1.0-beta > JsonObject obj = doc.to(); > JsonArray arr = obj.createNestedArray("key"); > if (arr.isNull()) { -> Serial.println("No enough memory"); +> Serial.println("Not enough memory"); > return; > } > ``` diff --git a/src/ArduinoJson/Json/JsonDeserializer.hpp b/src/ArduinoJson/Json/JsonDeserializer.hpp index c1314b09..6ab00779 100644 --- a/src/ArduinoJson/Json/JsonDeserializer.hpp +++ b/src/ArduinoJson/Json/JsonDeserializer.hpp @@ -121,7 +121,7 @@ class JsonDeserializer { for (;;) { // Parse key const char *key; - err = parseString(&key); + err = parseKey(&key); if (err) return err; // Skip spaces @@ -152,48 +152,69 @@ class JsonDeserializer { } DeserializationError parseValue(JsonVariant &variant) { - bool hasQuotes = isQuote(current()); - const char *value; - DeserializationError error = parseString(&value); - if (error) return error; - if (hasQuotes) { - variant = value; + if (isQuote(current())) { + return parseStringValue(variant); } else { - variant = RawJson(value); + return parseNumericValue(variant); } + } + + DeserializationError parseKey(const char **key) { + if (isQuote(current())) { + return parseQuotedString(key); + } else { + return parseNonQuotedString(key); + } + } + + DeserializationError parseStringValue(JsonVariant &variant) { + const char *value; + DeserializationError err = parseQuotedString(&value); + if (err) return err; + variant = value; return DeserializationError::Ok; } - DeserializationError parseString(const char **result) { + DeserializationError parseQuotedString(const char **result) { + typename remove_reference::type::String str = + _stringStorage.startString(); + + char stopChar = current(); + + move(); + for (;;) { + char c = current(); + move(); + if (c == stopChar) break; + + if (c == '\0') return DeserializationError::IncompleteInput; + + if (c == '\\') { + c = current(); + if (c == '\0') return DeserializationError::IncompleteInput; + if (c == 'u') return DeserializationError::NotSupported; + // replace char + c = EscapeSequence::unescapeChar(c); + if (c == '\0') return DeserializationError::InvalidInput; + move(); + } + + str.append(c); + } + + *result = str.c_str(); + if (*result == NULL) return DeserializationError::NoMemory; + return DeserializationError::Ok; + } + + DeserializationError parseNonQuotedString(const char **result) { typename remove_reference::type::String str = _stringStorage.startString(); char c = current(); if (c == '\0') return DeserializationError::IncompleteInput; - if (isQuote(c)) { // quotes - move(); - char stopChar = c; - for (;;) { - c = current(); - move(); - if (c == stopChar) break; - - if (c == '\0') return DeserializationError::IncompleteInput; - - if (c == '\\') { - c = current(); - if (c == '\0') return DeserializationError::IncompleteInput; - if (c == 'u') return DeserializationError::NotSupported; - // replace char - c = EscapeSequence::unescapeChar(c); - if (c == '\0') return DeserializationError::InvalidInput; - move(); - } - - str.append(c); - } - } else if (canBeInNonQuotedString(c)) { // no quotes + if (canBeInNonQuotedString(c)) { // no quotes do { move(); str.append(c); @@ -208,6 +229,34 @@ class JsonDeserializer { return DeserializationError::Ok; } + DeserializationError parseNumericValue(JsonVariant &result) { + char buffer[64]; + uint8_t n = 0; + + char c = current(); + while (canBeInNonQuotedString(c) && n < 63) { + move(); + buffer[n++] = c; + c = current(); + } + buffer[n] = 0; + + if (isInteger(buffer)) { + result = parseInteger(buffer); + } else if (isFloat(buffer)) { + result = parseFloat(buffer); + } else if (!strcmp(buffer, "true")) { + result = true; + } else if (!strcmp(buffer, "false")) { + result = false; + } else if (!strcmp(buffer, "null")) { + result = static_cast(0); + } else { + return DeserializationError::InvalidInput; + } + return DeserializationError::Ok; + } + static inline bool isBetween(char c, char min, char max) { return min <= c && c <= max; } @@ -286,7 +335,7 @@ class JsonDeserializer { uint8_t _nestingLimit; char _current; bool _loaded; -}; +}; // namespace Internals } // namespace Internals template diff --git a/src/ArduinoJson/Numbers/isInteger.hpp b/src/ArduinoJson/Numbers/isInteger.hpp index b87ebc25..e1457f13 100644 --- a/src/ArduinoJson/Numbers/isInteger.hpp +++ b/src/ArduinoJson/Numbers/isInteger.hpp @@ -10,7 +10,7 @@ namespace ArduinoJson { namespace Internals { inline bool isInteger(const char* s) { - if (!s) return false; + if (!s || !*s) return false; if (issign(*s)) s++; while (isdigit(*s)) s++; return *s == '\0'; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e3fa8284..724c5181 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -67,13 +67,13 @@ endif() add_subdirectory(DynamicJsonBuffer) add_subdirectory(IntegrationTests) add_subdirectory(JsonArray) -add_subdirectory(JsonObject) add_subdirectory(JsonDeserializer) +add_subdirectory(JsonObject) add_subdirectory(JsonSerializer) add_subdirectory(JsonVariant) add_subdirectory(JsonWriter) add_subdirectory(Misc) add_subdirectory(MsgPackDeserializer) add_subdirectory(MsgPackSerializer) -add_subdirectory(Polyfills) +add_subdirectory(Numbers) add_subdirectory(StaticJsonBuffer) diff --git a/test/IntegrationTests/CMakeLists.txt b/test/IntegrationTests/CMakeLists.txt index c158367e..d65e743e 100644 --- a/test/IntegrationTests/CMakeLists.txt +++ b/test/IntegrationTests/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(IntegrationTests gbathree.cpp + issue772.cpp round_trip.cpp ) diff --git a/test/IntegrationTests/issue772.cpp b/test/IntegrationTests/issue772.cpp new file mode 100644 index 00000000..21ee4efc --- /dev/null +++ b/test/IntegrationTests/issue772.cpp @@ -0,0 +1,27 @@ +// ArduinoJson - arduinojson.org +// Copyright Benoit Blanchon 2014-2018 +// MIT License + +#include +#include + +// https://github.com/bblanchon/ArduinoJson/issues/772 + +TEST_CASE("Issue772") { + DynamicJsonDocument doc1, doc2; + DeserializationError err; + std::string data = + "{\"state\":{\"reported\":{\"timestamp\":\"2018-07-02T09:40:12Z\"," + "\"mac\":\"2C3AE84FC076\",\"firmwareVersion\":\"v0.2.7-5-gf4d4d78\"," + "\"visibleLight\":261,\"infraRed\":255,\"ultraViolet\":0.02," + "\"Temperature\":26.63,\"Pressure\":101145.7,\"Humidity\":54.79883," + "\"Vbat\":4.171261,\"soilMoisture\":0,\"ActB\":0}}}"; + err = deserializeJson(doc1, data); + REQUIRE(err == DeserializationError::Ok); + + data = ""; + serializeMsgPack(doc1, data); + err = deserializeMsgPack(doc2, data); + + REQUIRE(err == DeserializationError::Ok); +} diff --git a/test/JsonDeserializer/deserializeJsonArray.cpp b/test/JsonDeserializer/deserializeJsonArray.cpp index cec7f105..260fec3e 100644 --- a/test/JsonDeserializer/deserializeJsonArray.cpp +++ b/test/JsonDeserializer/deserializeJsonArray.cpp @@ -128,12 +128,7 @@ TEST_CASE("deserialize JSON array") { SECTION("No quotes") { DeserializationError err = deserializeJson(doc, "[ hello , world ]"); - JsonArray arr = doc.as(); - - REQUIRE(err == DeserializationError::Ok); - REQUIRE(2 == arr.size()); - REQUIRE(arr[0] == "hello"); - REQUIRE(arr[1] == "world"); + REQUIRE(err == DeserializationError::InvalidInput); } SECTION("Double quotes (empty strings)") { diff --git a/test/JsonDeserializer/deserializeJsonObject.cpp b/test/JsonDeserializer/deserializeJsonObject.cpp index df6da728..eee363ca 100644 --- a/test/JsonDeserializer/deserializeJsonObject.cpp +++ b/test/JsonDeserializer/deserializeJsonObject.cpp @@ -39,7 +39,7 @@ TEST_CASE("deserialize JSON object") { } SECTION("No quotes") { - DeserializationError err = deserializeJson(doc, "{key:value}"); + DeserializationError err = deserializeJson(doc, "{key:'value'}"); JsonObject obj = doc.as(); REQUIRE(err == DeserializationError::Ok); diff --git a/test/JsonDeserializer/std_istream.cpp b/test/JsonDeserializer/std_istream.cpp index 771885ce..8f698389 100644 --- a/test/JsonDeserializer/std_istream.cpp +++ b/test/JsonDeserializer/std_istream.cpp @@ -21,7 +21,7 @@ TEST_CASE("deserializeJson(std::istream&)") { } SECTION("object") { - std::istringstream json(" { hello : world // comment\n }"); + std::istringstream json(" { hello : 'world' // comment\n }"); DeserializationError err = deserializeJson(doc, json); JsonObject obj = doc.as(); diff --git a/test/Polyfills/CMakeLists.txt b/test/Numbers/CMakeLists.txt similarity index 57% rename from test/Polyfills/CMakeLists.txt rename to test/Numbers/CMakeLists.txt index d96ce719..3f3dc9e8 100644 --- a/test/Polyfills/CMakeLists.txt +++ b/test/Numbers/CMakeLists.txt @@ -2,12 +2,12 @@ # Copyright Benoit Blanchon 2014-2018 # MIT License -add_executable(PolyfillsTests +add_executable(NumbersTests isFloat.cpp isInteger.cpp parseFloat.cpp parseInteger.cpp ) -target_link_libraries(PolyfillsTests catch) -add_test(Polyfills PolyfillsTests) +target_link_libraries(NumbersTests catch) +add_test(Numbers NumbersTests) diff --git a/test/Numbers/isFloat.cpp b/test/Numbers/isFloat.cpp new file mode 100644 index 00000000..3edce987 --- /dev/null +++ b/test/Numbers/isFloat.cpp @@ -0,0 +1,80 @@ +// ArduinoJson - arduinojson.org +// Copyright Benoit Blanchon 2014-2018 +// MIT License + +#include +#include + +using namespace ArduinoJson::Internals; + +TEST_CASE("isFloat()") { + SECTION("Input is NULL") { + REQUIRE(isFloat(NULL) == false); + } + + SECTION("Empty string") { + REQUIRE(isFloat("") == false); + } + + SECTION("NoExponent") { + REQUIRE(isFloat("3.14") == true); + REQUIRE(isFloat("-3.14") == true); + REQUIRE(isFloat("+3.14") == true); + } + + SECTION("IntegralPartMissing") { + REQUIRE(isFloat(".14") == true); + REQUIRE(isFloat("-.14") == true); + REQUIRE(isFloat("+.14") == true); + } + + SECTION("FractionalPartMissing") { + REQUIRE(isFloat("3.") == true); + REQUIRE(isFloat("-3.e14") == true); + REQUIRE(isFloat("+3.e-14") == true); + } + + SECTION("NoDot") { + REQUIRE(isFloat("3e14") == true); + REQUIRE(isFloat("3e-14") == true); + REQUIRE(isFloat("3e+14") == true); + } + + SECTION("Integer") { + REQUIRE(isFloat("14") == true); + REQUIRE(isFloat("-14") == true); + REQUIRE(isFloat("+14") == true); + } + + SECTION("ExponentMissing") { + REQUIRE(isFloat("3.14e") == false); + REQUIRE(isFloat("3.14e-") == false); + REQUIRE(isFloat("3.14e+") == false); + } + + SECTION("JustASign") { + REQUIRE(isFloat("-") == false); + REQUIRE(isFloat("+") == false); + } + + SECTION("Empty") { + REQUIRE(isFloat("") == false); + } + + SECTION("NaN") { + REQUIRE(isFloat("NaN") == true); + REQUIRE(isFloat("n") == false); + REQUIRE(isFloat("N") == false); + REQUIRE(isFloat("nan") == false); + REQUIRE(isFloat("-NaN") == false); + REQUIRE(isFloat("+NaN") == false); + } + + SECTION("Infinity") { + REQUIRE(isFloat("Infinity") == true); + REQUIRE(isFloat("+Infinity") == true); + REQUIRE(isFloat("-Infinity") == true); + REQUIRE(isFloat("infinity") == false); + REQUIRE(isFloat("Inf") == false); + } +} diff --git a/test/Numbers/isInteger.cpp b/test/Numbers/isInteger.cpp new file mode 100644 index 00000000..650d37c0 --- /dev/null +++ b/test/Numbers/isInteger.cpp @@ -0,0 +1,40 @@ +// ArduinoJson - arduinojson.org +// Copyright Benoit Blanchon 2014-2018 +// MIT License + +#include +#include + +using namespace ArduinoJson::Internals; + +TEST_CASE("isInteger()") { + SECTION("Null") { + REQUIRE(isInteger(NULL) == false); + } + + SECTION("Empty string") { + REQUIRE(isInteger("") == false); + } + + SECTION("FloatNotInteger") { + REQUIRE(isInteger("3.14") == false); + REQUIRE(isInteger("-3.14") == false); + REQUIRE(isInteger("+3.14") == false); + } + + SECTION("Spaces") { + REQUIRE(isInteger("42 ") == false); + REQUIRE(isInteger(" 42") == false); + } + + SECTION("Valid") { + REQUIRE(isInteger("42") == true); + REQUIRE(isInteger("-42") == true); + REQUIRE(isInteger("+42") == true); + } + + SECTION("ExtraSign") { + REQUIRE(isInteger("--42") == false); + REQUIRE(isInteger("++42") == false); + } +} diff --git a/test/Polyfills/parseFloat.cpp b/test/Numbers/parseFloat.cpp similarity index 100% rename from test/Polyfills/parseFloat.cpp rename to test/Numbers/parseFloat.cpp diff --git a/test/Polyfills/parseInteger.cpp b/test/Numbers/parseInteger.cpp similarity index 100% rename from test/Polyfills/parseInteger.cpp rename to test/Numbers/parseInteger.cpp diff --git a/test/Polyfills/isFloat.cpp b/test/Polyfills/isFloat.cpp deleted file mode 100644 index 3d77ac49..00000000 --- a/test/Polyfills/isFloat.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// ArduinoJson - arduinojson.org -// Copyright Benoit Blanchon 2014-2018 -// MIT License - -#include -#include - -using namespace ArduinoJson::Internals; - -TEST_CASE("isFloat()") { - SECTION("Input is NULL") { - REQUIRE(isFloat(NULL) == false); - } - - SECTION("NoExponent") { - REQUIRE(isFloat("3.14")); - REQUIRE(isFloat("-3.14")); - REQUIRE(isFloat("+3.14")); - } - - SECTION("IntegralPartMissing") { - REQUIRE(isFloat(".14")); - REQUIRE(isFloat("-.14")); - REQUIRE(isFloat("+.14")); - } - - SECTION("FractionalPartMissing") { - REQUIRE(isFloat("3.")); - REQUIRE(isFloat("-3.e14")); - REQUIRE(isFloat("+3.e-14")); - } - - SECTION("NoDot") { - REQUIRE(isFloat("3e14")); - REQUIRE(isFloat("3e-14")); - REQUIRE(isFloat("3e+14")); - } - - SECTION("Integer") { - REQUIRE(isFloat("14")); - REQUIRE(isFloat("-14")); - REQUIRE(isFloat("+14")); - } - - SECTION("ExponentMissing") { - REQUIRE_FALSE(isFloat("3.14e")); - REQUIRE_FALSE(isFloat("3.14e-")); - REQUIRE_FALSE(isFloat("3.14e+")); - } - - SECTION("JustASign") { - REQUIRE_FALSE(isFloat("-")); - REQUIRE_FALSE(isFloat("+")); - } - - SECTION("Empty") { - REQUIRE_FALSE(isFloat("")); - } - - SECTION("NaN") { - REQUIRE(isFloat("NaN")); - REQUIRE_FALSE(isFloat("n")); - REQUIRE_FALSE(isFloat("N")); - REQUIRE_FALSE(isFloat("nan")); - REQUIRE_FALSE(isFloat("-NaN")); - REQUIRE_FALSE(isFloat("+NaN")); - } - - SECTION("Infinity") { - REQUIRE(isFloat("Infinity")); - REQUIRE(isFloat("+Infinity")); - REQUIRE(isFloat("-Infinity")); - REQUIRE_FALSE(isFloat("infinity")); - REQUIRE_FALSE(isFloat("Inf")); - } -} diff --git a/test/Polyfills/isInteger.cpp b/test/Polyfills/isInteger.cpp deleted file mode 100644 index a2f11fc9..00000000 --- a/test/Polyfills/isInteger.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// ArduinoJson - arduinojson.org -// Copyright Benoit Blanchon 2014-2018 -// MIT License - -#include -#include - -using namespace ArduinoJson::Internals; - -TEST_CASE("isInteger()") { - SECTION("Null") { - REQUIRE_FALSE(isInteger(NULL)); - } - - SECTION("FloatNotInteger") { - REQUIRE_FALSE(isInteger("3.14")); - REQUIRE_FALSE(isInteger("-3.14")); - REQUIRE_FALSE(isInteger("+3.14")); - } - - SECTION("Spaces") { - REQUIRE_FALSE(isInteger("42 ")); - REQUIRE_FALSE(isInteger(" 42")); - } - - SECTION("Valid") { - REQUIRE(isInteger("42")); - REQUIRE(isInteger("-42")); - REQUIRE(isInteger("+42")); - } - - SECTION("ExtraSign") { - REQUIRE_FALSE(isInteger("--42")); - REQUIRE_FALSE(isInteger("++42")); - } -}