diff --git a/doc/release-notes.md b/doc/release-notes.md index 4753be5672..304c93bce6 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,6 +1,7 @@ Bitcoin ABC version 0.21.5 is now available from: This release includes the following features and fixes: - Removed deprecated `getinfo` RPC. + - Update univalue to 1.0.5 diff --git a/src/univalue/Makefile.am b/src/univalue/Makefile.am index e283fc890e..0f5ba59954 100644 --- a/src/univalue/Makefile.am +++ b/src/univalue/Makefile.am @@ -1,116 +1,118 @@ ACLOCAL_AMFLAGS = -I build-aux/m4 .PHONY: gen .INTERMEDIATE: $(GENBIN) include_HEADERS = include/univalue.h noinst_HEADERS = lib/univalue_escapes.h lib/univalue_utffilter.h lib_LTLIBRARIES = libunivalue.la pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = pc/libunivalue.pc libunivalue_la_SOURCES = \ lib/univalue.cpp \ lib/univalue_get.cpp \ lib/univalue_read.cpp \ lib/univalue_write.cpp libunivalue_la_LDFLAGS = \ -version-info $(LIBUNIVALUE_CURRENT):$(LIBUNIVALUE_REVISION):$(LIBUNIVALUE_AGE) \ -no-undefined libunivalue_la_CXXFLAGS = -I$(top_srcdir)/include TESTS = test/object test/unitester test/no_nul GENBIN = gen/gen$(BUILD_EXEEXT) GEN_SRCS = gen/gen.cpp $(GENBIN): $(GEN_SRCS) @echo Building $@ $(AM_V_at)c++ -I$(top_srcdir)/include -o $@ $< gen: lib/univalue_escapes.h $(GENBIN) @echo Updating $< $(AM_V_at)$(GENBIN) > lib/univalue_escapes.h noinst_PROGRAMS = $(TESTS) test/test_json TEST_DATA_DIR=test test_unitester_SOURCES = test/unitester.cpp test_unitester_LDADD = libunivalue.la test_unitester_CXXFLAGS = -I$(top_srcdir)/include -DJSON_TEST_SRC=\"$(srcdir)/$(TEST_DATA_DIR)\" test_unitester_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) test_test_json_SOURCES = test/test_json.cpp test_test_json_LDADD = libunivalue.la test_test_json_CXXFLAGS = -I$(top_srcdir)/include test_test_json_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) test_no_nul_SOURCES = test/no_nul.cpp test_no_nul_LDADD = libunivalue.la test_no_nul_CXXFLAGS = -I$(top_srcdir)/include test_no_nul_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) test_object_SOURCES = test/object.cpp test_object_LDADD = libunivalue.la test_object_CXXFLAGS = -I$(top_srcdir)/include test_object_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) TEST_FILES = \ $(TEST_DATA_DIR)/fail10.json \ $(TEST_DATA_DIR)/fail11.json \ $(TEST_DATA_DIR)/fail12.json \ $(TEST_DATA_DIR)/fail13.json \ $(TEST_DATA_DIR)/fail14.json \ $(TEST_DATA_DIR)/fail15.json \ $(TEST_DATA_DIR)/fail16.json \ $(TEST_DATA_DIR)/fail17.json \ $(TEST_DATA_DIR)/fail18.json \ $(TEST_DATA_DIR)/fail19.json \ $(TEST_DATA_DIR)/fail1.json \ $(TEST_DATA_DIR)/fail20.json \ $(TEST_DATA_DIR)/fail21.json \ $(TEST_DATA_DIR)/fail22.json \ $(TEST_DATA_DIR)/fail23.json \ $(TEST_DATA_DIR)/fail24.json \ $(TEST_DATA_DIR)/fail25.json \ $(TEST_DATA_DIR)/fail26.json \ $(TEST_DATA_DIR)/fail27.json \ $(TEST_DATA_DIR)/fail28.json \ $(TEST_DATA_DIR)/fail29.json \ $(TEST_DATA_DIR)/fail2.json \ $(TEST_DATA_DIR)/fail30.json \ $(TEST_DATA_DIR)/fail31.json \ $(TEST_DATA_DIR)/fail32.json \ $(TEST_DATA_DIR)/fail33.json \ $(TEST_DATA_DIR)/fail34.json \ $(TEST_DATA_DIR)/fail35.json \ $(TEST_DATA_DIR)/fail36.json \ $(TEST_DATA_DIR)/fail37.json \ $(TEST_DATA_DIR)/fail38.json \ $(TEST_DATA_DIR)/fail39.json \ $(TEST_DATA_DIR)/fail40.json \ $(TEST_DATA_DIR)/fail41.json \ $(TEST_DATA_DIR)/fail42.json \ $(TEST_DATA_DIR)/fail44.json \ + $(TEST_DATA_DIR)/fail45.json \ $(TEST_DATA_DIR)/fail3.json \ $(TEST_DATA_DIR)/fail4.json \ $(TEST_DATA_DIR)/fail5.json \ $(TEST_DATA_DIR)/fail6.json \ $(TEST_DATA_DIR)/fail7.json \ $(TEST_DATA_DIR)/fail8.json \ $(TEST_DATA_DIR)/fail9.json \ $(TEST_DATA_DIR)/pass1.json \ $(TEST_DATA_DIR)/pass2.json \ $(TEST_DATA_DIR)/pass3.json \ + $(TEST_DATA_DIR)/pass4.json \ $(TEST_DATA_DIR)/round1.json \ $(TEST_DATA_DIR)/round2.json \ $(TEST_DATA_DIR)/round3.json \ $(TEST_DATA_DIR)/round4.json \ $(TEST_DATA_DIR)/round5.json \ $(TEST_DATA_DIR)/round6.json \ $(TEST_DATA_DIR)/round7.json EXTRA_DIST=$(TEST_FILES) $(GEN_SRCS) diff --git a/src/univalue/README.md b/src/univalue/README.md index 36aa786a4c..46df8d316f 100644 --- a/src/univalue/README.md +++ b/src/univalue/README.md @@ -1,32 +1,33 @@ # UniValue ## Summary A universal value class, with JSON encoding and decoding. UniValue is an abstract data type that may be a null, boolean, string, number, array container, or a key/value dictionary container, nested to an arbitrary depth. This class is aligned with the JSON standard, [RFC 7159](https://tools.ietf.org/html/rfc7159.html). +## Motivation + +UniValue is a reaction to json_spirit, seeking to minimize template +and memory use, providing a straightforward RAII class compatible with +link-time optimization and embedded uses. + ## Installation This project is a standard GNU [autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html) project. Build and install instructions are available in the `INSTALL` file provided with GNU autotools. ``` $ ./autogen.sh $ ./configure $ make ``` -## Design - -UniValue provides a single dynamic RAII C++ object class, -and minimizes template use (contra json_spirit). - diff --git a/src/univalue/configure.ac b/src/univalue/configure.ac index f313128399..67d499e966 100644 --- a/src/univalue/configure.ac +++ b/src/univalue/configure.ac @@ -1,69 +1,69 @@ m4_define([libunivalue_major_version], [1]) m4_define([libunivalue_minor_version], [1]) m4_define([libunivalue_micro_version], [4]) m4_define([libunivalue_interface_age], [4]) # If you need a modifier for the version number. # Normally empty, but can be used to make "fixup" releases. m4_define([libunivalue_extraversion], []) dnl libtool versioning from libunivalue m4_define([libunivalue_current], [m4_eval(100 * libunivalue_minor_version + libunivalue_micro_version - libunivalue_interface_age)]) m4_define([libunivalue_binary_age], [m4_eval(100 * libunivalue_minor_version + libunivalue_micro_version)]) m4_define([libunivalue_revision], [libunivalue_interface_age]) m4_define([libunivalue_age], [m4_eval(libunivalue_binary_age - libunivalue_interface_age)]) m4_define([libunivalue_version], [libunivalue_major_version().libunivalue_minor_version().libunivalue_micro_version()libunivalue_extraversion()]) -AC_INIT([univalue], [1.0.4], +AC_INIT([univalue], [1.0.5], [http://github.com/jgarzik/univalue/]) dnl make the compilation flags quiet unless V=1 is used m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) AC_PREREQ(2.60) AC_CONFIG_SRCDIR([lib/univalue.cpp]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([build-aux/m4]) AC_CONFIG_HEADERS([univalue-config.h]) AM_INIT_AUTOMAKE([subdir-objects foreign]) LIBUNIVALUE_MAJOR_VERSION=libunivalue_major_version LIBUNIVALUE_MINOR_VERSION=libunivalue_minor_version LIBUNIVALUE_MICRO_VERSION=libunivalue_micro_version LIBUNIVALUE_INTERFACE_AGE=libunivalue_interface_age # ABI version # http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html LIBUNIVALUE_CURRENT=libunivalue_current LIBUNIVALUE_REVISION=libunivalue_revision LIBUNIVALUE_AGE=libunivalue_age AC_SUBST(LIBUNIVALUE_CURRENT) AC_SUBST(LIBUNIVALUE_REVISION) AC_SUBST(LIBUNIVALUE_AGE) LT_INIT LT_LANG([C++]) case $host in *mingw*) LIBTOOL_APP_LDFLAGS="$LIBTOOL_APP_LDFLAGS -all-static" ;; esac BUILD_EXEEXT= case $build in *mingw*) BUILD_EXEEXT=".exe" ;; esac AC_CONFIG_FILES([ Makefile pc/libunivalue.pc pc/libunivalue-uninstalled.pc]) AC_SUBST(LIBTOOL_APP_LDFLAGS) AC_SUBST(BUILD_EXEEXT) AC_OUTPUT diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h index ead8afb434..225533ce30 100644 --- a/src/univalue/include/univalue.h +++ b/src/univalue/include/univalue.h @@ -1,239 +1,306 @@ // Copyright 2014 BitPay Inc. // Copyright 2015 Bitcoin Core Developers // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. #ifndef __UNIVALUE_H__ #define __UNIVALUE_H__ #include #include #include #include #include #include -#include // .get_int64() +#include // std::pair class UniValue { public: enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, }; UniValue() { typ = VNULL; } UniValue(UniValue::VType initialType, const std::string& initialStr = "") { typ = initialType; val = initialStr; } UniValue(uint64_t val_) { setInt(val_); } UniValue(int64_t val_) { setInt(val_); } UniValue(bool val_) { setBool(val_); } UniValue(int val_) { setInt(val_); } UniValue(double val_) { setFloat(val_); } UniValue(const std::string& val_) { setStr(val_); } UniValue(const char *val_) { std::string s(val_); setStr(s); } ~UniValue() {} void clear(); bool setNull(); bool setBool(bool val); bool setNumStr(const std::string& val); bool setInt(uint64_t val); bool setInt(int64_t val); bool setInt(int val_) { return setInt((int64_t)val_); } bool setFloat(double val); bool setStr(const std::string& val); bool setArray(); bool setObject(); enum VType getType() const { return typ; } const std::string& getValStr() const { return val; } bool empty() const { return (values.size() == 0); } size_t size() const { return values.size(); } bool getBool() const { return isTrue(); } void getObjMap(std::map& kv) const; bool checkObject(const std::map& memberTypes) const; const UniValue& operator[](const std::string& key) const; const UniValue& operator[](size_t index) const; bool exists(const std::string& key) const { size_t i; return findKey(key, i); } bool isNull() const { return (typ == VNULL); } bool isTrue() const { return (typ == VBOOL) && (val == "1"); } bool isFalse() const { return (typ == VBOOL) && (val != "1"); } bool isBool() const { return (typ == VBOOL); } bool isStr() const { return (typ == VSTR); } bool isNum() const { return (typ == VNUM); } bool isArray() const { return (typ == VARR); } bool isObject() const { return (typ == VOBJ); } bool push_back(const UniValue& val); bool push_back(const std::string& val_) { UniValue tmpVal(VSTR, val_); return push_back(tmpVal); } bool push_back(const char *val_) { std::string s(val_); return push_back(s); } bool push_back(uint64_t val_) { UniValue tmpVal(val_); return push_back(tmpVal); } bool push_back(int64_t val_) { UniValue tmpVal(val_); return push_back(tmpVal); } bool push_back(int val_) { UniValue tmpVal(val_); return push_back(tmpVal); } bool push_back(double val_) { UniValue tmpVal(val_); return push_back(tmpVal); } bool push_backV(const std::vector& vec); void __pushKV(const std::string& key, const UniValue& val); bool pushKV(const std::string& key, const UniValue& val); bool pushKV(const std::string& key, const std::string& val_) { UniValue tmpVal(VSTR, val_); return pushKV(key, tmpVal); } bool pushKV(const std::string& key, const char *val_) { std::string _val(val_); return pushKV(key, _val); } bool pushKV(const std::string& key, int64_t val_) { UniValue tmpVal(val_); return pushKV(key, tmpVal); } bool pushKV(const std::string& key, uint64_t val_) { UniValue tmpVal(val_); return pushKV(key, tmpVal); } bool pushKV(const std::string& key, bool val_) { UniValue tmpVal((bool)val_); return pushKV(key, tmpVal); } bool pushKV(const std::string& key, int val_) { UniValue tmpVal((int64_t)val_); return pushKV(key, tmpVal); } bool pushKV(const std::string& key, double val_) { UniValue tmpVal(val_); return pushKV(key, tmpVal); } bool pushKVs(const UniValue& obj); std::string write(unsigned int prettyIndent = 0, unsigned int indentLevel = 0) const; bool read(const char *raw, size_t len); bool read(const char *raw) { return read(raw, strlen(raw)); } bool read(const std::string& rawStr) { return read(rawStr.data(), rawStr.size()); } private: UniValue::VType typ; std::string val; // numbers are stored as C++ strings std::vector keys; std::vector values; bool findKey(const std::string& key, size_t& retIdx) const; void writeArray(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; void writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; public: // Strict type-specific getters, these throw std::runtime_error if the // value is of unexpected type const std::vector& getKeys() const; const std::vector& getValues() const; bool get_bool() const; const std::string& get_str() const; int get_int() const; int64_t get_int64() const; double get_real() const; const UniValue& get_obj() const; const UniValue& get_array() const; enum VType type() const { return getType(); } + bool push_back(std::pair pear) { + return pushKV(pear.first, pear.second); + } friend const UniValue& find_value( const UniValue& obj, const std::string& name); }; +// +// The following were added for compatibility with json_spirit. +// Most duplicate other methods, and should be removed. +// +static inline std::pair Pair(const char *cKey, const char *cVal) +{ + std::string key(cKey); + UniValue uVal(cVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, std::string strVal) +{ + std::string key(cKey); + UniValue uVal(strVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, uint64_t u64Val) +{ + std::string key(cKey); + UniValue uVal(u64Val); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, int64_t i64Val) +{ + std::string key(cKey); + UniValue uVal(i64Val); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, bool iVal) +{ + std::string key(cKey); + UniValue uVal(iVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, int iVal) +{ + std::string key(cKey); + UniValue uVal(iVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, double dVal) +{ + std::string key(cKey); + UniValue uVal(dVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, const UniValue& uVal) +{ + std::string key(cKey); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(std::string key, const UniValue& uVal) +{ + return std::make_pair(key, uVal); +} + enum jtokentype { JTOK_ERR = -1, JTOK_NONE = 0, // eof JTOK_OBJ_OPEN, JTOK_OBJ_CLOSE, JTOK_ARR_OPEN, JTOK_ARR_CLOSE, JTOK_COLON, JTOK_COMMA, JTOK_KW_NULL, JTOK_KW_TRUE, JTOK_KW_FALSE, JTOK_NUMBER, JTOK_STRING, }; extern enum jtokentype getJsonToken(std::string& tokenVal, unsigned int& consumed, const char *raw, const char *end); extern const char *uvTypeName(UniValue::VType t); static inline bool jsonTokenIsValue(enum jtokentype jtt) { switch (jtt) { case JTOK_KW_NULL: case JTOK_KW_TRUE: case JTOK_KW_FALSE: case JTOK_NUMBER: case JTOK_STRING: return true; default: return false; } // not reached } static inline bool json_isspace(int ch) { switch (ch) { case 0x20: case 0x09: case 0x0a: case 0x0d: return true; default: return false; } // not reached } extern const UniValue NullUniValue; const UniValue& find_value( const UniValue& obj, const std::string& name); #endif // __UNIVALUE_H__ diff --git a/src/univalue/lib/univalue_get.cpp b/src/univalue/lib/univalue_get.cpp index 19ba5bbc3e..8b2da7555a 100644 --- a/src/univalue/lib/univalue_get.cpp +++ b/src/univalue/lib/univalue_get.cpp @@ -1,147 +1,148 @@ // Copyright 2014 BitPay Inc. // Copyright 2015 Bitcoin Core Developers // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include +#include #include "univalue.h" namespace { static bool ParsePrechecks(const std::string& str) { if (str.empty()) // No empty string allowed return false; if (str.size() >= 1 && (json_isspace(str[0]) || json_isspace(str[str.size()-1]))) // No padding allowed return false; if (str.size() != strlen(str.c_str())) // No embedded NUL characters allowed return false; return true; } bool ParseInt32(const std::string& str, int32_t *out) { if (!ParsePrechecks(str)) return false; char *endp = NULL; errno = 0; // strtol will not set errno if valid long int n = strtol(str.c_str(), &endp, 10); if(out) *out = (int32_t)n; // Note that strtol returns a *long int*, so even if strtol doesn't report a over/underflow // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit // platforms the size of these types may be different. return endp && *endp == 0 && !errno && n >= std::numeric_limits::min() && n <= std::numeric_limits::max(); } bool ParseInt64(const std::string& str, int64_t *out) { if (!ParsePrechecks(str)) return false; char *endp = NULL; errno = 0; // strtoll will not set errno if valid long long int n = strtoll(str.c_str(), &endp, 10); if(out) *out = (int64_t)n; // Note that strtoll returns a *long long int*, so even if strtol doesn't report a over/underflow // we still have to check that the returned value is within the range of an *int64_t*. return endp && *endp == 0 && !errno && n >= std::numeric_limits::min() && n <= std::numeric_limits::max(); } bool ParseDouble(const std::string& str, double *out) { if (!ParsePrechecks(str)) return false; if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') // No hexadecimal floats allowed return false; std::istringstream text(str); text.imbue(std::locale::classic()); double result; text >> result; if(out) *out = result; return text.eof() && !text.fail(); } } const std::vector& UniValue::getKeys() const { if (typ != VOBJ) throw std::runtime_error("JSON value is not an object as expected"); return keys; } const std::vector& UniValue::getValues() const { if (typ != VOBJ && typ != VARR) throw std::runtime_error("JSON value is not an object or array as expected"); return values; } bool UniValue::get_bool() const { if (typ != VBOOL) throw std::runtime_error("JSON value is not a boolean as expected"); return getBool(); } const std::string& UniValue::get_str() const { if (typ != VSTR) throw std::runtime_error("JSON value is not a string as expected"); return getValStr(); } int UniValue::get_int() const { if (typ != VNUM) throw std::runtime_error("JSON value is not an integer as expected"); int32_t retval; if (!ParseInt32(getValStr(), &retval)) throw std::runtime_error("JSON integer out of range"); return retval; } int64_t UniValue::get_int64() const { if (typ != VNUM) throw std::runtime_error("JSON value is not an integer as expected"); int64_t retval; if (!ParseInt64(getValStr(), &retval)) throw std::runtime_error("JSON integer out of range"); return retval; } double UniValue::get_real() const { if (typ != VNUM) throw std::runtime_error("JSON value is not a number as expected"); double retval; if (!ParseDouble(getValStr(), &retval)) throw std::runtime_error("JSON double out of range"); return retval; } const UniValue& UniValue::get_obj() const { if (typ != VOBJ) throw std::runtime_error("JSON value is not an object as expected"); return *this; } const UniValue& UniValue::get_array() const { if (typ != VARR) throw std::runtime_error("JSON value is not an array as expected"); return *this; } diff --git a/src/univalue/lib/univalue_read.cpp b/src/univalue/lib/univalue_read.cpp index c29821e597..7c17c4bec8 100644 --- a/src/univalue/lib/univalue_read.cpp +++ b/src/univalue/lib/univalue_read.cpp @@ -1,449 +1,464 @@ // Copyright 2014 BitPay Inc. // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. #include #include #include #include "univalue.h" #include "univalue_utffilter.h" +/* + * According to stackexchange, the original json test suite wanted + * to limit depth to 22. Widely-deployed PHP bails at depth 512, + * so we will follow PHP's lead, which should be more than sufficient + * (further stackexchange comments indicate depth > 32 rarely occurs). + */ +static const size_t MAX_JSON_DEPTH = 512; + static bool json_isdigit(int ch) { return ((ch >= '0') && (ch <= '9')); } // convert hexadecimal string to unsigned integer static const char *hatoui(const char *first, const char *last, unsigned int& out) { unsigned int result = 0; for (; first != last; ++first) { int digit; if (json_isdigit(*first)) digit = *first - '0'; else if (*first >= 'a' && *first <= 'f') digit = *first - 'a' + 10; else if (*first >= 'A' && *first <= 'F') digit = *first - 'A' + 10; else break; result = 16 * result + digit; } out = result; return first; } enum jtokentype getJsonToken(std::string& tokenVal, unsigned int& consumed, const char *raw, const char *end) { tokenVal.clear(); consumed = 0; const char *rawStart = raw; while (raw < end && (json_isspace(*raw))) // skip whitespace raw++; if (raw >= end) return JTOK_NONE; switch (*raw) { case '{': raw++; consumed = (raw - rawStart); return JTOK_OBJ_OPEN; case '}': raw++; consumed = (raw - rawStart); return JTOK_OBJ_CLOSE; case '[': raw++; consumed = (raw - rawStart); return JTOK_ARR_OPEN; case ']': raw++; consumed = (raw - rawStart); return JTOK_ARR_CLOSE; case ':': raw++; consumed = (raw - rawStart); return JTOK_COLON; case ',': raw++; consumed = (raw - rawStart); return JTOK_COMMA; case 'n': case 't': case 'f': if (!strncmp(raw, "null", 4)) { raw += 4; consumed = (raw - rawStart); return JTOK_KW_NULL; } else if (!strncmp(raw, "true", 4)) { raw += 4; consumed = (raw - rawStart); return JTOK_KW_TRUE; } else if (!strncmp(raw, "false", 5)) { raw += 5; consumed = (raw - rawStart); return JTOK_KW_FALSE; } else return JTOK_ERR; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { // part 1: int std::string numStr; const char *first = raw; const char *firstDigit = first; if (!json_isdigit(*firstDigit)) firstDigit++; if ((*firstDigit == '0') && json_isdigit(firstDigit[1])) return JTOK_ERR; numStr += *raw; // copy first char raw++; if ((*first == '-') && (raw < end) && (!json_isdigit(*raw))) return JTOK_ERR; while (raw < end && json_isdigit(*raw)) { // copy digits numStr += *raw; raw++; } // part 2: frac if (raw < end && *raw == '.') { numStr += *raw; // copy . raw++; if (raw >= end || !json_isdigit(*raw)) return JTOK_ERR; while (raw < end && json_isdigit(*raw)) { // copy digits numStr += *raw; raw++; } } // part 3: exp if (raw < end && (*raw == 'e' || *raw == 'E')) { numStr += *raw; // copy E raw++; if (raw < end && (*raw == '-' || *raw == '+')) { // copy +/- numStr += *raw; raw++; } if (raw >= end || !json_isdigit(*raw)) return JTOK_ERR; while (raw < end && json_isdigit(*raw)) { // copy digits numStr += *raw; raw++; } } tokenVal = numStr; consumed = (raw - rawStart); return JTOK_NUMBER; } case '"': { raw++; // skip " std::string valStr; JSONUTF8StringFilter writer(valStr); while (true) { if (raw >= end || (unsigned char)*raw < 0x20) return JTOK_ERR; else if (*raw == '\\') { raw++; // skip backslash if (raw >= end) return JTOK_ERR; switch (*raw) { case '"': writer.push_back('\"'); break; case '\\': writer.push_back('\\'); break; case '/': writer.push_back('/'); break; case 'b': writer.push_back('\b'); break; case 'f': writer.push_back('\f'); break; case 'n': writer.push_back('\n'); break; case 'r': writer.push_back('\r'); break; case 't': writer.push_back('\t'); break; case 'u': { unsigned int codepoint; if (raw + 1 + 4 >= end || hatoui(raw + 1, raw + 1 + 4, codepoint) != raw + 1 + 4) return JTOK_ERR; writer.push_back_u(codepoint); raw += 4; break; } default: return JTOK_ERR; } raw++; // skip esc'd char } else if (*raw == '"') { raw++; // skip " break; // stop scanning } else { writer.push_back(*raw); raw++; } } if (!writer.finalize()) return JTOK_ERR; tokenVal = valStr; consumed = (raw - rawStart); return JTOK_STRING; } default: return JTOK_ERR; } } enum expect_bits { EXP_OBJ_NAME = (1U << 0), EXP_COLON = (1U << 1), EXP_ARR_VALUE = (1U << 2), EXP_VALUE = (1U << 3), EXP_NOT_VALUE = (1U << 4), }; #define expect(bit) (expectMask & (EXP_##bit)) #define setExpect(bit) (expectMask |= EXP_##bit) #define clearExpect(bit) (expectMask &= ~EXP_##bit) bool UniValue::read(const char *raw, size_t size) { clear(); uint32_t expectMask = 0; std::vector stack; std::string tokenVal; unsigned int consumed; enum jtokentype tok = JTOK_NONE; enum jtokentype last_tok = JTOK_NONE; const char* end = raw + size; do { last_tok = tok; tok = getJsonToken(tokenVal, consumed, raw, end); if (tok == JTOK_NONE || tok == JTOK_ERR) - return false; + goto return_fail; raw += consumed; bool isValueOpen = jsonTokenIsValue(tok) || tok == JTOK_OBJ_OPEN || tok == JTOK_ARR_OPEN; if (expect(VALUE)) { if (!isValueOpen) - return false; + goto return_fail; clearExpect(VALUE); } else if (expect(ARR_VALUE)) { bool isArrValue = isValueOpen || (tok == JTOK_ARR_CLOSE); if (!isArrValue) - return false; + goto return_fail; clearExpect(ARR_VALUE); } else if (expect(OBJ_NAME)) { bool isObjName = (tok == JTOK_OBJ_CLOSE || tok == JTOK_STRING); if (!isObjName) - return false; + goto return_fail; } else if (expect(COLON)) { if (tok != JTOK_COLON) - return false; + goto return_fail; clearExpect(COLON); } else if (!expect(COLON) && (tok == JTOK_COLON)) { - return false; + goto return_fail; } if (expect(NOT_VALUE)) { if (isValueOpen) - return false; + goto return_fail; clearExpect(NOT_VALUE); } switch (tok) { case JTOK_OBJ_OPEN: case JTOK_ARR_OPEN: { VType utyp = (tok == JTOK_OBJ_OPEN ? VOBJ : VARR); if (!stack.size()) { if (utyp == VOBJ) setObject(); else setArray(); stack.push_back(this); } else { UniValue tmpVal(utyp); UniValue *top = stack.back(); top->values.push_back(tmpVal); UniValue *newTop = &(top->values.back()); stack.push_back(newTop); } + if (stack.size() > MAX_JSON_DEPTH) + goto return_fail; + if (utyp == VOBJ) setExpect(OBJ_NAME); else setExpect(ARR_VALUE); break; } case JTOK_OBJ_CLOSE: case JTOK_ARR_CLOSE: { if (!stack.size() || (last_tok == JTOK_COMMA)) - return false; + goto return_fail; VType utyp = (tok == JTOK_OBJ_CLOSE ? VOBJ : VARR); UniValue *top = stack.back(); if (utyp != top->getType()) - return false; + goto return_fail; stack.pop_back(); clearExpect(OBJ_NAME); setExpect(NOT_VALUE); break; } case JTOK_COLON: { if (!stack.size()) - return false; + goto return_fail; UniValue *top = stack.back(); if (top->getType() != VOBJ) - return false; + goto return_fail; setExpect(VALUE); break; } case JTOK_COMMA: { if (!stack.size() || (last_tok == JTOK_COMMA) || (last_tok == JTOK_ARR_OPEN)) - return false; + goto return_fail; UniValue *top = stack.back(); if (top->getType() == VOBJ) setExpect(OBJ_NAME); else setExpect(ARR_VALUE); break; } case JTOK_KW_NULL: case JTOK_KW_TRUE: case JTOK_KW_FALSE: { UniValue tmpVal; switch (tok) { case JTOK_KW_NULL: // do nothing more break; case JTOK_KW_TRUE: tmpVal.setBool(true); break; case JTOK_KW_FALSE: tmpVal.setBool(false); break; default: /* impossible */ break; } if (!stack.size()) { *this = tmpVal; break; } UniValue *top = stack.back(); top->values.push_back(tmpVal); setExpect(NOT_VALUE); break; } case JTOK_NUMBER: { UniValue tmpVal(VNUM, tokenVal); if (!stack.size()) { *this = tmpVal; break; } UniValue *top = stack.back(); top->values.push_back(tmpVal); setExpect(NOT_VALUE); break; } case JTOK_STRING: { if (expect(OBJ_NAME)) { UniValue *top = stack.back(); top->keys.push_back(tokenVal); clearExpect(OBJ_NAME); setExpect(COLON); } else { UniValue tmpVal(VSTR, tokenVal); if (!stack.size()) { *this = tmpVal; break; } UniValue *top = stack.back(); top->values.push_back(tmpVal); } setExpect(NOT_VALUE); break; } default: - return false; + goto return_fail; } } while (!stack.empty ()); /* Check that nothing follows the initial construct (parsed above). */ tok = getJsonToken(tokenVal, consumed, raw, end); if (tok != JTOK_NONE) - return false; + goto return_fail; return true; + +return_fail: + clear(); + return false; } diff --git a/src/univalue/lib/univalue_write.cpp b/src/univalue/lib/univalue_write.cpp index d31414edde..db039fcb00 100644 --- a/src/univalue/lib/univalue_write.cpp +++ b/src/univalue/lib/univalue_write.cpp @@ -1,113 +1,112 @@ // Copyright 2014 BitPay Inc. // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. #include -#include #include #include "univalue.h" #include "univalue_escapes.h" static std::string json_escape(const std::string& inS) { std::string outS; outS.reserve(inS.size() * 2); for (unsigned int i = 0; i < inS.size(); i++) { unsigned char ch = inS[i]; const char *escStr = escapes[ch]; if (escStr) outS += escStr; else outS += ch; } return outS; } std::string UniValue::write(unsigned int prettyIndent, unsigned int indentLevel) const { std::string s; s.reserve(1024); unsigned int modIndent = indentLevel; if (modIndent == 0) modIndent = 1; switch (typ) { case VNULL: s += "null"; break; case VOBJ: writeObject(prettyIndent, modIndent, s); break; case VARR: writeArray(prettyIndent, modIndent, s); break; case VSTR: s += "\"" + json_escape(val) + "\""; break; case VNUM: s += val; break; case VBOOL: s += (val == "1" ? "true" : "false"); break; } return s; } static void indentStr(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) { s.append(prettyIndent * indentLevel, ' '); } void UniValue::writeArray(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const { s += "["; if (prettyIndent) s += "\n"; for (unsigned int i = 0; i < values.size(); i++) { if (prettyIndent) indentStr(prettyIndent, indentLevel, s); s += values[i].write(prettyIndent, indentLevel + 1); if (i != (values.size() - 1)) { s += ","; } if (prettyIndent) s += "\n"; } if (prettyIndent) indentStr(prettyIndent, indentLevel - 1, s); s += "]"; } void UniValue::writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const { s += "{"; if (prettyIndent) s += "\n"; for (unsigned int i = 0; i < keys.size(); i++) { if (prettyIndent) indentStr(prettyIndent, indentLevel, s); s += "\"" + json_escape(keys[i]) + "\":"; if (prettyIndent) s += " "; s += values.at(i).write(prettyIndent, indentLevel + 1); if (i != (values.size() - 1)) s += ","; if (prettyIndent) s += "\n"; } if (prettyIndent) indentStr(prettyIndent, indentLevel - 1, s); s += "}"; } diff --git a/src/univalue/test/fail45.json b/src/univalue/test/fail45.json new file mode 100644 index 0000000000..03a30d8800 --- /dev/null +++ b/src/univalue/test/fail45.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] diff --git a/src/univalue/test/object.cpp b/src/univalue/test/object.cpp index 15b043d0eb..a168493907 100644 --- a/src/univalue/test/object.cpp +++ b/src/univalue/test/object.cpp @@ -1,407 +1,408 @@ // Copyright (c) 2014 BitPay Inc. // Copyright (c) 2014-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #define BOOST_FIXTURE_TEST_SUITE(a, b) #define BOOST_AUTO_TEST_CASE(funcName) void funcName() #define BOOST_AUTO_TEST_SUITE_END() #define BOOST_CHECK(expr) assert(expr) #define BOOST_CHECK_EQUAL(v1, v2) assert((v1) == (v2)) #define BOOST_CHECK_THROW(stmt, excMatch) { \ try { \ (stmt); \ + assert(0 && "No exception caught"); \ } catch (excMatch & e) { \ } catch (...) { \ - assert(0); \ + assert(0 && "Wrong exception caught"); \ } \ } #define BOOST_CHECK_NO_THROW(stmt) { \ try { \ (stmt); \ } catch (...) { \ assert(0); \ } \ } BOOST_FIXTURE_TEST_SUITE(univalue_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(univalue_constructor) { UniValue v1; BOOST_CHECK(v1.isNull()); UniValue v2(UniValue::VSTR); BOOST_CHECK(v2.isStr()); UniValue v3(UniValue::VSTR, "foo"); BOOST_CHECK(v3.isStr()); BOOST_CHECK_EQUAL(v3.getValStr(), "foo"); UniValue numTest; BOOST_CHECK(numTest.setNumStr("82")); BOOST_CHECK(numTest.isNum()); BOOST_CHECK_EQUAL(numTest.getValStr(), "82"); uint64_t vu64 = 82; UniValue v4(vu64); BOOST_CHECK(v4.isNum()); BOOST_CHECK_EQUAL(v4.getValStr(), "82"); int64_t vi64 = -82; UniValue v5(vi64); BOOST_CHECK(v5.isNum()); BOOST_CHECK_EQUAL(v5.getValStr(), "-82"); int vi = -688; UniValue v6(vi); BOOST_CHECK(v6.isNum()); BOOST_CHECK_EQUAL(v6.getValStr(), "-688"); double vd = -7.21; UniValue v7(vd); BOOST_CHECK(v7.isNum()); BOOST_CHECK_EQUAL(v7.getValStr(), "-7.21"); std::string vs("yawn"); UniValue v8(vs); BOOST_CHECK(v8.isStr()); BOOST_CHECK_EQUAL(v8.getValStr(), "yawn"); const char *vcs = "zappa"; UniValue v9(vcs); BOOST_CHECK(v9.isStr()); BOOST_CHECK_EQUAL(v9.getValStr(), "zappa"); } BOOST_AUTO_TEST_CASE(univalue_typecheck) { UniValue v1; BOOST_CHECK(v1.setNumStr("1")); BOOST_CHECK(v1.isNum()); BOOST_CHECK_THROW(v1.get_bool(), std::runtime_error); UniValue v2; BOOST_CHECK(v2.setBool(true)); BOOST_CHECK_EQUAL(v2.get_bool(), true); BOOST_CHECK_THROW(v2.get_int(), std::runtime_error); UniValue v3; BOOST_CHECK(v3.setNumStr("32482348723847471234")); BOOST_CHECK_THROW(v3.get_int64(), std::runtime_error); BOOST_CHECK(v3.setNumStr("1000")); BOOST_CHECK_EQUAL(v3.get_int64(), 1000); UniValue v4; BOOST_CHECK(v4.setNumStr("2147483648")); BOOST_CHECK_EQUAL(v4.get_int64(), 2147483648); BOOST_CHECK_THROW(v4.get_int(), std::runtime_error); BOOST_CHECK(v4.setNumStr("1000")); BOOST_CHECK_EQUAL(v4.get_int(), 1000); BOOST_CHECK_THROW(v4.get_str(), std::runtime_error); BOOST_CHECK_EQUAL(v4.get_real(), 1000); BOOST_CHECK_THROW(v4.get_array(), std::runtime_error); BOOST_CHECK_THROW(v4.getKeys(), std::runtime_error); BOOST_CHECK_THROW(v4.getValues(), std::runtime_error); BOOST_CHECK_THROW(v4.get_obj(), std::runtime_error); UniValue v5; BOOST_CHECK(v5.read("[true, 10]")); BOOST_CHECK_NO_THROW(v5.get_array()); std::vector vals = v5.getValues(); BOOST_CHECK_THROW(vals[0].get_int(), std::runtime_error); BOOST_CHECK_EQUAL(vals[0].get_bool(), true); BOOST_CHECK_EQUAL(vals[1].get_int(), 10); BOOST_CHECK_THROW(vals[1].get_bool(), std::runtime_error); } BOOST_AUTO_TEST_CASE(univalue_set) { UniValue v(UniValue::VSTR, "foo"); v.clear(); BOOST_CHECK(v.isNull()); BOOST_CHECK_EQUAL(v.getValStr(), ""); BOOST_CHECK(v.setObject()); BOOST_CHECK(v.isObject()); BOOST_CHECK_EQUAL(v.size(), 0); BOOST_CHECK_EQUAL(v.getType(), UniValue::VOBJ); BOOST_CHECK(v.empty()); BOOST_CHECK(v.setArray()); BOOST_CHECK(v.isArray()); BOOST_CHECK_EQUAL(v.size(), 0); BOOST_CHECK(v.setStr("zum")); BOOST_CHECK(v.isStr()); BOOST_CHECK_EQUAL(v.getValStr(), "zum"); BOOST_CHECK(v.setFloat(-1.01)); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "-1.01"); BOOST_CHECK(v.setInt((int)1023)); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "1023"); BOOST_CHECK(v.setInt((int64_t)-1023LL)); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "-1023"); BOOST_CHECK(v.setInt((uint64_t)1023ULL)); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "1023"); BOOST_CHECK(v.setNumStr("-688")); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "-688"); BOOST_CHECK(v.setBool(false)); BOOST_CHECK_EQUAL(v.isBool(), true); BOOST_CHECK_EQUAL(v.isTrue(), false); BOOST_CHECK_EQUAL(v.isFalse(), true); BOOST_CHECK_EQUAL(v.getBool(), false); BOOST_CHECK(v.setBool(true)); BOOST_CHECK_EQUAL(v.isBool(), true); BOOST_CHECK_EQUAL(v.isTrue(), true); BOOST_CHECK_EQUAL(v.isFalse(), false); BOOST_CHECK_EQUAL(v.getBool(), true); BOOST_CHECK(!v.setNumStr("zombocom")); BOOST_CHECK(v.setNull()); BOOST_CHECK(v.isNull()); } BOOST_AUTO_TEST_CASE(univalue_array) { UniValue arr(UniValue::VARR); UniValue v((int64_t)1023LL); BOOST_CHECK(arr.push_back(v)); std::string vStr("zippy"); BOOST_CHECK(arr.push_back(vStr)); const char *s = "pippy"; BOOST_CHECK(arr.push_back(s)); std::vector vec; v.setStr("boing"); vec.push_back(v); v.setStr("going"); vec.push_back(v); BOOST_CHECK(arr.push_backV(vec)); BOOST_CHECK(arr.push_back((uint64_t) 400ULL)); BOOST_CHECK(arr.push_back((int64_t) -400LL)); BOOST_CHECK(arr.push_back((int) -401)); BOOST_CHECK(arr.push_back(-40.1)); BOOST_CHECK_EQUAL(arr.empty(), false); BOOST_CHECK_EQUAL(arr.size(), 9); BOOST_CHECK_EQUAL(arr[0].getValStr(), "1023"); BOOST_CHECK_EQUAL(arr[1].getValStr(), "zippy"); BOOST_CHECK_EQUAL(arr[2].getValStr(), "pippy"); BOOST_CHECK_EQUAL(arr[3].getValStr(), "boing"); BOOST_CHECK_EQUAL(arr[4].getValStr(), "going"); BOOST_CHECK_EQUAL(arr[5].getValStr(), "400"); BOOST_CHECK_EQUAL(arr[6].getValStr(), "-400"); BOOST_CHECK_EQUAL(arr[7].getValStr(), "-401"); BOOST_CHECK_EQUAL(arr[8].getValStr(), "-40.1"); BOOST_CHECK_EQUAL(arr[999].getValStr(), ""); arr.clear(); BOOST_CHECK(arr.empty()); BOOST_CHECK_EQUAL(arr.size(), 0); } BOOST_AUTO_TEST_CASE(univalue_object) { UniValue obj(UniValue::VOBJ); std::string strKey, strVal; UniValue v; strKey = "age"; v.setInt(100); BOOST_CHECK(obj.pushKV(strKey, v)); strKey = "first"; strVal = "John"; BOOST_CHECK(obj.pushKV(strKey, strVal)); strKey = "last"; const char *cVal = "Smith"; BOOST_CHECK(obj.pushKV(strKey, cVal)); strKey = "distance"; BOOST_CHECK(obj.pushKV(strKey, (int64_t) 25)); strKey = "time"; BOOST_CHECK(obj.pushKV(strKey, (uint64_t) 3600)); strKey = "calories"; BOOST_CHECK(obj.pushKV(strKey, (int) 12)); strKey = "temperature"; BOOST_CHECK(obj.pushKV(strKey, (double) 90.012)); strKey = "moon"; BOOST_CHECK(obj.pushKV(strKey, true)); strKey = "spoon"; BOOST_CHECK(obj.pushKV(strKey, false)); UniValue obj2(UniValue::VOBJ); BOOST_CHECK(obj2.pushKV("cat1", 9000)); BOOST_CHECK(obj2.pushKV("cat2", 12345)); BOOST_CHECK(obj.pushKVs(obj2)); BOOST_CHECK_EQUAL(obj.empty(), false); BOOST_CHECK_EQUAL(obj.size(), 11); BOOST_CHECK_EQUAL(obj["age"].getValStr(), "100"); BOOST_CHECK_EQUAL(obj["first"].getValStr(), "John"); BOOST_CHECK_EQUAL(obj["last"].getValStr(), "Smith"); BOOST_CHECK_EQUAL(obj["distance"].getValStr(), "25"); BOOST_CHECK_EQUAL(obj["time"].getValStr(), "3600"); BOOST_CHECK_EQUAL(obj["calories"].getValStr(), "12"); BOOST_CHECK_EQUAL(obj["temperature"].getValStr(), "90.012"); BOOST_CHECK_EQUAL(obj["moon"].getValStr(), "1"); BOOST_CHECK_EQUAL(obj["spoon"].getValStr(), ""); BOOST_CHECK_EQUAL(obj["cat1"].getValStr(), "9000"); BOOST_CHECK_EQUAL(obj["cat2"].getValStr(), "12345"); BOOST_CHECK_EQUAL(obj["nyuknyuknyuk"].getValStr(), ""); BOOST_CHECK(obj.exists("age")); BOOST_CHECK(obj.exists("first")); BOOST_CHECK(obj.exists("last")); BOOST_CHECK(obj.exists("distance")); BOOST_CHECK(obj.exists("time")); BOOST_CHECK(obj.exists("calories")); BOOST_CHECK(obj.exists("temperature")); BOOST_CHECK(obj.exists("moon")); BOOST_CHECK(obj.exists("spoon")); BOOST_CHECK(obj.exists("cat1")); BOOST_CHECK(obj.exists("cat2")); BOOST_CHECK(!obj.exists("nyuknyuknyuk")); std::map objTypes; objTypes["age"] = UniValue::VNUM; objTypes["first"] = UniValue::VSTR; objTypes["last"] = UniValue::VSTR; objTypes["distance"] = UniValue::VNUM; objTypes["time"] = UniValue::VNUM; objTypes["calories"] = UniValue::VNUM; objTypes["temperature"] = UniValue::VNUM; objTypes["moon"] = UniValue::VBOOL; objTypes["spoon"] = UniValue::VBOOL; objTypes["cat1"] = UniValue::VNUM; objTypes["cat2"] = UniValue::VNUM; BOOST_CHECK(obj.checkObject(objTypes)); objTypes["cat2"] = UniValue::VSTR; BOOST_CHECK(!obj.checkObject(objTypes)); obj.clear(); BOOST_CHECK(obj.empty()); BOOST_CHECK_EQUAL(obj.size(), 0); BOOST_CHECK_EQUAL(obj.getType(), UniValue::VNULL); BOOST_CHECK_EQUAL(obj.setObject(), true); UniValue uv; uv.setInt(42); obj.__pushKV("age", uv); BOOST_CHECK_EQUAL(obj.size(), 1); BOOST_CHECK_EQUAL(obj["age"].getValStr(), "42"); uv.setInt(43); obj.pushKV("age", uv); BOOST_CHECK_EQUAL(obj.size(), 1); BOOST_CHECK_EQUAL(obj["age"].getValStr(), "43"); obj.pushKV("name", "foo bar"); std::map kv; obj.getObjMap(kv); BOOST_CHECK_EQUAL(kv["age"].getValStr(), "43"); BOOST_CHECK_EQUAL(kv["name"].getValStr(), "foo bar"); } static const char *json1 = "[1.10000000,{\"key1\":\"str\\u0000\",\"key2\":800,\"key3\":{\"name\":\"martian http://test.com\"}}]"; BOOST_AUTO_TEST_CASE(univalue_readwrite) { UniValue v; BOOST_CHECK(v.read(json1)); std::string strJson1(json1); BOOST_CHECK(v.read(strJson1)); BOOST_CHECK(v.isArray()); BOOST_CHECK_EQUAL(v.size(), 2); BOOST_CHECK_EQUAL(v[0].getValStr(), "1.10000000"); UniValue obj = v[1]; BOOST_CHECK(obj.isObject()); BOOST_CHECK_EQUAL(obj.size(), 3); BOOST_CHECK(obj["key1"].isStr()); std::string correctValue("str"); correctValue.push_back('\0'); BOOST_CHECK_EQUAL(obj["key1"].getValStr(), correctValue); BOOST_CHECK(obj["key2"].isNum()); BOOST_CHECK_EQUAL(obj["key2"].getValStr(), "800"); BOOST_CHECK(obj["key3"].isObject()); BOOST_CHECK_EQUAL(strJson1, v.write()); /* Check for (correctly reporting) a parsing error if the initial JSON construct is followed by more stuff. Note that whitespace is, of course, exempt. */ BOOST_CHECK(v.read(" {}\n ")); BOOST_CHECK(v.isObject()); BOOST_CHECK(v.read(" []\n ")); BOOST_CHECK(v.isArray()); BOOST_CHECK(!v.read("@{}")); BOOST_CHECK(!v.read("{} garbage")); BOOST_CHECK(!v.read("[]{}")); BOOST_CHECK(!v.read("{}[]")); BOOST_CHECK(!v.read("{} 42")); } BOOST_AUTO_TEST_SUITE_END() int main (int argc, char *argv[]) { univalue_constructor(); univalue_typecheck(); univalue_set(); univalue_array(); univalue_object(); univalue_readwrite(); return 0; } diff --git a/src/univalue/test/pass4.json b/src/univalue/test/pass4.json new file mode 100644 index 0000000000..f5a680b31c --- /dev/null +++ b/src/univalue/test/pass4.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] diff --git a/src/univalue/test/unitester.cpp b/src/univalue/test/unitester.cpp index 8915714200..1a6c9ef69f 100644 --- a/src/univalue/test/unitester.cpp +++ b/src/univalue/test/unitester.cpp @@ -1,169 +1,171 @@ // Copyright 2014 BitPay Inc. // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. #include #include #include #include #include #include "univalue.h" #ifndef JSON_TEST_SRC #error JSON_TEST_SRC must point to test source directory #endif #ifndef ARRAY_SIZE #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #endif std::string srcdir(JSON_TEST_SRC); static bool test_failed = false; #define d_assert(expr) { if (!(expr)) { test_failed = true; fprintf(stderr, "%s failed\n", filename.c_str()); } } #define f_assert(expr) { if (!(expr)) { test_failed = true; fprintf(stderr, "%s failed\n", __func__); } } static std::string rtrim(std::string s) { s.erase(s.find_last_not_of(" \n\r\t")+1); return s; } static void runtest(std::string filename, const std::string& jdata) { std::string prefix = filename.substr(0, 4); bool wantPass = (prefix == "pass") || (prefix == "roun"); bool wantFail = (prefix == "fail"); bool wantRoundTrip = (prefix == "roun"); assert(wantPass || wantFail); UniValue val; bool testResult = val.read(jdata); if (wantPass) { d_assert(testResult == true); } else { d_assert(testResult == false); } if (wantRoundTrip) { std::string odata = val.write(0, 0); assert(odata == rtrim(jdata)); } } static void runtest_file(const char *filename_) { std::string basename(filename_); std::string filename = srcdir + "/" + basename; FILE *f = fopen(filename.c_str(), "r"); assert(f != NULL); std::string jdata; char buf[4096]; while (!feof(f)) { int bread = fread(buf, 1, sizeof(buf), f); assert(!ferror(f)); std::string s(buf, bread); jdata += s; } assert(!ferror(f)); fclose(f); runtest(basename, jdata); } static const char *filenames[] = { "fail10.json", "fail11.json", "fail12.json", "fail13.json", "fail14.json", "fail15.json", "fail16.json", "fail17.json", //"fail18.json", // investigate "fail19.json", "fail1.json", "fail20.json", "fail21.json", "fail22.json", "fail23.json", "fail24.json", "fail25.json", "fail26.json", "fail27.json", "fail28.json", "fail29.json", "fail2.json", "fail30.json", "fail31.json", "fail32.json", "fail33.json", "fail34.json", "fail35.json", "fail36.json", "fail37.json", "fail38.json", // invalid unicode: only first half of surrogate pair "fail39.json", // invalid unicode: only second half of surrogate pair "fail40.json", // invalid unicode: broken UTF-8 "fail41.json", // invalid unicode: unfinished UTF-8 "fail42.json", // valid json with garbage following a nul byte "fail44.json", // unterminated string + "fail45.json", // nested beyond max depth "fail3.json", "fail4.json", // extra comma "fail5.json", "fail6.json", "fail7.json", "fail8.json", "fail9.json", // extra comma "pass1.json", "pass2.json", "pass3.json", + "pass4.json", "round1.json", // round-trip test "round2.json", // unicode "round3.json", // bare string "round4.json", // bare number "round5.json", // bare true "round6.json", // bare false "round7.json", // bare null }; // Test \u handling void unescape_unicode_test() { UniValue val; bool testResult; // Escaped ASCII (quote) testResult = val.read("[\"\\u0022\"]"); f_assert(testResult); f_assert(val[0].get_str() == "\""); // Escaped Basic Plane character, two-byte UTF-8 testResult = val.read("[\"\\u0191\"]"); f_assert(testResult); f_assert(val[0].get_str() == "\xc6\x91"); // Escaped Basic Plane character, three-byte UTF-8 testResult = val.read("[\"\\u2191\"]"); f_assert(testResult); f_assert(val[0].get_str() == "\xe2\x86\x91"); // Escaped Supplementary Plane character U+1d161 testResult = val.read("[\"\\ud834\\udd61\"]"); f_assert(testResult); f_assert(val[0].get_str() == "\xf0\x9d\x85\xa1"); } int main (int argc, char *argv[]) { for (unsigned int fidx = 0; fidx < ARRAY_SIZE(filenames); fidx++) { runtest_file(filenames[fidx]); } unescape_unicode_test(); return test_failed ? 1 : 0; }