diff --git a/.gitignore b/.gitignore index 98e8c2c8c..940328992 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /build /bin /lib +/sftp-config.json diff --git a/.travis.yml b/.travis.yml index 4768f0518..b2fc495de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,7 @@ compiler: - clang before_install: - sudo pip install cpp-coveralls -before_script: - - cmake -DCOVERAGE=true . script: - - make && make test + - cmake -DCOVERAGE=true . && make && make test after_success: - - coveralls --exclude test --exclude third-party --gcov-options '\-lp' \ No newline at end of file + - if [ "$CC" = "gcc" ]; then coveralls --exclude third-party --gcov-options '\-lp'; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 2add9888b..e30858a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ v5.0 (currently in beta) ---- * Added support of `String` class (issue #55, #56, #70, #77) +* Added support of non standard JSON input (issue #44) * Redesigned `JsonVariant` to leverage converting constructors instead of assignment operators * Switched to new the library layout (requires Arduino 1.0.6 or above) @@ -20,6 +21,15 @@ The `String` class is **bad** because it uses dynamic memory allocation. Compared to static allocation, it compiles to a bigger, slower program, and is less predictable. You certainly don't want that in an embedded environment! +v4.5 +---- + +* Fixed buffer overflow when input contains a backslash followed by a terminator (issue #81) + +**Upgrading is recommended** since previous versions contain a potential security risk. + +Special thanks to [Giancarlo Canales Barreto](https://github.com/gcanalesb) for finding this nasty bug. + v4.4 ---- diff --git a/CMakeLists.txt b/CMakeLists.txt index 47f23e273..1f95d4936 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,9 +12,8 @@ if(MSVC) endif() if(${COVERAGE}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -coverage") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -coverage") + set(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") endif() add_subdirectory(src) -add_subdirectory(test) \ No newline at end of file +add_subdirectory(test) diff --git a/include/ArduinoJson/Internals/Encoding.hpp b/include/ArduinoJson/Internals/Encoding.hpp new file mode 100644 index 000000000..09c20b382 --- /dev/null +++ b/include/ArduinoJson/Internals/Encoding.hpp @@ -0,0 +1,39 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#pragma once + +#include "../Arduino/Print.hpp" + +namespace ArduinoJson { +namespace Internals { + +class Encoding { + public: + // Optimized for code size on a 8-bit AVR + static char escapeChar(char c) { + const char *p = _escapeTable; + while (p[0] && p[1] != c) { + p += 2; + } + return p[0]; + } + + // Optimized for code size on a 8-bit AVR + static char unescapeChar(char c) { + const char *p = _escapeTable + 4; + for (;;) { + if (p[0] == '\0') return c; + if (p[0] == c) return p[1]; + p += 2; + } + } + +private: + static const char _escapeTable[]; +}; +} +} diff --git a/include/ArduinoJson/Internals/JsonParser.hpp b/include/ArduinoJson/Internals/JsonParser.hpp index 9d8278a1d..c6730ef04 100644 --- a/include/ArduinoJson/Internals/JsonParser.hpp +++ b/include/ArduinoJson/Internals/JsonParser.hpp @@ -18,7 +18,10 @@ namespace Internals { class JsonParser { public: JsonParser(JsonBuffer *buffer, char *json, uint8_t nestingLimit) - : _buffer(buffer), _ptr(json), _nestingLimit(nestingLimit) {} + : _buffer(buffer), + _readPtr(json), + _writePtr(json), + _nestingLimit(nestingLimit) {} JsonArray &parseArray(); JsonObject &parseObject(); @@ -26,13 +29,11 @@ class JsonParser { private: bool skip(char charToSkip); bool skip(const char *wordToSkip); - void skipSpaces(); + const char *parseString(); bool parseAnythingTo(JsonVariant *destination); FORCE_INLINE bool parseAnythingToUnsafe(JsonVariant *destination); - const char *parseString(); - inline bool parseArrayTo(JsonVariant *destination); inline bool parseBooleanTo(JsonVariant *destination); inline bool parseNullTo(JsonVariant *destination); @@ -41,7 +42,8 @@ class JsonParser { inline bool parseStringTo(JsonVariant *destination); JsonBuffer *_buffer; - char *_ptr; + const char *_readPtr; + char *_writePtr; uint8_t _nestingLimit; }; } diff --git a/include/ArduinoJson/Internals/JsonPrintable.hpp b/include/ArduinoJson/Internals/JsonPrintable.hpp index 9c51105e6..24bbd6c33 100644 --- a/include/ArduinoJson/Internals/JsonPrintable.hpp +++ b/include/ArduinoJson/Internals/JsonPrintable.hpp @@ -12,6 +12,10 @@ #include "Prettyfier.hpp" #include "StringBuilder.hpp" +#ifdef ARDUINOJSON_ENABLE_STD_STREAM +#include "StreamPrintAdapter.hpp" +#endif + namespace ArduinoJson { namespace Internals { @@ -28,6 +32,14 @@ class JsonPrintable { return writer.bytesWritten(); } +#ifdef ARDUINOJSON_ENABLE_STD_STREAM + std::ostream& printTo(std::ostream &os) const { + StreamPrintAdapter adapter(os); + printTo(adapter); + return os; + } +#endif + size_t printTo(char *buffer, size_t bufferSize) const { StringBuilder sb(buffer, bufferSize); return printTo(sb); @@ -61,5 +73,13 @@ class JsonPrintable { private: const T &downcast() const { return *static_cast(this); } }; + +#ifdef ARDUINOJSON_ENABLE_STD_STREAM +template +inline std::ostream& operator<<(std::ostream& os, const JsonPrintable& v) { + return v.printTo(os); +} +#endif + } } diff --git a/include/ArduinoJson/Internals/JsonWriter.hpp b/include/ArduinoJson/Internals/JsonWriter.hpp index 427cc848c..d9983a3b7 100644 --- a/include/ArduinoJson/Internals/JsonWriter.hpp +++ b/include/ArduinoJson/Internals/JsonWriter.hpp @@ -7,7 +7,7 @@ #pragma once #include "../Arduino/Print.hpp" -#include "QuotedString.hpp" +#include "Encoding.hpp" namespace ArduinoJson { namespace Internals { @@ -26,7 +26,7 @@ class JsonWriter { // Returns the number of bytes sent to the Print implementation. // This is very handy for implementations of printTo() that must return the // number of bytes written. - size_t bytesWritten() { return _length; } + size_t bytesWritten() const { return _length; } void beginArray() { write('['); } void endArray() { write(']'); } @@ -37,15 +37,32 @@ class JsonWriter { void writeColon() { write(':'); } void writeComma() { write(','); } + void writeBoolean(bool value) { + write(value ? "true" : "false"); + } + void writeString(const char *value) { - _length += QuotedString::printTo(value, _sink); + if (!value) { + write("null"); + } else { + write('\"'); + while (*value) writeChar(*value++); + write('\"'); + } + } + + void writeChar(char c) { + char specialChar = Encoding::escapeChar(c); + if (specialChar) { + write('\\'); + write(specialChar); + } else { + write(c); + } } void writeLong(long value) { _length += _sink.print(value); } - void writeBoolean(bool value) { - _length += _sink.print(value ? "true" : "false"); - } void writeDouble(double value, uint8_t decimals) { _length += _sink.print(value, decimals); } diff --git a/include/ArduinoJson/Internals/QuotedString.hpp b/include/ArduinoJson/Internals/QuotedString.hpp deleted file mode 100644 index 091de936e..000000000 --- a/include/ArduinoJson/Internals/QuotedString.hpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright Benoit Blanchon 2014-2015 -// MIT License -// -// Arduino JSON library -// https://github.com/bblanchon/ArduinoJson - -#pragma once - -#include "../Arduino/Print.hpp" - -namespace ArduinoJson { -namespace Internals { - -// An helper class to print and extract doubly-quoted strings -class QuotedString { - public: - // Writes a doubly-quote string to a Print implementation. - // It adds the double quotes (") at the beginning and the end of the string. - // It escapes the special characters as required by the JSON specifications. - static size_t printTo(const char *, Print &); - - // Reads a doubly-quoted string from a buffer. - // It removes the double quotes ("). - // It unescapes the special character as required by the JSON specification, - // with the exception of the Unicode characters (\u0000). - static char *extractFrom(char *input, char **end); -}; -} -} diff --git a/include/ArduinoJson/Internals/StreamPrintAdapter.hpp b/include/ArduinoJson/Internals/StreamPrintAdapter.hpp new file mode 100644 index 000000000..1e2c53b72 --- /dev/null +++ b/include/ArduinoJson/Internals/StreamPrintAdapter.hpp @@ -0,0 +1,34 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#pragma once + +#ifdef ARDUINOJSON_ENABLE_STD_STREAM + +#include "../Arduino/Print.hpp" + +namespace ArduinoJson { +namespace Internals { + +class StreamPrintAdapter : public Print { + public: + explicit StreamPrintAdapter(std::ostream& os) : _os(os) {} + + virtual size_t write(uint8_t c) { + _os << static_cast(c); + return 1; + } + + private: + // cannot be assigned + StreamPrintAdapter& operator=(const StreamPrintAdapter&); + + std::ostream& _os; +}; +} +} + +#endif // ARDUINOJSON_ENABLE_STD_STREAM diff --git a/include/ArduinoJson/JsonArraySubscript.hpp b/include/ArduinoJson/JsonArraySubscript.hpp index 4f2aca38c..177835494 100644 --- a/include/ArduinoJson/JsonArraySubscript.hpp +++ b/include/ArduinoJson/JsonArraySubscript.hpp @@ -40,8 +40,19 @@ class JsonArraySubscript : public JsonVariantBase { return _array.is(_index); } + void writeTo(Internals::JsonWriter &writer) const { + _array.get(_index).writeTo(writer); + } + private: JsonArray& _array; const size_t _index; }; + +#ifdef ARDUINOJSON_ENABLE_STD_STREAM +inline std::ostream& operator<<(std::ostream& os, const JsonArraySubscript& source) { + return source.printTo(os); } +#endif + +} // namespace ArduinoJson diff --git a/include/ArduinoJson/JsonObjectSubscript.hpp b/include/ArduinoJson/JsonObjectSubscript.hpp index 77034713b..697e2b0ed 100644 --- a/include/ArduinoJson/JsonObjectSubscript.hpp +++ b/include/ArduinoJson/JsonObjectSubscript.hpp @@ -41,8 +41,18 @@ class JsonObjectSubscript : public JsonVariantBase { return _object.is(_key); } + void writeTo(Internals::JsonWriter &writer) const { + _object.get(_key).writeTo(writer); + } + private: JsonObject& _object; JsonObjectKey _key; }; + +#ifdef ARDUINOJSON_ENABLE_STD_STREAM +inline std::ostream& operator<<(std::ostream& os, const JsonObjectSubscript& source) { + return source.printTo(os); +} +#endif } diff --git a/include/ArduinoJson/JsonVariant.hpp b/include/ArduinoJson/JsonVariant.hpp index 0afd4b78c..1ee1acc56 100644 --- a/include/ArduinoJson/JsonVariant.hpp +++ b/include/ArduinoJson/JsonVariant.hpp @@ -28,8 +28,7 @@ class JsonObject; // - a char, short, int or a long (signed or unsigned) // - a string (const char*) // - a reference to a JsonArray or JsonObject -class JsonVariant : public Internals::JsonPrintable, - public JsonVariantBase { +class JsonVariant : public JsonVariantBase { public: // Creates an uninitialized JsonVariant FORCE_INLINE JsonVariant() : _type(Internals::JSON_UNDEFINED) {} diff --git a/include/ArduinoJson/JsonVariant.ipp b/include/ArduinoJson/JsonVariant.ipp index 35460572d..818fea690 100644 --- a/include/ArduinoJson/JsonVariant.ipp +++ b/include/ArduinoJson/JsonVariant.ipp @@ -186,4 +186,11 @@ template <> inline bool JsonVariant::is() const { return _type == Internals::JSON_LONG; } + +#ifdef ARDUINOJSON_ENABLE_STD_STREAM +inline std::ostream& operator<<(std::ostream& os, const JsonVariant& source) { + return source.printTo(os); } +#endif + +} // namespace ArduinoJson diff --git a/include/ArduinoJson/JsonVariantBase.hpp b/include/ArduinoJson/JsonVariantBase.hpp index c85ad12dc..fba044b0c 100644 --- a/include/ArduinoJson/JsonVariantBase.hpp +++ b/include/ArduinoJson/JsonVariantBase.hpp @@ -16,7 +16,7 @@ class JsonArraySubscript; class JsonObjectSubscript; template -class JsonVariantBase { +class JsonVariantBase : public Internals::JsonPrintable { public: // Gets the variant as a boolean value. // Returns false if the variant is not a boolean value. @@ -79,6 +79,9 @@ class JsonVariantBase { FORCE_INLINE const JsonObjectSubscript operator[](const char *key) const; FORCE_INLINE const JsonObjectSubscript operator[](const String &key) const; + // Serialize the variant to a JsonWriter + void writeTo(Internals::JsonWriter &writer) const; + private: const TImpl *impl() const { return static_cast(this); } }; diff --git a/src/Internals/Encoding.cpp b/src/Internals/Encoding.cpp new file mode 100644 index 000000000..0120fffa2 --- /dev/null +++ b/src/Internals/Encoding.cpp @@ -0,0 +1,12 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#include "../../include/ArduinoJson/Internals/Encoding.hpp" + +// How to escape special chars: +// _escapeTable[2*i+1] => the special char +// _escapeTable[2*i] => the char to use instead +const char ArduinoJson::Internals::Encoding::_escapeTable[] = "\"\"\\\\b\bf\fn\nr\rt\t"; diff --git a/src/Internals/JsonParser.cpp b/src/Internals/JsonParser.cpp index 2f7e7edd8..553e2f886 100644 --- a/src/Internals/JsonParser.cpp +++ b/src/Internals/JsonParser.cpp @@ -9,7 +9,7 @@ #include // for strtol, strtod #include -#include "../../include/ArduinoJson/Internals/QuotedString.hpp" +#include "../../include/ArduinoJson/Internals/Encoding.hpp" #include "../../include/ArduinoJson/JsonArray.hpp" #include "../../include/ArduinoJson/JsonBuffer.hpp" #include "../../include/ArduinoJson/JsonObject.hpp" @@ -17,25 +17,28 @@ using namespace ArduinoJson; using namespace ArduinoJson::Internals; -void JsonParser::skipSpaces() { - while (isspace(*_ptr)) _ptr++; +static const char *skipSpaces(const char *ptr) { + while (isspace(*ptr)) ptr++; + return ptr; } bool JsonParser::skip(char charToSkip) { - skipSpaces(); - if (*_ptr != charToSkip) return false; - _ptr++; - skipSpaces(); + register const char *ptr = skipSpaces(_readPtr); + if (*ptr != charToSkip) return false; + ptr++; + _readPtr = skipSpaces(ptr); return true; } bool JsonParser::skip(const char *wordToSkip) { - const char *charToSkip = wordToSkip; - while (*charToSkip && *_ptr == *charToSkip) { - charToSkip++; - _ptr++; + register const char *ptr = _readPtr; + while (*wordToSkip && *ptr == *wordToSkip) { + wordToSkip++; + ptr++; } - return *charToSkip == '\0'; + if (*wordToSkip != '\0') return false; + _readPtr = ptr; + return true; } bool JsonParser::parseAnythingTo(JsonVariant *destination) { @@ -47,9 +50,9 @@ bool JsonParser::parseAnythingTo(JsonVariant *destination) { } inline bool JsonParser::parseAnythingToUnsafe(JsonVariant *destination) { - skipSpaces(); + _readPtr = skipSpaces(_readPtr); - switch (*_ptr) { + switch (*_readPtr) { case '[': return parseArrayTo(destination); @@ -181,7 +184,7 @@ bool JsonParser::parseBooleanTo(JsonVariant *destination) { bool JsonParser::parseNumberTo(JsonVariant *destination) { char *endOfLong; - long longValue = strtol(_ptr, &endOfLong, 10); + long longValue = strtol(_readPtr, &endOfLong, 10); char stopChar = *endOfLong; // Could it be a floating point value? @@ -189,14 +192,14 @@ bool JsonParser::parseNumberTo(JsonVariant *destination) { if (couldBeFloat) { // Yes => parse it as a double - double doubleValue = strtod(_ptr, &_ptr); + double doubleValue = strtod(_readPtr, const_cast(&_readPtr)); // Count the decimal digits - uint8_t decimals = static_cast(_ptr - endOfLong - 1); + uint8_t decimals = static_cast(_readPtr - endOfLong - 1); // Set the variant as a double *destination = JsonVariant(doubleValue, decimals); } else { // No => set the variant as a long - _ptr = endOfLong; + _readPtr = endOfLong; *destination = longValue; } return true; @@ -209,8 +212,53 @@ bool JsonParser::parseNullTo(JsonVariant *destination) { return true; } +static bool isStopChar(char c) { + return c == '\0' || c == ':' || c == '}' || c == ']' || c == ','; +} + const char *JsonParser::parseString() { - return QuotedString::extractFrom(_ptr, &_ptr); + const char *readPtr = _readPtr; + char *writePtr = _writePtr; + + char c = *readPtr; + + if (c == '\'' || c == '\"') { + char stopChar = c; + for (;;) { + c = *++readPtr; + if (c == '\0') break; + + if (c == stopChar) { + readPtr++; + break; + } + + if (c == '\\') { + // replace char + c = Encoding::unescapeChar(*++readPtr); + if (c == '\0') break; + } + + *writePtr++ = c; + } + } else { + for (;;) { + if (isStopChar(c)) break; + *writePtr++ = c; + c = *++readPtr; + } + } + // end the string here + *writePtr++ = '\0'; + + const char *startPtr = _writePtr; + + // update end ptr + _readPtr = readPtr; + _writePtr = writePtr; + + // return pointer to unquoted string + return startPtr; } bool JsonParser::parseStringTo(JsonVariant *destination) { diff --git a/src/Internals/QuotedString.cpp b/src/Internals/QuotedString.cpp deleted file mode 100644 index 3a90defc0..000000000 --- a/src/Internals/QuotedString.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright Benoit Blanchon 2014-2015 -// MIT License -// -// Arduino JSON library -// https://github.com/bblanchon/ArduinoJson - -#include "../../include/ArduinoJson/Internals/QuotedString.hpp" - -using namespace ArduinoJson::Internals; - -// How to escape special chars: -// specialChars[2*i+1] => the special char -// specialChars[2*i] => the char to use instead -static const char specialChars[] = "\"\"\\\\b\bf\fn\nr\rt\t"; - -static inline char getSpecialChar(char c) { - // Optimized for code size on a 8-bit AVR - - const char *p = specialChars; - - while (p[0] && p[1] != c) { - p += 2; - } - - return p[0]; -} - -static inline size_t printCharTo(char c, Print &p) { - char specialChar = getSpecialChar(c); - - return specialChar ? p.write('\\') + p.write(specialChar) : p.write(c); -} - -size_t QuotedString::printTo(const char *s, Print &p) { - if (!s) return p.print("null"); - - size_t n = p.write('\"'); - - while (*s) { - n += printCharTo(*s++, p); - } - - return n + p.write('\"'); -} - -static char unescapeChar(char c) { - // Optimized for code size on a 8-bit AVR - - const char *p = specialChars + 4; - - for (;;) { - if (p[0] == '\0') return c; - if (p[0] == c) return p[1]; - p += 2; - } -} - -static inline bool isQuote(char c) { return c == '\"' || c == '\''; } - -char *QuotedString::extractFrom(char *input, char **endPtr) { - char firstChar = *input; - - if (!isQuote(firstChar)) { - // must start with a quote - return NULL; - } - - char stopChar = firstChar; // closing quote is the same as opening quote - - char *startPtr = input + 1; // skip the quote - char *readPtr = startPtr; - char *writePtr = startPtr; - char c; - - for (;;) { - c = *readPtr++; - - if (c == '\0') { - // premature ending - return NULL; - } - - if (c == stopChar) { - // closing quote - break; - } - - if (c == '\\') { - // replace char - c = unescapeChar(*readPtr++); - } - - *writePtr++ = c; - } - - // end the string here - *writePtr = '\0'; - - // update end ptr - *endPtr = readPtr; - - return startPtr; -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0342d746a..c66a796e5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,4 +18,4 @@ add_executable(ArduinoJsonTests target_link_libraries(ArduinoJsonTests ArduinoJson) -add_test(ArduinoJsonTests ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ArduinoJsonTests) \ No newline at end of file +add_test(ArduinoJsonTests ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ArduinoJsonTests) diff --git a/test/GbathreeBug.cpp b/test/GbathreeBug.cpp index 2cd6e8861..96885d714 100644 --- a/test/GbathreeBug.cpp +++ b/test/GbathreeBug.cpp @@ -5,8 +5,8 @@ // https://github.com/bblanchon/ArduinoJson #include +#define ARDUINOJSON_ENABLE_STD_STREAM #include -#include "Printers.hpp" class GbathreeBug : public testing::Test { public: diff --git a/test/JsonObject_Iterator_Tests.cpp b/test/JsonObject_Iterator_Tests.cpp index d68af9bcf..0fee51898 100644 --- a/test/JsonObject_Iterator_Tests.cpp +++ b/test/JsonObject_Iterator_Tests.cpp @@ -5,8 +5,8 @@ // https://github.com/bblanchon/ArduinoJson #include +#define ARDUINOJSON_ENABLE_STD_STREAM #include -#include "Printers.hpp" class JsonObject_Iterator_Test : public testing::Test { public: diff --git a/test/JsonParser_Array_Tests.cpp b/test/JsonParser_Array_Tests.cpp index 2d6fbe3bd..f392516f8 100644 --- a/test/JsonParser_Array_Tests.cpp +++ b/test/JsonParser_Array_Tests.cpp @@ -9,14 +9,19 @@ class JsonParser_Array_Tests : public testing::Test { protected: - void whenInputIs(const char *json) { - strcpy(_jsonString, json); - _array = &_jsonBuffer.parseArray(_jsonString); + void whenInputIs(const char *json) { strcpy(_jsonString, json); } + + void whenInputIs(const char *json, size_t len) { + memcpy(_jsonString, json, len); } - void parseMustSucceed() { EXPECT_TRUE(_array->success()); } + void parseMustSucceed() { + _array = &_jsonBuffer.parseArray(_jsonString); + EXPECT_TRUE(_array->success()); + } void parseMustFail() { + _array = &_jsonBuffer.parseArray(_jsonString); EXPECT_FALSE(_array->success()); EXPECT_EQ(0, _array->size()); } @@ -154,6 +159,11 @@ TEST_F(JsonParser_Array_Tests, IncompleteFalse) { parseMustFail(); } +TEST_F(JsonParser_Array_Tests, MixedTrueFalse) { + whenInputIs("[trufalse]"); + parseMustFail(); +} + TEST_F(JsonParser_Array_Tests, TwoStrings) { whenInputIs("[\"hello\",\"world\"]"); @@ -162,3 +172,55 @@ TEST_F(JsonParser_Array_Tests, TwoStrings) { firstElementMustBe("hello"); secondElementMustBe("world"); } + +TEST_F(JsonParser_Array_Tests, EmptyStringsDoubleQuotes) { + whenInputIs("[\"\",\"\"]"); + + parseMustSucceed(); + sizeMustBe(2); + firstElementMustBe(""); + secondElementMustBe(""); +} + +TEST_F(JsonParser_Array_Tests, EmptyStringSingleQuotes) { + whenInputIs("[\'\',\'\']"); + + parseMustSucceed(); + sizeMustBe(2); + firstElementMustBe(""); + secondElementMustBe(""); +} + +TEST_F(JsonParser_Array_Tests, EmptyStringNoQuotes) { + whenInputIs("[,]"); + + parseMustSucceed(); + sizeMustBe(2); + firstElementMustBe(""); + secondElementMustBe(""); +} + +TEST_F(JsonParser_Array_Tests, ClosingDoubleQuoteMissing) { + whenInputIs("[\"]"); + + parseMustFail(); +} + +TEST_F(JsonParser_Array_Tests, ClosingSignleQuoteMissing) { + whenInputIs("[\']"); + + parseMustFail(); +} + +TEST_F(JsonParser_Array_Tests, StringWithEscapedChars) { + whenInputIs("[\"1\\\"2\\\\3\\/4\\b5\\f6\\n7\\r8\\t9\"]"); + + parseMustSucceed(); + sizeMustBe(1); + firstElementMustBe("1\"2\\3/4\b5\f6\n7\r8\t9"); +} + +TEST_F(JsonParser_Array_Tests, StringWithUnterminatedEscapeSequence) { + whenInputIs("\"\\\0\"", 4); + parseMustFail(); +} diff --git a/test/JsonParser_Object_Tests.cpp b/test/JsonParser_Object_Tests.cpp index b868c68e2..eb0e0e40d 100644 --- a/test/JsonParser_Object_Tests.cpp +++ b/test/JsonParser_Object_Tests.cpp @@ -75,6 +75,13 @@ TEST_F(JsonParser_Object_Test, OneStringSingleQuotes) { keyMustHaveValue("key", "value"); } +TEST_F(JsonParser_Object_Test, OneStringNoQuotes) { + whenInputIs("{key:value}"); + parseMustSucceed(); + sizeMustBe(1); + keyMustHaveValue("key", "value"); +} + TEST_F(JsonParser_Object_Test, OneStringSpaceBeforeKey) { whenInputIs("{ \"key\":\"value\"}"); parseMustSucceed(); diff --git a/test/JsonVariant_Comparison_Tests.cpp b/test/JsonVariant_Comparison_Tests.cpp index 35687d85e..f155e733d 100644 --- a/test/JsonVariant_Comparison_Tests.cpp +++ b/test/JsonVariant_Comparison_Tests.cpp @@ -5,8 +5,8 @@ // https://github.com/bblanchon/ArduinoJson #include +#define ARDUINOJSON_ENABLE_STD_STREAM #include -#include "Printers.hpp" using namespace ArduinoJson; diff --git a/test/JsonVariant_Undefined_Tests.cpp b/test/JsonVariant_Undefined_Tests.cpp index 98ef914a4..fd072960d 100644 --- a/test/JsonVariant_Undefined_Tests.cpp +++ b/test/JsonVariant_Undefined_Tests.cpp @@ -5,8 +5,8 @@ // https://github.com/bblanchon/ArduinoJson #include +#define ARDUINOJSON_ENABLE_STD_STREAM #include -#include "Printers.hpp" class JsonVariant_Undefined_Tests : public ::testing::Test { protected: diff --git a/test/QuotedString_PrintTo_Tests.cpp b/test/JsonWriter_WriteString_Tests.cpp similarity index 59% rename from test/QuotedString_PrintTo_Tests.cpp rename to test/JsonWriter_WriteString_Tests.cpp index 4ee03474e..b7c8af4ba 100644 --- a/test/QuotedString_PrintTo_Tests.cpp +++ b/test/JsonWriter_WriteString_Tests.cpp @@ -6,16 +6,18 @@ #include -#include +#include #include using namespace ArduinoJson::Internals; -class QuotedString_PrintTo_Tests : public testing::Test { +class JsonWriter_WriteString_Tests : public testing::Test { protected: void whenInputIs(const char *input) { StringBuilder sb(buffer, sizeof(buffer)); - returnValue = QuotedString::printTo(input, sb); + JsonWriter writer(sb); + writer.writeString(input); + returnValue = writer.bytesWritten(); } void outputMustBe(const char *expected) { @@ -28,52 +30,52 @@ class QuotedString_PrintTo_Tests : public testing::Test { size_t returnValue; }; -TEST_F(QuotedString_PrintTo_Tests, Null) { +TEST_F(JsonWriter_WriteString_Tests, Null) { whenInputIs(0); outputMustBe("null"); } -TEST_F(QuotedString_PrintTo_Tests, EmptyString) { +TEST_F(JsonWriter_WriteString_Tests, EmptyString) { whenInputIs(""); outputMustBe("\"\""); } -TEST_F(QuotedString_PrintTo_Tests, QuotationMark) { +TEST_F(JsonWriter_WriteString_Tests, QuotationMark) { whenInputIs("\""); outputMustBe("\"\\\"\""); } -TEST_F(QuotedString_PrintTo_Tests, ReverseSolidus) { +TEST_F(JsonWriter_WriteString_Tests, ReverseSolidus) { whenInputIs("\\"); outputMustBe("\"\\\\\""); } -TEST_F(QuotedString_PrintTo_Tests, Solidus) { +TEST_F(JsonWriter_WriteString_Tests, Solidus) { whenInputIs("/"); outputMustBe("\"/\""); // but the JSON format allows \/ } -TEST_F(QuotedString_PrintTo_Tests, Backspace) { +TEST_F(JsonWriter_WriteString_Tests, Backspace) { whenInputIs("\b"); outputMustBe("\"\\b\""); } -TEST_F(QuotedString_PrintTo_Tests, Formfeed) { +TEST_F(JsonWriter_WriteString_Tests, Formfeed) { whenInputIs("\f"); outputMustBe("\"\\f\""); } -TEST_F(QuotedString_PrintTo_Tests, Newline) { +TEST_F(JsonWriter_WriteString_Tests, Newline) { whenInputIs("\n"); outputMustBe("\"\\n\""); } -TEST_F(QuotedString_PrintTo_Tests, CarriageReturn) { +TEST_F(JsonWriter_WriteString_Tests, CarriageReturn) { whenInputIs("\r"); outputMustBe("\"\\r\""); } -TEST_F(QuotedString_PrintTo_Tests, HorizontalTab) { +TEST_F(JsonWriter_WriteString_Tests, HorizontalTab) { whenInputIs("\t"); outputMustBe("\"\\t\""); } diff --git a/test/Printers.cpp b/test/Printers.cpp deleted file mode 100644 index 8b7e7451f..000000000 --- a/test/Printers.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright Benoit Blanchon 2014-2015 -// MIT License -// -// Arduino JSON library -// https://github.com/bblanchon/ArduinoJson - -#include "Printers.hpp" -#include - -class StreamPrintAdapter : public Print { - public: - explicit StreamPrintAdapter(std::ostream& os) : _os(os) {} - - virtual size_t write(uint8_t c) { - _os << static_cast(c); - return 1; - } - - private: - // cannot be assigned - StreamPrintAdapter& operator=(const StreamPrintAdapter&); - - std::ostream& _os; -}; - -std::ostream& ArduinoJson::operator<<(std::ostream& os, - const ArduinoJson::JsonVariant& v) { - StreamPrintAdapter adapter(os); - v.printTo(adapter); - return os; -} - -std::ostream& ArduinoJson::operator<<(std::ostream& os, - const ArduinoJson::JsonArray& v) { - StreamPrintAdapter adapter(os); - v.printTo(adapter); - return os; -} - -std::ostream& ArduinoJson::operator<<( - std::ostream& os, const ArduinoJson::JsonObjectSubscript& v) { - JsonVariant value = v; - return os << value; -} - -std::ostream& ArduinoJson::operator<<( - std::ostream& os, const ArduinoJson::JsonArraySubscript& v) { - JsonVariant value = v; - return os << value; -} diff --git a/test/Printers.hpp b/test/Printers.hpp deleted file mode 100644 index 5abba06aa..000000000 --- a/test/Printers.hpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Benoit Blanchon 2014-2015 -// MIT License -// -// Arduino JSON library -// https://github.com/bblanchon/ArduinoJson - -#pragma once - -#include -#include - -namespace ArduinoJson { -std::ostream& operator<<(std::ostream& os, const JsonVariant& v); -std::ostream& operator<<(std::ostream& os, const JsonArray& v); -std::ostream& operator<<(std::ostream& os, const JsonObjectSubscript& v); -std::ostream& operator<<(std::ostream& os, const JsonArraySubscript& v); -} diff --git a/test/QuotedString_ExtractFrom_Tests.cpp b/test/QuotedString_ExtractFrom_Tests.cpp deleted file mode 100644 index 2f6747f3d..000000000 --- a/test/QuotedString_ExtractFrom_Tests.cpp +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright Benoit Blanchon 2014-2015 -// MIT License -// -// Arduino JSON library -// https://github.com/bblanchon/ArduinoJson - -#include -#include - -using namespace ArduinoJson::Internals; - -class QuotedString_ExtractFrom_Tests : public testing::Test { - protected: - void whenInputIs(const char *json) { - strcpy(_jsonString, json); - _result = QuotedString::extractFrom(_jsonString, &_trailing); - } - - void resultMustBe(const char *expected) { EXPECT_STREQ(expected, _result); } - - void trailingMustBe(const char *expected) { - EXPECT_STREQ(expected, _trailing); - } - - private: - char _jsonString[256]; - char *_result; - char *_trailing; -}; - -TEST_F(QuotedString_ExtractFrom_Tests, EmptyDoubleQuotedString) { - whenInputIs("\"\""); - - resultMustBe(""); - trailingMustBe(""); -} - -TEST_F(QuotedString_ExtractFrom_Tests, NoQuotes) { - whenInputIs("hello world"); - - resultMustBe(0); -} - -TEST_F(QuotedString_ExtractFrom_Tests, MissingClosingQuote) { - whenInputIs("\"hello world"); - - resultMustBe(0); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EmptySingleQuotedString) { - whenInputIs("''"); - - resultMustBe(""); - trailingMustBe(""); -} - -TEST_F(QuotedString_ExtractFrom_Tests, SimpleDoubleQuotedString) { - whenInputIs("\"hello world\""); - - resultMustBe("hello world"); - trailingMustBe(""); -} - -TEST_F(QuotedString_ExtractFrom_Tests, DoubleQuotedStringWithTrailing) { - whenInputIs("\"hello\" world"); - - resultMustBe("hello"); - trailingMustBe(" world"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, SingleQuotedStringWithTrailing) { - whenInputIs("'hello' world"); - - resultMustBe("hello"); - trailingMustBe(" world"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, CurlyBraces) { - whenInputIs("\"{hello:world}\""); - resultMustBe("{hello:world}"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, SquareBraquets) { - whenInputIs("\"[hello,world]\""); - resultMustBe("[hello,world]"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EscapedDoubleQuote) { - whenInputIs("\"hello \\\"world\\\"\""); - resultMustBe("hello \"world\""); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EscapedSingleQuote) { - whenInputIs("\"hello \\\'world\\\'\""); - resultMustBe("hello 'world'"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EscapedSolidus) { - whenInputIs("\"hello \\/world\\/\""); - resultMustBe("hello /world/"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EscapedReverseSolidus) { - whenInputIs("\"hello \\\\world\\\\\""); - resultMustBe("hello \\world\\"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EscapedBackspace) { - whenInputIs("\"hello \\bworld\\b\""); - resultMustBe("hello \bworld\b"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EscapedFormfeed) { - whenInputIs("\"hello \\fworld\\f\""); - resultMustBe("hello \fworld\f"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EscapedNewline) { - whenInputIs("\"hello \\nworld\\n\""); - resultMustBe("hello \nworld\n"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EscapedCarriageReturn) { - whenInputIs("\"hello \\rworld\\r\""); - resultMustBe("hello \rworld\r"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, EscapedTab) { - whenInputIs("\"hello \\tworld\\t\""); - resultMustBe("hello \tworld\t"); -} - -TEST_F(QuotedString_ExtractFrom_Tests, AllEscapedCharsTogether) { - whenInputIs("\"1\\\"2\\\\3\\/4\\b5\\f6\\n7\\r8\\t9\""); - resultMustBe("1\"2\\3/4\b5\f6\n7\r8\t9"); -} diff --git a/test/StdStream.cpp b/test/StdStream.cpp new file mode 100644 index 000000000..8fddc8ee5 --- /dev/null +++ b/test/StdStream.cpp @@ -0,0 +1,62 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#include +#include +#define ARDUINOJSON_ENABLE_STD_STREAM +#include + +TEST(StdStream, JsonVariantFalse) { + std::ostringstream os; + JsonVariant variant = false; + os << variant; + ASSERT_EQ("false", os.str()); +} + +TEST(StdStream, JsonVariantString) { + std::ostringstream os; + JsonVariant variant = "coucou"; + os << variant; + ASSERT_EQ("\"coucou\"", os.str()); +} + +TEST(StdStream, JsonObject) { + std::ostringstream os; + DynamicJsonBuffer jsonBuffer; + JsonObject& object = jsonBuffer.createObject(); + object["key"] = "value"; + os << object; + ASSERT_EQ("{\"key\":\"value\"}", os.str()); +} + +TEST(StdStream, JsonObjectSubscript) { + std::ostringstream os; + DynamicJsonBuffer jsonBuffer; + JsonObject& object = jsonBuffer.createObject(); + object["key"] = "value"; + os << object["key"]; + ASSERT_EQ("\"value\"", os.str()); +} + +TEST(StdStream, JsonArray) { + std::ostringstream os; + DynamicJsonBuffer jsonBuffer; + JsonArray& array = jsonBuffer.createArray(); + array.add("value"); + os << array; + ASSERT_EQ("[\"value\"]", os.str()); +} + +TEST(StdStream, JsonArraySubscript) { + std::ostringstream os; + DynamicJsonBuffer jsonBuffer; + JsonArray& array = jsonBuffer.createArray(); + array.add("value"); + os << array[0]; + ASSERT_EQ("\"value\"", os.str()); +} + +