Przeglądaj źródła

Added support of non standard JSON input (issue #44)

Benoit Blanchon 10 lat temu
rodzic
commit
92e687303d

+ 1 - 0
.gitignore

@@ -3,3 +3,4 @@
 /build
 /bin
 /lib
+/sftp-config.json

+ 2 - 4
.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'
+    - if [ "$CC" = "gcc" ]; then coveralls --exclude third-party --gcov-options '\-lp'; fi

+ 10 - 0
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
 ----
 

+ 2 - 3
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)
+add_subdirectory(test)

+ 39 - 0
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[];
+};
+}
+}

+ 7 - 5
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;
 };
 }

+ 20 - 0
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<const T *>(this); }
 };
+
+#ifdef ARDUINOJSON_ENABLE_STD_STREAM
+template<typename T>
+inline std::ostream& operator<<(std::ostream& os, const JsonPrintable<T>& v) {
+  return v.printTo(os);
+}
+#endif
+
 }
 }

+ 23 - 6
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);
   }

+ 0 - 29
include/ArduinoJson/Internals/QuotedString.hpp

@@ -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);
-};
-}
-}

+ 34 - 0
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<char>(c);
+    return 1;
+  }
+
+ private:
+  // cannot be assigned
+  StreamPrintAdapter& operator=(const StreamPrintAdapter&);
+
+  std::ostream& _os;
+};
+}
+}
+
+#endif // ARDUINOJSON_ENABLE_STD_STREAM

+ 11 - 0
include/ArduinoJson/JsonArraySubscript.hpp

@@ -40,8 +40,19 @@ class JsonArraySubscript : public JsonVariantBase<JsonArraySubscript> {
     return _array.is<T>(_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

+ 10 - 0
include/ArduinoJson/JsonObjectSubscript.hpp

@@ -41,8 +41,18 @@ class JsonObjectSubscript : public JsonVariantBase<JsonObjectSubscript> {
     return _object.is<T>(_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
 }

+ 1 - 2
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<JsonVariant>,
-                    public JsonVariantBase<JsonVariant> {
+class JsonVariant : public JsonVariantBase<JsonVariant> {
  public:
   // Creates an uninitialized JsonVariant
   FORCE_INLINE JsonVariant() : _type(Internals::JSON_UNDEFINED) {}

+ 7 - 0
include/ArduinoJson/JsonVariant.ipp

@@ -186,4 +186,11 @@ template <>
 inline bool JsonVariant::is<unsigned short>() 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

+ 4 - 1
include/ArduinoJson/JsonVariantBase.hpp

@@ -16,7 +16,7 @@ class JsonArraySubscript;
 class JsonObjectSubscript;
 
 template <typename TImpl>
-class JsonVariantBase {
+class JsonVariantBase : public Internals::JsonPrintable<TImpl> {
  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<const TImpl *>(this); }
 };

+ 12 - 0
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";

+ 67 - 19
src/Internals/JsonParser.cpp

@@ -9,7 +9,7 @@
 #include <stdlib.h>  // for strtol, strtod
 #include <ctype.h>
 
-#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<char **>(&_readPtr));
     // Count the decimal digits
-    uint8_t decimals = static_cast<uint8_t>(_ptr - endOfLong - 1);
+    uint8_t decimals = static_cast<uint8_t>(_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) {

+ 0 - 103
src/Internals/QuotedString.cpp

@@ -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;
-}

+ 1 - 1
test/CMakeLists.txt

@@ -18,4 +18,4 @@ add_executable(ArduinoJsonTests
 
 target_link_libraries(ArduinoJsonTests ArduinoJson)
 
-add_test(ArduinoJsonTests ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ArduinoJsonTests)
+add_test(ArduinoJsonTests ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ArduinoJsonTests)

+ 1 - 1
test/GbathreeBug.cpp

@@ -5,8 +5,8 @@
 // https://github.com/bblanchon/ArduinoJson
 
 #include <gtest/gtest.h>
+#define ARDUINOJSON_ENABLE_STD_STREAM
 #include <ArduinoJson.h>
-#include "Printers.hpp"
 
 class GbathreeBug : public testing::Test {
  public:

+ 1 - 1
test/JsonObject_Iterator_Tests.cpp

@@ -5,8 +5,8 @@
 // https://github.com/bblanchon/ArduinoJson
 
 #include <gtest/gtest.h>
+#define ARDUINOJSON_ENABLE_STD_STREAM
 #include <ArduinoJson.h>
-#include "Printers.hpp"
 
 class JsonObject_Iterator_Test : public testing::Test {
  public:

+ 66 - 4
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();
+}

+ 7 - 0
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();

+ 1 - 1
test/JsonVariant_Comparison_Tests.cpp

@@ -5,8 +5,8 @@
 // https://github.com/bblanchon/ArduinoJson
 
 #include <gtest/gtest.h>
+#define ARDUINOJSON_ENABLE_STD_STREAM
 #include <ArduinoJson/JsonVariant.hpp>
-#include "Printers.hpp"
 
 using namespace ArduinoJson;
 

+ 1 - 1
test/JsonVariant_Undefined_Tests.cpp

@@ -5,8 +5,8 @@
 // https://github.com/bblanchon/ArduinoJson
 
 #include <gtest/gtest.h>
+#define ARDUINOJSON_ENABLE_STD_STREAM
 #include <ArduinoJson.h>
-#include "Printers.hpp"
 
 class JsonVariant_Undefined_Tests : public ::testing::Test {
  protected:

+ 15 - 13
test/QuotedString_PrintTo_Tests.cpp → test/JsonWriter_WriteString_Tests.cpp

@@ -6,16 +6,18 @@
 
 #include <gtest/gtest.h>
 
-#include <ArduinoJson/Internals/QuotedString.hpp>
+#include <ArduinoJson/Internals/JsonWriter.hpp>
 #include <ArduinoJson/Internals/StringBuilder.hpp>
 
 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\"");
 }

+ 0 - 50
test/Printers.cpp

@@ -1,50 +0,0 @@
-// Copyright Benoit Blanchon 2014-2015
-// MIT License
-//
-// Arduino JSON library
-// https://github.com/bblanchon/ArduinoJson
-
-#include "Printers.hpp"
-#include <ArduinoJson/JsonArray.hpp>
-
-class StreamPrintAdapter : public Print {
- public:
-  explicit StreamPrintAdapter(std::ostream& os) : _os(os) {}
-
-  virtual size_t write(uint8_t c) {
-    _os << static_cast<char>(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;
-}

+ 0 - 17
test/Printers.hpp

@@ -1,17 +0,0 @@
-// Copyright Benoit Blanchon 2014-2015
-// MIT License
-//
-// Arduino JSON library
-// https://github.com/bblanchon/ArduinoJson
-
-#pragma once
-
-#include <ArduinoJson.h>
-#include <ostream>
-
-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);
-}

+ 0 - 136
test/QuotedString_ExtractFrom_Tests.cpp

@@ -1,136 +0,0 @@
-// Copyright Benoit Blanchon 2014-2015
-// MIT License
-//
-// Arduino JSON library
-// https://github.com/bblanchon/ArduinoJson
-
-#include <gtest/gtest.h>
-#include <ArduinoJson/Internals/QuotedString.hpp>
-
-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");
-}

+ 62 - 0
test/StdStream.cpp

@@ -0,0 +1,62 @@
+// Copyright Benoit Blanchon 2014-2015
+// MIT License
+//
+// Arduino JSON library
+// https://github.com/bblanchon/ArduinoJson
+
+#include <sstream>
+#include <gtest/gtest.h>
+#define ARDUINOJSON_ENABLE_STD_STREAM
+#include <ArduinoJson.h>
+
+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());
+}
+
+