diff options
Diffstat (limited to 'src/univalue')
80 files changed, 2751 insertions, 0 deletions
diff --git a/src/univalue/.gitignore b/src/univalue/.gitignore new file mode 100644 index 0000000000..19e42f814a --- /dev/null +++ b/src/univalue/.gitignore @@ -0,0 +1,32 @@ +.deps/ +INSTALL +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +compile +config.log +config.status +config.guess +config.sub +configure +depcomp +install-sh +missing +stamp-h1 +univalue-config.h* +test-driver +libtool +ltmain.sh +test-suite.log + +*.a +*.la +*.lo +*.logs +*.o +*.pc +*.trs + +.dirstamp +.libs diff --git a/src/univalue/.travis.yml b/src/univalue/.travis.yml new file mode 100644 index 0000000000..43a1ed362e --- /dev/null +++ b/src/univalue/.travis.yml @@ -0,0 +1,51 @@ +language: cpp + +compiler: + - clang + - gcc + +os: + - linux + - osx + +sudo: false + +env: + global: + - MAKEJOBS=-j3 + - RUN_TESTS=true + - BASE_OUTDIR=$TRAVIS_BUILD_DIR/out + +cache: + apt: true + +addons: + apt: + packages: + - pkg-config + +before_script: + - if [ -n "$USE_SHELL" ]; then export CONFIG_SHELL="$USE_SHELL"; fi + - test -n "$USE_SHELL" && eval '"$USE_SHELL" -c "./autogen.sh"' || ./autogen.sh + +script: + - if [ -n "$UNIVALUE_CONFIG" ]; then unset CC; unset CXX; fi + - OUTDIR=$BASE_OUTDIR/$TRAVIS_PULL_REQUEST/$TRAVIS_JOB_NUMBER-$HOST + - UNIVALUE_CONFIG_ALL="--prefix=$TRAVIS_BUILD_DIR/depends/$HOST --bindir=$OUTDIR/bin --libdir=$OUTDIR/lib" + - ./configure --cache-file=config.cache $UNIVALUE_CONFIG_ALL $UNIVALUE_CONFIG || ( cat config.log && false) + - make -s $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && make $GOAL ; false ) + - export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib + - if [ "$RUN_TESTS" = "true" ]; then make $MAKEJOBS distcheck; fi + +matrix: + fast_finish: true + include: + - os: linux + compiler: gcc + env: UNIVALUE_CONFIG=--host=x86_64-w64-mingw32 RUN_TESTS=false + addons: + apt: + packages: + - g++-mingw-w64-x86-64 + - gcc-mingw-w64-x86-64 + - binutils-mingw-w64-x86-64 diff --git a/src/univalue/COPYING b/src/univalue/COPYING new file mode 100644 index 0000000000..1fb429f356 --- /dev/null +++ b/src/univalue/COPYING @@ -0,0 +1,19 @@ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/src/univalue/Makefile.am b/src/univalue/Makefile.am new file mode 100644 index 0000000000..0f5ba59954 --- /dev/null +++ b/src/univalue/Makefile.am @@ -0,0 +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 new file mode 100644 index 0000000000..7c62c33970 --- /dev/null +++ b/src/univalue/README.md @@ -0,0 +1,21 @@ + +# 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). + +## Library usage + +This is a fork of univalue used by Bitcoin Core. It is not maintained for usage +by other projects. Notably, the API may break in non-backward-compatible ways. + +Other projects looking for a maintained library should use the upstream +univalue at https://github.com/jgarzik/univalue. diff --git a/src/univalue/TODO b/src/univalue/TODO new file mode 100644 index 0000000000..5530048e92 --- /dev/null +++ b/src/univalue/TODO @@ -0,0 +1,10 @@ + +Rearrange tree for easier 'git subtree' style use + +Move towards C++11 etc. + +Namespace support - must come up with useful shorthand, avoiding +long Univalue::Univalue::Univalue usages forced upon library users. + +Improve test suite + diff --git a/src/univalue/autogen.sh b/src/univalue/autogen.sh new file mode 100755 index 0000000000..4b38721faa --- /dev/null +++ b/src/univalue/autogen.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e +srcdir="$(dirname $0)" +cd "$srcdir" +if [ -z ${LIBTOOLIZE} ] && GLIBTOOLIZE="`which glibtoolize 2>/dev/null`"; then + LIBTOOLIZE="${GLIBTOOLIZE}" + export LIBTOOLIZE +fi +autoreconf --install --force diff --git a/src/univalue/build-aux/m4/.gitignore b/src/univalue/build-aux/m4/.gitignore new file mode 100644 index 0000000000..f063686524 --- /dev/null +++ b/src/univalue/build-aux/m4/.gitignore @@ -0,0 +1 @@ +/*.m4 diff --git a/src/univalue/configure.ac b/src/univalue/configure.ac new file mode 100644 index 0000000000..8298332ac1 --- /dev/null +++ b/src/univalue/configure.ac @@ -0,0 +1,69 @@ +m4_define([libunivalue_major_version], [1]) +m4_define([libunivalue_minor_version], [1]) +m4_define([libunivalue_micro_version], [3]) +m4_define([libunivalue_interface_age], [3]) +# 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.3], + [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/gen/gen.cpp b/src/univalue/gen/gen.cpp new file mode 100644 index 0000000000..85fe20924a --- /dev/null +++ b/src/univalue/gen/gen.cpp @@ -0,0 +1,82 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// +// To re-create univalue_escapes.h: +// $ g++ -o gen gen.cpp +// $ ./gen > univalue_escapes.h +// + +#include <stdio.h> +#include <string.h> +#include "univalue.h" + +static bool initEscapes; +static std::string escapes[256]; + +static void initJsonEscape() +{ + // Escape all lower control characters (some get overridden with smaller sequences below) + for (int ch=0x00; ch<0x20; ++ch) { + char tmpbuf[20]; + snprintf(tmpbuf, sizeof(tmpbuf), "\\u%04x", ch); + escapes[ch] = std::string(tmpbuf); + } + + escapes[(int)'"'] = "\\\""; + escapes[(int)'\\'] = "\\\\"; + escapes[(int)'\b'] = "\\b"; + escapes[(int)'\f'] = "\\f"; + escapes[(int)'\n'] = "\\n"; + escapes[(int)'\r'] = "\\r"; + escapes[(int)'\t'] = "\\t"; + escapes[(int)'\x7f'] = "\\u007f"; // U+007F DELETE + + initEscapes = true; +} + +static void outputEscape() +{ + printf( "// Automatically generated file. Do not modify.\n" + "#ifndef BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n" + "#define BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n" + "static const char *escapes[256] = {\n"); + + for (unsigned int i = 0; i < 256; i++) { + if (escapes[i].empty()) { + printf("\tNULL,\n"); + } else { + printf("\t\""); + + unsigned int si; + for (si = 0; si < escapes[i].size(); si++) { + char ch = escapes[i][si]; + switch (ch) { + case '"': + printf("\\\""); + break; + case '\\': + printf("\\\\"); + break; + default: + printf("%c", escapes[i][si]); + break; + } + } + + printf("\",\n"); + } + } + + printf( "};\n" + "#endif // BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n"); +} + +int main (int argc, char *argv[]) +{ + initJsonEscape(); + outputEscape(); + return 0; +} + diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h new file mode 100644 index 0000000000..6080516353 --- /dev/null +++ b/src/univalue/include/univalue.h @@ -0,0 +1,238 @@ +// Copyright 2014 BitPay Inc. +// Copyright 2015 Bitcoin Core Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef __UNIVALUE_H__ +#define __UNIVALUE_H__ + +#include <stdint.h> +#include <string.h> + +#include <string> +#include <vector> +#include <map> +#include <cassert> + +#include <sstream> // .get_int64() + +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); + } + + 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<std::string,UniValue>& kv) const; + bool checkObject(const std::map<std::string,UniValue::VType>& 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<UniValue>& 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<std::string> keys; + std::vector<UniValue> 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<std::string>& getKeys() const; + const std::vector<UniValue>& 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(); } + friend const UniValue& find_value( const UniValue& obj, const std::string& name); +}; + +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/.gitignore b/src/univalue/lib/.gitignore new file mode 100644 index 0000000000..ee7fc2851c --- /dev/null +++ b/src/univalue/lib/.gitignore @@ -0,0 +1,2 @@ +gen +.libs diff --git a/src/univalue/lib/univalue.cpp b/src/univalue/lib/univalue.cpp new file mode 100644 index 0000000000..4c9c15d63e --- /dev/null +++ b/src/univalue/lib/univalue.cpp @@ -0,0 +1,242 @@ +// Copyright 2014 BitPay Inc. +// Copyright 2015 Bitcoin Core Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <stdint.h> +#include <iomanip> +#include <sstream> +#include <stdlib.h> + +#include "univalue.h" + +const UniValue NullUniValue; + +void UniValue::clear() +{ + typ = VNULL; + val.clear(); + keys.clear(); + values.clear(); +} + +bool UniValue::setNull() +{ + clear(); + return true; +} + +bool UniValue::setBool(bool val_) +{ + clear(); + typ = VBOOL; + if (val_) + val = "1"; + return true; +} + +static bool validNumStr(const std::string& s) +{ + std::string tokenVal; + unsigned int consumed; + enum jtokentype tt = getJsonToken(tokenVal, consumed, s.data(), s.data() + s.size()); + return (tt == JTOK_NUMBER); +} + +bool UniValue::setNumStr(const std::string& val_) +{ + if (!validNumStr(val_)) + return false; + + clear(); + typ = VNUM; + val = val_; + return true; +} + +bool UniValue::setInt(uint64_t val_) +{ + std::ostringstream oss; + + oss << val_; + + return setNumStr(oss.str()); +} + +bool UniValue::setInt(int64_t val_) +{ + std::ostringstream oss; + + oss << val_; + + return setNumStr(oss.str()); +} + +bool UniValue::setFloat(double val_) +{ + std::ostringstream oss; + + oss << std::setprecision(16) << val_; + + bool ret = setNumStr(oss.str()); + typ = VNUM; + return ret; +} + +bool UniValue::setStr(const std::string& val_) +{ + clear(); + typ = VSTR; + val = val_; + return true; +} + +bool UniValue::setArray() +{ + clear(); + typ = VARR; + return true; +} + +bool UniValue::setObject() +{ + clear(); + typ = VOBJ; + return true; +} + +bool UniValue::push_back(const UniValue& val_) +{ + if (typ != VARR) + return false; + + values.push_back(val_); + return true; +} + +bool UniValue::push_backV(const std::vector<UniValue>& vec) +{ + if (typ != VARR) + return false; + + values.insert(values.end(), vec.begin(), vec.end()); + + return true; +} + +void UniValue::__pushKV(const std::string& key, const UniValue& val_) +{ + keys.push_back(key); + values.push_back(val_); +} + +bool UniValue::pushKV(const std::string& key, const UniValue& val_) +{ + if (typ != VOBJ) + return false; + + size_t idx; + if (findKey(key, idx)) + values[idx] = val_; + else + __pushKV(key, val_); + return true; +} + +bool UniValue::pushKVs(const UniValue& obj) +{ + if (typ != VOBJ || obj.typ != VOBJ) + return false; + + for (size_t i = 0; i < obj.keys.size(); i++) + __pushKV(obj.keys[i], obj.values.at(i)); + + return true; +} + +void UniValue::getObjMap(std::map<std::string,UniValue>& kv) const +{ + if (typ != VOBJ) + return; + + kv.clear(); + for (size_t i = 0; i < keys.size(); i++) + kv[keys[i]] = values[i]; +} + +bool UniValue::findKey(const std::string& key, size_t& retIdx) const +{ + for (size_t i = 0; i < keys.size(); i++) { + if (keys[i] == key) { + retIdx = i; + return true; + } + } + + return false; +} + +bool UniValue::checkObject(const std::map<std::string,UniValue::VType>& t) const +{ + if (typ != VOBJ) + return false; + + for (std::map<std::string,UniValue::VType>::const_iterator it = t.begin(); + it != t.end(); ++it) { + size_t idx = 0; + if (!findKey(it->first, idx)) + return false; + + if (values.at(idx).getType() != it->second) + return false; + } + + return true; +} + +const UniValue& UniValue::operator[](const std::string& key) const +{ + if (typ != VOBJ) + return NullUniValue; + + size_t index = 0; + if (!findKey(key, index)) + return NullUniValue; + + return values.at(index); +} + +const UniValue& UniValue::operator[](size_t index) const +{ + if (typ != VOBJ && typ != VARR) + return NullUniValue; + if (index >= values.size()) + return NullUniValue; + + return values.at(index); +} + +const char *uvTypeName(UniValue::VType t) +{ + switch (t) { + case UniValue::VNULL: return "null"; + case UniValue::VBOOL: return "bool"; + case UniValue::VOBJ: return "object"; + case UniValue::VARR: return "array"; + case UniValue::VSTR: return "string"; + case UniValue::VNUM: return "number"; + } + + // not reached + return NULL; +} + +const UniValue& find_value(const UniValue& obj, const std::string& name) +{ + for (unsigned int i = 0; i < obj.keys.size(); i++) + if (obj.keys[i] == name) + return obj.values.at(i); + + return NullUniValue; +} + diff --git a/src/univalue/lib/univalue_escapes.h b/src/univalue/lib/univalue_escapes.h new file mode 100644 index 0000000000..74596aab6d --- /dev/null +++ b/src/univalue/lib/univalue_escapes.h @@ -0,0 +1,262 @@ +// Automatically generated file. Do not modify. +#ifndef BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H +#define BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H +static const char *escapes[256] = { + "\\u0000", + "\\u0001", + "\\u0002", + "\\u0003", + "\\u0004", + "\\u0005", + "\\u0006", + "\\u0007", + "\\b", + "\\t", + "\\n", + "\\u000b", + "\\f", + "\\r", + "\\u000e", + "\\u000f", + "\\u0010", + "\\u0011", + "\\u0012", + "\\u0013", + "\\u0014", + "\\u0015", + "\\u0016", + "\\u0017", + "\\u0018", + "\\u0019", + "\\u001a", + "\\u001b", + "\\u001c", + "\\u001d", + "\\u001e", + "\\u001f", + NULL, + NULL, + "\\\"", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "\\\\", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "\\u007f", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; +#endif // BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H diff --git a/src/univalue/lib/univalue_get.cpp b/src/univalue/lib/univalue_get.cpp new file mode 100644 index 0000000000..0ad6146545 --- /dev/null +++ b/src/univalue/lib/univalue_get.cpp @@ -0,0 +1,147 @@ +// Copyright 2014 BitPay Inc. +// Copyright 2015 Bitcoin Core Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <stdexcept> +#include <vector> +#include <limits> +#include <string> + +#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 an 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<int32_t>::min() && + n <= std::numeric_limits<int32_t>::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<int64_t>::min() && + n <= std::numeric_limits<int64_t>::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<std::string>& UniValue::getKeys() const +{ + if (typ != VOBJ) + throw std::runtime_error("JSON value is not an object as expected"); + return keys; +} + +const std::vector<UniValue>& 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 new file mode 100644 index 0000000000..5c6a1acf75 --- /dev/null +++ b/src/univalue/lib/univalue_read.cpp @@ -0,0 +1,460 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <string.h> +#include <vector> +#include <stdio.h> +#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<UniValue*> 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; + raw += consumed; + + bool isValueOpen = jsonTokenIsValue(tok) || + tok == JTOK_OBJ_OPEN || tok == JTOK_ARR_OPEN; + + if (expect(VALUE)) { + if (!isValueOpen) + return false; + clearExpect(VALUE); + + } else if (expect(ARR_VALUE)) { + bool isArrValue = isValueOpen || (tok == JTOK_ARR_CLOSE); + if (!isArrValue) + return false; + + clearExpect(ARR_VALUE); + + } else if (expect(OBJ_NAME)) { + bool isObjName = (tok == JTOK_OBJ_CLOSE || tok == JTOK_STRING); + if (!isObjName) + return false; + + } else if (expect(COLON)) { + if (tok != JTOK_COLON) + return false; + clearExpect(COLON); + + } else if (!expect(COLON) && (tok == JTOK_COLON)) { + return false; + } + + if (expect(NOT_VALUE)) { + if (isValueOpen) + return false; + 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) + return false; + + 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; + + VType utyp = (tok == JTOK_OBJ_CLOSE ? VOBJ : VARR); + UniValue *top = stack.back(); + if (utyp != top->getType()) + return false; + + stack.pop_back(); + clearExpect(OBJ_NAME); + setExpect(NOT_VALUE); + break; + } + + case JTOK_COLON: { + if (!stack.size()) + return false; + + UniValue *top = stack.back(); + if (top->getType() != VOBJ) + return false; + + setExpect(VALUE); + break; + } + + case JTOK_COMMA: { + if (!stack.size() || + (last_tok == JTOK_COMMA) || (last_tok == JTOK_ARR_OPEN)) + return false; + + 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; + } + } while (!stack.empty ()); + + /* Check that nothing follows the initial construct (parsed above). */ + tok = getJsonToken(tokenVal, consumed, raw, end); + if (tok != JTOK_NONE) + return false; + + return true; +} + diff --git a/src/univalue/lib/univalue_utffilter.h b/src/univalue/lib/univalue_utffilter.h new file mode 100644 index 0000000000..20d4043009 --- /dev/null +++ b/src/univalue/lib/univalue_utffilter.h @@ -0,0 +1,119 @@ +// Copyright 2016 Wladimir J. van der Laan +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef UNIVALUE_UTFFILTER_H +#define UNIVALUE_UTFFILTER_H + +#include <string> + +/** + * Filter that generates and validates UTF-8, as well as collates UTF-16 + * surrogate pairs as specified in RFC4627. + */ +class JSONUTF8StringFilter +{ +public: + explicit JSONUTF8StringFilter(std::string &s): + str(s), is_valid(true), codepoint(0), state(0), surpair(0) + { + } + // Write single 8-bit char (may be part of UTF-8 sequence) + void push_back(unsigned char ch) + { + if (state == 0) { + if (ch < 0x80) // 7-bit ASCII, fast direct pass-through + str.push_back(ch); + else if (ch < 0xc0) // Mid-sequence character, invalid in this state + is_valid = false; + else if (ch < 0xe0) { // Start of 2-byte sequence + codepoint = (ch & 0x1f) << 6; + state = 6; + } else if (ch < 0xf0) { // Start of 3-byte sequence + codepoint = (ch & 0x0f) << 12; + state = 12; + } else if (ch < 0xf8) { // Start of 4-byte sequence + codepoint = (ch & 0x07) << 18; + state = 18; + } else // Reserved, invalid + is_valid = false; + } else { + if ((ch & 0xc0) != 0x80) // Not a continuation, invalid + is_valid = false; + state -= 6; + codepoint |= (ch & 0x3f) << state; + if (state == 0) + push_back_u(codepoint); + } + } + // Write codepoint directly, possibly collating surrogate pairs + void push_back_u(unsigned int codepoint_) + { + if (state) // Only accept full codepoints in open state + is_valid = false; + if (codepoint_ >= 0xD800 && codepoint_ < 0xDC00) { // First half of surrogate pair + if (surpair) // Two subsequent surrogate pair openers - fail + is_valid = false; + else + surpair = codepoint_; + } else if (codepoint_ >= 0xDC00 && codepoint_ < 0xE000) { // Second half of surrogate pair + if (surpair) { // Open surrogate pair, expect second half + // Compute code point from UTF-16 surrogate pair + append_codepoint(0x10000 | ((surpair - 0xD800)<<10) | (codepoint_ - 0xDC00)); + surpair = 0; + } else // Second half doesn't follow a first half - fail + is_valid = false; + } else { + if (surpair) // First half of surrogate pair not followed by second - fail + is_valid = false; + else + append_codepoint(codepoint_); + } + } + // Check that we're in a state where the string can be ended + // No open sequences, no open surrogate pairs, etc + bool finalize() + { + if (state || surpair) + is_valid = false; + return is_valid; + } +private: + std::string &str; + bool is_valid; + // Current UTF-8 decoding state + unsigned int codepoint; + int state; // Top bit to be filled in for next UTF-8 byte, or 0 + + // Keep track of the following state to handle the following section of + // RFC4627: + // + // To escape an extended character that is not in the Basic Multilingual + // Plane, the character is represented as a twelve-character sequence, + // encoding the UTF-16 surrogate pair. So, for example, a string + // containing only the G clef character (U+1D11E) may be represented as + // "\uD834\uDD1E". + // + // Two subsequent \u.... may have to be replaced with one actual codepoint. + unsigned int surpair; // First half of open UTF-16 surrogate pair, or 0 + + void append_codepoint(unsigned int codepoint_) + { + if (codepoint_ <= 0x7f) + str.push_back((char)codepoint_); + else if (codepoint_ <= 0x7FF) { + str.push_back((char)(0xC0 | (codepoint_ >> 6))); + str.push_back((char)(0x80 | (codepoint_ & 0x3F))); + } else if (codepoint_ <= 0xFFFF) { + str.push_back((char)(0xE0 | (codepoint_ >> 12))); + str.push_back((char)(0x80 | ((codepoint_ >> 6) & 0x3F))); + str.push_back((char)(0x80 | (codepoint_ & 0x3F))); + } else if (codepoint_ <= 0x1FFFFF) { + str.push_back((char)(0xF0 | (codepoint_ >> 18))); + str.push_back((char)(0x80 | ((codepoint_ >> 12) & 0x3F))); + str.push_back((char)(0x80 | ((codepoint_ >> 6) & 0x3F))); + str.push_back((char)(0x80 | (codepoint_ & 0x3F))); + } + } +}; + +#endif diff --git a/src/univalue/lib/univalue_write.cpp b/src/univalue/lib/univalue_write.cpp new file mode 100644 index 0000000000..827eb9b271 --- /dev/null +++ b/src/univalue/lib/univalue_write.cpp @@ -0,0 +1,113 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <iomanip> +#include <sstream> +#include <stdio.h> +#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/pc/libunivalue-uninstalled.pc.in b/src/univalue/pc/libunivalue-uninstalled.pc.in new file mode 100644 index 0000000000..b7f53e875e --- /dev/null +++ b/src/univalue/pc/libunivalue-uninstalled.pc.in @@ -0,0 +1,9 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libunivalue +Description: libunivalue, C++ universal value object and JSON library +Version: @VERSION@ +Libs: ${pc_top_builddir}/${pcfiledir}/libunivalue.la diff --git a/src/univalue/pc/libunivalue.pc.in b/src/univalue/pc/libunivalue.pc.in new file mode 100644 index 0000000000..358a2d5f73 --- /dev/null +++ b/src/univalue/pc/libunivalue.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libunivalue +Description: libunivalue, C++ universal value object and JSON library +Version: @VERSION@ +Libs: -L${libdir} -lunivalue +Cflags: -I${includedir} diff --git a/src/univalue/test/.gitignore b/src/univalue/test/.gitignore new file mode 100644 index 0000000000..7b27cf0da2 --- /dev/null +++ b/src/univalue/test/.gitignore @@ -0,0 +1,8 @@ + +object +unitester +test_json +no_nul + +*.trs +*.log diff --git a/src/univalue/test/fail1.json b/src/univalue/test/fail1.json new file mode 100644 index 0000000000..8feb01a6d0 --- /dev/null +++ b/src/univalue/test/fail1.json @@ -0,0 +1 @@ +"This is a string that never ends, yes it goes on and on, my friends. diff --git a/src/univalue/test/fail10.json b/src/univalue/test/fail10.json new file mode 100644 index 0000000000..5d8c0047bd --- /dev/null +++ b/src/univalue/test/fail10.json @@ -0,0 +1 @@ +{"Extra value after close": true} "misplaced quoted value"
\ No newline at end of file diff --git a/src/univalue/test/fail11.json b/src/univalue/test/fail11.json new file mode 100644 index 0000000000..76eb95b458 --- /dev/null +++ b/src/univalue/test/fail11.json @@ -0,0 +1 @@ +{"Illegal expression": 1 + 2}
\ No newline at end of file diff --git a/src/univalue/test/fail12.json b/src/univalue/test/fail12.json new file mode 100644 index 0000000000..77580a4522 --- /dev/null +++ b/src/univalue/test/fail12.json @@ -0,0 +1 @@ +{"Illegal invocation": alert()}
\ No newline at end of file diff --git a/src/univalue/test/fail13.json b/src/univalue/test/fail13.json new file mode 100644 index 0000000000..379406b59b --- /dev/null +++ b/src/univalue/test/fail13.json @@ -0,0 +1 @@ +{"Numbers cannot have leading zeroes": 013}
\ No newline at end of file diff --git a/src/univalue/test/fail14.json b/src/univalue/test/fail14.json new file mode 100644 index 0000000000..0ed366b38a --- /dev/null +++ b/src/univalue/test/fail14.json @@ -0,0 +1 @@ +{"Numbers cannot be hex": 0x14}
\ No newline at end of file diff --git a/src/univalue/test/fail15.json b/src/univalue/test/fail15.json new file mode 100644 index 0000000000..fc8376b605 --- /dev/null +++ b/src/univalue/test/fail15.json @@ -0,0 +1 @@ +["Illegal backslash escape: \x15"]
\ No newline at end of file diff --git a/src/univalue/test/fail16.json b/src/univalue/test/fail16.json new file mode 100644 index 0000000000..3fe21d4b53 --- /dev/null +++ b/src/univalue/test/fail16.json @@ -0,0 +1 @@ +[\naked]
\ No newline at end of file diff --git a/src/univalue/test/fail17.json b/src/univalue/test/fail17.json new file mode 100644 index 0000000000..62b9214aed --- /dev/null +++ b/src/univalue/test/fail17.json @@ -0,0 +1 @@ +["Illegal backslash escape: \017"]
\ No newline at end of file diff --git a/src/univalue/test/fail18.json b/src/univalue/test/fail18.json new file mode 100644 index 0000000000..edac92716f --- /dev/null +++ b/src/univalue/test/fail18.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
\ No newline at end of file diff --git a/src/univalue/test/fail19.json b/src/univalue/test/fail19.json new file mode 100644 index 0000000000..3b9c46fa9a --- /dev/null +++ b/src/univalue/test/fail19.json @@ -0,0 +1 @@ +{"Missing colon" null}
\ No newline at end of file diff --git a/src/univalue/test/fail2.json b/src/univalue/test/fail2.json new file mode 100644 index 0000000000..6b7c11e5a5 --- /dev/null +++ b/src/univalue/test/fail2.json @@ -0,0 +1 @@ +["Unclosed array"
\ No newline at end of file diff --git a/src/univalue/test/fail20.json b/src/univalue/test/fail20.json new file mode 100644 index 0000000000..27c1af3e72 --- /dev/null +++ b/src/univalue/test/fail20.json @@ -0,0 +1 @@ +{"Double colon":: null}
\ No newline at end of file diff --git a/src/univalue/test/fail21.json b/src/univalue/test/fail21.json new file mode 100644 index 0000000000..62474573b2 --- /dev/null +++ b/src/univalue/test/fail21.json @@ -0,0 +1 @@ +{"Comma instead of colon", null}
\ No newline at end of file diff --git a/src/univalue/test/fail22.json b/src/univalue/test/fail22.json new file mode 100644 index 0000000000..a7752581bc --- /dev/null +++ b/src/univalue/test/fail22.json @@ -0,0 +1 @@ +["Colon instead of comma": false]
\ No newline at end of file diff --git a/src/univalue/test/fail23.json b/src/univalue/test/fail23.json new file mode 100644 index 0000000000..494add1ca1 --- /dev/null +++ b/src/univalue/test/fail23.json @@ -0,0 +1 @@ +["Bad value", truth]
\ No newline at end of file diff --git a/src/univalue/test/fail24.json b/src/univalue/test/fail24.json new file mode 100644 index 0000000000..caff239bfc --- /dev/null +++ b/src/univalue/test/fail24.json @@ -0,0 +1 @@ +['single quote']
\ No newline at end of file diff --git a/src/univalue/test/fail25.json b/src/univalue/test/fail25.json new file mode 100644 index 0000000000..8b7ad23e01 --- /dev/null +++ b/src/univalue/test/fail25.json @@ -0,0 +1 @@ +[" tab character in string "]
\ No newline at end of file diff --git a/src/univalue/test/fail26.json b/src/univalue/test/fail26.json new file mode 100644 index 0000000000..845d26a6a5 --- /dev/null +++ b/src/univalue/test/fail26.json @@ -0,0 +1 @@ +["tab\ character\ in\ string\ "]
\ No newline at end of file diff --git a/src/univalue/test/fail27.json b/src/univalue/test/fail27.json new file mode 100644 index 0000000000..6b01a2ca4a --- /dev/null +++ b/src/univalue/test/fail27.json @@ -0,0 +1,2 @@ +["line +break"]
\ No newline at end of file diff --git a/src/univalue/test/fail28.json b/src/univalue/test/fail28.json new file mode 100644 index 0000000000..621a0101c6 --- /dev/null +++ b/src/univalue/test/fail28.json @@ -0,0 +1,2 @@ +["line\ +break"]
\ No newline at end of file diff --git a/src/univalue/test/fail29.json b/src/univalue/test/fail29.json new file mode 100644 index 0000000000..47ec421bb6 --- /dev/null +++ b/src/univalue/test/fail29.json @@ -0,0 +1 @@ +[0e]
\ No newline at end of file diff --git a/src/univalue/test/fail3.json b/src/univalue/test/fail3.json new file mode 100644 index 0000000000..168c81eb78 --- /dev/null +++ b/src/univalue/test/fail3.json @@ -0,0 +1 @@ +{unquoted_key: "keys must be quoted"}
\ No newline at end of file diff --git a/src/univalue/test/fail30.json b/src/univalue/test/fail30.json new file mode 100644 index 0000000000..8ab0bc4b8b --- /dev/null +++ b/src/univalue/test/fail30.json @@ -0,0 +1 @@ +[0e+]
\ No newline at end of file diff --git a/src/univalue/test/fail31.json b/src/univalue/test/fail31.json new file mode 100644 index 0000000000..1cce602b51 --- /dev/null +++ b/src/univalue/test/fail31.json @@ -0,0 +1 @@ +[0e+-1]
\ No newline at end of file diff --git a/src/univalue/test/fail32.json b/src/univalue/test/fail32.json new file mode 100644 index 0000000000..45cba7396f --- /dev/null +++ b/src/univalue/test/fail32.json @@ -0,0 +1 @@ +{"Comma instead if closing brace": true,
\ No newline at end of file diff --git a/src/univalue/test/fail33.json b/src/univalue/test/fail33.json new file mode 100644 index 0000000000..ca5eb19dc9 --- /dev/null +++ b/src/univalue/test/fail33.json @@ -0,0 +1 @@ +["mismatch"}
\ No newline at end of file diff --git a/src/univalue/test/fail34.json b/src/univalue/test/fail34.json new file mode 100644 index 0000000000..3f8be17286 --- /dev/null +++ b/src/univalue/test/fail34.json @@ -0,0 +1 @@ +{} garbage
\ No newline at end of file diff --git a/src/univalue/test/fail35.json b/src/univalue/test/fail35.json new file mode 100644 index 0000000000..de30ca5c47 --- /dev/null +++ b/src/univalue/test/fail35.json @@ -0,0 +1 @@ +[ true true true [] [] [] ] diff --git a/src/univalue/test/fail36.json b/src/univalue/test/fail36.json new file mode 100644 index 0000000000..f82eb8e1f0 --- /dev/null +++ b/src/univalue/test/fail36.json @@ -0,0 +1 @@ +{"a":} diff --git a/src/univalue/test/fail37.json b/src/univalue/test/fail37.json new file mode 100644 index 0000000000..3294dc3a42 --- /dev/null +++ b/src/univalue/test/fail37.json @@ -0,0 +1 @@ +{"a":1 "b":2} diff --git a/src/univalue/test/fail38.json b/src/univalue/test/fail38.json new file mode 100644 index 0000000000..b245e2e46c --- /dev/null +++ b/src/univalue/test/fail38.json @@ -0,0 +1 @@ +["\ud834"] diff --git a/src/univalue/test/fail39.json b/src/univalue/test/fail39.json new file mode 100644 index 0000000000..7c9e263f27 --- /dev/null +++ b/src/univalue/test/fail39.json @@ -0,0 +1 @@ +["\udd61"] diff --git a/src/univalue/test/fail4.json b/src/univalue/test/fail4.json new file mode 100644 index 0000000000..9de168bf34 --- /dev/null +++ b/src/univalue/test/fail4.json @@ -0,0 +1 @@ +["extra comma",]
\ No newline at end of file diff --git a/src/univalue/test/fail40.json b/src/univalue/test/fail40.json new file mode 100644 index 0000000000..664dc9e245 --- /dev/null +++ b/src/univalue/test/fail40.json @@ -0,0 +1 @@ +["
ก"]
\ No newline at end of file diff --git a/src/univalue/test/fail41.json b/src/univalue/test/fail41.json new file mode 100644 index 0000000000..0de342a2b5 --- /dev/null +++ b/src/univalue/test/fail41.json @@ -0,0 +1 @@ +["๐
"]
\ No newline at end of file diff --git a/src/univalue/test/fail42.json b/src/univalue/test/fail42.json Binary files differnew file mode 100644 index 0000000000..9c7565adbd --- /dev/null +++ b/src/univalue/test/fail42.json diff --git a/src/univalue/test/fail44.json b/src/univalue/test/fail44.json new file mode 100644 index 0000000000..80edceddf1 --- /dev/null +++ b/src/univalue/test/fail44.json @@ -0,0 +1 @@ +"This file ends without a newline or close-quote.
\ No newline at end of file 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/fail5.json b/src/univalue/test/fail5.json new file mode 100644 index 0000000000..ddf3ce3d24 --- /dev/null +++ b/src/univalue/test/fail5.json @@ -0,0 +1 @@ +["double extra comma",,]
\ No newline at end of file diff --git a/src/univalue/test/fail6.json b/src/univalue/test/fail6.json new file mode 100644 index 0000000000..ed91580e1b --- /dev/null +++ b/src/univalue/test/fail6.json @@ -0,0 +1 @@ +[ , "<-- missing value"]
\ No newline at end of file diff --git a/src/univalue/test/fail7.json b/src/univalue/test/fail7.json new file mode 100644 index 0000000000..8a96af3e4e --- /dev/null +++ b/src/univalue/test/fail7.json @@ -0,0 +1 @@ +["Comma after the close"],
\ No newline at end of file diff --git a/src/univalue/test/fail8.json b/src/univalue/test/fail8.json new file mode 100644 index 0000000000..b28479c6ec --- /dev/null +++ b/src/univalue/test/fail8.json @@ -0,0 +1 @@ +["Extra close"]]
\ No newline at end of file diff --git a/src/univalue/test/fail9.json b/src/univalue/test/fail9.json new file mode 100644 index 0000000000..5815574f36 --- /dev/null +++ b/src/univalue/test/fail9.json @@ -0,0 +1 @@ +{"Extra comma": true,}
\ No newline at end of file diff --git a/src/univalue/test/no_nul.cpp b/src/univalue/test/no_nul.cpp new file mode 100644 index 0000000000..83d292200b --- /dev/null +++ b/src/univalue/test/no_nul.cpp @@ -0,0 +1,8 @@ +#include "univalue.h" + +int main (int argc, char *argv[]) +{ + char buf[] = "___[1,2,3]___"; + UniValue val; + return val.read(buf + 3, 7) ? 0 : 1; +} diff --git a/src/univalue/test/object.cpp b/src/univalue/test/object.cpp new file mode 100644 index 0000000000..70ccc0d08a --- /dev/null +++ b/src/univalue/test/object.cpp @@ -0,0 +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 http://www.opensource.org/licenses/mit-license.php. + +#include <stdint.h> +#include <vector> +#include <string> +#include <map> +#include <cassert> +#include <stdexcept> +#include <univalue.h> + +#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 && "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<UniValue> 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<UniValue> 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<std::string, UniValue::VType> 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<std::string,UniValue> 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/pass1.json b/src/univalue/test/pass1.json new file mode 100644 index 0000000000..70e2685436 --- /dev/null +++ b/src/univalue/test/pass1.json @@ -0,0 +1,58 @@ +[ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & \/", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?", + "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* <!-- --", + "# -- --> */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \u0022 %22 0x22 034 "", + "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" +: "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 +,"rosebud"]
\ No newline at end of file diff --git a/src/univalue/test/pass2.json b/src/univalue/test/pass2.json new file mode 100644 index 0000000000..d3c63c7ad8 --- /dev/null +++ b/src/univalue/test/pass2.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
\ No newline at end of file diff --git a/src/univalue/test/pass3.json b/src/univalue/test/pass3.json new file mode 100644 index 0000000000..4528d51f1a --- /dev/null +++ b/src/univalue/test/pass3.json @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} 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/round1.json b/src/univalue/test/round1.json new file mode 100644 index 0000000000..a711e7308b --- /dev/null +++ b/src/univalue/test/round1.json @@ -0,0 +1 @@ +["\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f"] diff --git a/src/univalue/test/round2.json b/src/univalue/test/round2.json new file mode 100644 index 0000000000..b766cccc68 --- /dev/null +++ b/src/univalue/test/round2.json @@ -0,0 +1 @@ +["aยงโ ๐๐
ก"] diff --git a/src/univalue/test/round3.json b/src/univalue/test/round3.json new file mode 100644 index 0000000000..7182dc2f9b --- /dev/null +++ b/src/univalue/test/round3.json @@ -0,0 +1 @@ +"abcdefghijklmnopqrstuvwxyz" diff --git a/src/univalue/test/round4.json b/src/univalue/test/round4.json new file mode 100644 index 0000000000..7f8f011eb7 --- /dev/null +++ b/src/univalue/test/round4.json @@ -0,0 +1 @@ +7 diff --git a/src/univalue/test/round5.json b/src/univalue/test/round5.json new file mode 100644 index 0000000000..27ba77ddaf --- /dev/null +++ b/src/univalue/test/round5.json @@ -0,0 +1 @@ +true diff --git a/src/univalue/test/round6.json b/src/univalue/test/round6.json new file mode 100644 index 0000000000..c508d5366f --- /dev/null +++ b/src/univalue/test/round6.json @@ -0,0 +1 @@ +false diff --git a/src/univalue/test/round7.json b/src/univalue/test/round7.json new file mode 100644 index 0000000000..19765bd501 --- /dev/null +++ b/src/univalue/test/round7.json @@ -0,0 +1 @@ +null diff --git a/src/univalue/test/test_json.cpp b/src/univalue/test/test_json.cpp new file mode 100644 index 0000000000..2943bae2b1 --- /dev/null +++ b/src/univalue/test/test_json.cpp @@ -0,0 +1,24 @@ +// Test program that can be called by the JSON test suite at +// https://github.com/nst/JSONTestSuite. +// +// It reads JSON input from stdin and exits with code 0 if it can be parsed +// successfully. It also pretty prints the parsed JSON value to stdout. + +#include <iostream> +#include <string> +#include "univalue.h" + +using namespace std; + +int main (int argc, char *argv[]) +{ + UniValue val; + if (val.read(string(istreambuf_iterator<char>(cin), + istreambuf_iterator<char>()))) { + cout << val.write(1 /* prettyIndent */, 4 /* indentLevel */) << endl; + return 0; + } else { + cerr << "JSON Parse Error." << endl; + return 1; + } +} diff --git a/src/univalue/test/unitester.cpp b/src/univalue/test/unitester.cpp new file mode 100644 index 0000000000..2308afbcdf --- /dev/null +++ b/src/univalue/test/unitester.cpp @@ -0,0 +1,171 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <cassert> +#include <string> +#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; +} + |