From 2f9f082b5ef3c495c70598ef23383effef675f9a Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Thu, 1 Oct 2015 10:37:19 +0200 Subject: [PATCH] Squashed 'src/univalue/' content from commit 87d9045 git-subtree-dir: src/univalue git-subtree-split: 87d90455ff5e87dedc304353aa23ace47ffb6c1c --- .gitignore | 22 ++ .travis.yml | 52 +++++ COPYING | 19 ++ Makefile.am | 84 +++++++ README | 7 + TODO | 10 + autogen.sh | 9 + build-aux/m4/.empty | 0 configure.ac | 69 ++++++ gen/gen.cpp | 77 ++++++ include/univalue.h | 250 ++++++++++++++++++++ lib/.gitignore | 10 + lib/univalue.cpp | 365 +++++++++++++++++++++++++++++ lib/univalue_escapes.h | 262 +++++++++++++++++++++ lib/univalue_read.cpp | 389 +++++++++++++++++++++++++++++++ lib/univalue_write.cpp | 127 ++++++++++ pc/libunivalue-uninstalled.pc.in | 9 + pc/libunivalue.pc.in | 10 + test/.gitignore | 7 + test/fail1.json | 1 + test/fail10.json | 1 + test/fail11.json | 1 + test/fail12.json | 1 + test/fail13.json | 1 + test/fail14.json | 1 + test/fail15.json | 1 + test/fail16.json | 1 + test/fail17.json | 1 + test/fail18.json | 1 + test/fail19.json | 1 + test/fail2.json | 1 + test/fail20.json | 1 + test/fail21.json | 1 + test/fail22.json | 1 + test/fail23.json | 1 + test/fail24.json | 1 + test/fail25.json | 1 + test/fail26.json | 1 + test/fail27.json | 2 + test/fail28.json | 2 + test/fail29.json | 1 + test/fail3.json | 1 + test/fail30.json | 1 + test/fail31.json | 1 + test/fail32.json | 1 + test/fail33.json | 1 + test/fail34.json | 1 + test/fail4.json | 1 + test/fail5.json | 1 + test/fail6.json | 1 + test/fail7.json | 1 + test/fail8.json | 1 + test/fail9.json | 1 + test/pass1.json | 58 +++++ test/pass2.json | 1 + test/pass3.json | 6 + test/unitester.cpp | 115 +++++++++ 57 files changed, 1994 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 COPYING create mode 100644 Makefile.am create mode 100644 README create mode 100644 TODO create mode 100755 autogen.sh create mode 100644 build-aux/m4/.empty create mode 100644 configure.ac create mode 100644 gen/gen.cpp create mode 100644 include/univalue.h create mode 100644 lib/.gitignore create mode 100644 lib/univalue.cpp create mode 100644 lib/univalue_escapes.h create mode 100644 lib/univalue_read.cpp create mode 100644 lib/univalue_write.cpp create mode 100644 pc/libunivalue-uninstalled.pc.in create mode 100644 pc/libunivalue.pc.in create mode 100644 test/.gitignore create mode 100644 test/fail1.json create mode 100644 test/fail10.json create mode 100644 test/fail11.json create mode 100644 test/fail12.json create mode 100644 test/fail13.json create mode 100644 test/fail14.json create mode 100644 test/fail15.json create mode 100644 test/fail16.json create mode 100644 test/fail17.json create mode 100644 test/fail18.json create mode 100644 test/fail19.json create mode 100644 test/fail2.json create mode 100644 test/fail20.json create mode 100644 test/fail21.json create mode 100644 test/fail22.json create mode 100644 test/fail23.json create mode 100644 test/fail24.json create mode 100644 test/fail25.json create mode 100644 test/fail26.json create mode 100644 test/fail27.json create mode 100644 test/fail28.json create mode 100644 test/fail29.json create mode 100644 test/fail3.json create mode 100644 test/fail30.json create mode 100644 test/fail31.json create mode 100644 test/fail32.json create mode 100644 test/fail33.json create mode 100644 test/fail34.json create mode 100644 test/fail4.json create mode 100644 test/fail5.json create mode 100644 test/fail6.json create mode 100644 test/fail7.json create mode 100644 test/fail8.json create mode 100644 test/fail9.json create mode 100644 test/pass1.json create mode 100644 test/pass2.json create mode 100644 test/pass3.json create mode 100644 test/unitester.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ca9e84234 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +.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 + +*.o diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..af632c78d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,52 @@ + +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 check; 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/COPYING b/COPYING new file mode 100644 index 000000000..1fb429f35 --- /dev/null +++ b/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/Makefile.am b/Makefile.am new file mode 100644 index 000000000..2800f466d --- /dev/null +++ b/Makefile.am @@ -0,0 +1,84 @@ +ACLOCAL_AMFLAGS = -I build-aux/m4 +.PHONY: gen +.INTERMEDIATE: $(GENBIN) + +include_HEADERS = include/univalue.h +noinst_HEADERS = lib/univalue_escapes.h + +lib_LTLIBRARIES = lib/libunivalue.la + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = pc/libunivalue.pc + +lib_libunivalue_la_SOURCES = \ + lib/univalue.cpp \ + lib/univalue_read.cpp \ + lib/univalue_write.cpp + +lib_libunivalue_la_LDFLAGS = \ + -version-info $(LIBUNIVALUE_CURRENT):$(LIBUNIVALUE_REVISION):$(LIBUNIVALUE_AGE) \ + -no-undefined +lib_libunivalue_la_CXXFLAGS = -I$(top_srcdir)/include + +TESTS = test/unitester + +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_DATA_DIR=test + +test_unitester_SOURCES = test/unitester.cpp +test_unitester_LDADD = lib/libunivalue.la +test_unitester_CXXFLAGS = -I$(top_srcdir)/include -DJSON_TEST_SRC=\"$(srcdir)/$(TEST_DATA_DIR)\" +test_unitester_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)/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 + +EXTRA_DIST=$(TEST_FILES) $(GEN_SRCS) diff --git a/README b/README new file mode 100644 index 000000000..48167b083 --- /dev/null +++ b/README @@ -0,0 +1,7 @@ + + UniValue + +A universal value object, with JSON encoding (output) and decoding (input). + +Built as a single dynamic RAII C++ object class, and no templates. + diff --git a/TODO b/TODO new file mode 100644 index 000000000..5530048e9 --- /dev/null +++ b/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/autogen.sh b/autogen.sh new file mode 100755 index 000000000..4b38721fa --- /dev/null +++ b/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/build-aux/m4/.empty b/build-aux/m4/.empty new file mode 100644 index 000000000..e69de29bb diff --git a/configure.ac b/configure.ac new file mode 100644 index 000000000..6cd951622 --- /dev/null +++ b/configure.ac @@ -0,0 +1,69 @@ +m4_define([libunivalue_major_version], [1]) +m4_define([libunivalue_minor_version], [1]) +m4_define([libunivalue_micro_version], [1]) +m4_define([libunivalue_interface_age], [1]) +# 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.0], + [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/gen/gen.cpp b/gen/gen.cpp new file mode 100644 index 000000000..5e5a4d4ae --- /dev/null +++ b/gen/gen.cpp @@ -0,0 +1,77 @@ +// 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 +#include +#include +#include "univalue.h" + +using namespace std; + +static bool initEscapes; +static const char *escapes[256]; + +static void initJsonEscape() +{ + escapes[(int)'"'] = "\\\""; + escapes[(int)'\\'] = "\\\\"; + escapes[(int)'\b'] = "\\b"; + escapes[(int)'\f'] = "\\f"; + escapes[(int)'\n'] = "\\n"; + escapes[(int)'\r'] = "\\r"; + escapes[(int)'\t'] = "\\t"; + + 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]) { + printf("\tNULL,\n"); + } else { + printf("\t\""); + + unsigned int si; + for (si = 0; si < strlen(escapes[i]); 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/include/univalue.h b/include/univalue.h new file mode 100644 index 000000000..ac0511601 --- /dev/null +++ b/include/univalue.h @@ -0,0 +1,250 @@ +// 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 + +#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(); } + bool checkObject(const std::map& memberTypes); + const UniValue& operator[](const std::string& key) const; + const UniValue& operator[](unsigned int index) const; + bool exists(const std::string& key) const { return (findKey(key) >= 0); } + + 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_backV(const std::vector& vec); + + 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, 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); + bool read(const std::string& rawStr) { + return read(rawStr.c_str()); + } + +private: + UniValue::VType typ; + std::string val; // numbers are stored as C++ strings + std::vector keys; + std::vector values; + + int findKey(const std::string& key) 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 + std::vector getKeys() const; + std::vector getValues() const; + bool get_bool() 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); +extern const char *uvTypeName(UniValue::VType t); + +extern const UniValue NullUniValue; + +const UniValue& find_value( const UniValue& obj, const std::string& name); + +#endif // __UNIVALUE_H__ \ No newline at end of file diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 000000000..ca8c16dcd --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1,10 @@ + +libunivalue-uninstalled.pc +libunivalue.pc +libunivalue.a +gen + +.libs +*.lo +*.la + diff --git a/lib/univalue.cpp b/lib/univalue.cpp new file mode 100644 index 000000000..883e8651f --- /dev/null +++ b/lib/univalue.cpp @@ -0,0 +1,365 @@ +// 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 +#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 && (isspace(str[0]) || 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(); +} +} + +using namespace std; + +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 string& s) +{ + string tokenVal; + unsigned int consumed; + enum jtokentype tt = getJsonToken(tokenVal, consumed, s.c_str()); + return (tt == JTOK_NUMBER); +} + +bool UniValue::setNumStr(const string& val_) +{ + if (!validNumStr(val_)) + return false; + + clear(); + typ = VNUM; + val = val_; + return true; +} + +bool UniValue::setInt(uint64_t val) +{ + string s; + ostringstream oss; + + oss << val; + + return setNumStr(oss.str()); +} + +bool UniValue::setInt(int64_t val) +{ + string s; + ostringstream oss; + + oss << val; + + return setNumStr(oss.str()); +} + +bool UniValue::setFloat(double val) +{ + string s; + ostringstream oss; + + oss << std::setprecision(16) << val; + + bool ret = setNumStr(oss.str()); + typ = VNUM; + return ret; +} + +bool UniValue::setStr(const 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& vec) +{ + if (typ != VARR) + return false; + + values.insert(values.end(), vec.begin(), vec.end()); + + return true; +} + +bool UniValue::pushKV(const std::string& key, const UniValue& val) +{ + if (typ != VOBJ) + return false; + + keys.push_back(key); + values.push_back(val); + return true; +} + +bool UniValue::pushKVs(const UniValue& obj) +{ + if (typ != VOBJ || obj.typ != VOBJ) + return false; + + for (unsigned int i = 0; i < obj.keys.size(); i++) { + keys.push_back(obj.keys[i]); + values.push_back(obj.values[i]); + } + + return true; +} + +int UniValue::findKey(const std::string& key) const +{ + for (unsigned int i = 0; i < keys.size(); i++) { + if (keys[i] == key) + return (int) i; + } + + return -1; +} + +bool UniValue::checkObject(const std::map& t) +{ + for (std::map::const_iterator it = t.begin(); + it != t.end(); it++) { + int idx = findKey(it->first); + if (idx < 0) + return false; + + if (values[idx].getType() != it->second) + return false; + } + + return true; +} + +const UniValue& UniValue::operator[](const std::string& key) const +{ + if (typ != VOBJ) + return NullUniValue; + + int index = findKey(key); + if (index < 0) + return NullUniValue; + + return values[index]; +} + +const UniValue& UniValue::operator[](unsigned int index) const +{ + if (typ != VOBJ && typ != VARR) + return NullUniValue; + if (index >= values.size()) + return NullUniValue; + + return values[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[i]; + } + } + + return NullUniValue; +} + +std::vector UniValue::getKeys() const +{ + if (typ != VOBJ) + throw std::runtime_error("JSON value is not an object as expected"); + return keys; +} + +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(); +} + +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/lib/univalue_escapes.h b/lib/univalue_escapes.h new file mode 100644 index 000000000..4133b24ca --- /dev/null +++ b/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] = { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "\\b", + "\\t", + "\\n", + NULL, + "\\f", + "\\r", + 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, + 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/lib/univalue_read.cpp b/lib/univalue_read.cpp new file mode 100644 index 000000000..64591234c --- /dev/null +++ b/lib/univalue_read.cpp @@ -0,0 +1,389 @@ +// 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 +#include +#include +#include "univalue.h" + +using namespace std; + +// 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 (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(string& tokenVal, unsigned int& consumed, + const char *raw) +{ + tokenVal.clear(); + consumed = 0; + + const char *rawStart = raw; + + while ((*raw) && (isspace(*raw))) // skip whitespace + raw++; + + switch (*raw) { + + case 0: + return JTOK_NONE; + + 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 + string numStr; + + const char *first = raw; + + const char *firstDigit = first; + if (!isdigit(*firstDigit)) + firstDigit++; + if ((*firstDigit == '0') && isdigit(firstDigit[1])) + return JTOK_ERR; + + numStr += *raw; // copy first char + raw++; + + if ((*first == '-') && (!isdigit(*raw))) + return JTOK_ERR; + + while ((*raw) && isdigit(*raw)) { // copy digits + numStr += *raw; + raw++; + } + + // part 2: frac + if (*raw == '.') { + numStr += *raw; // copy . + raw++; + + if (!isdigit(*raw)) + return JTOK_ERR; + while ((*raw) && isdigit(*raw)) { // copy digits + numStr += *raw; + raw++; + } + } + + // part 3: exp + if (*raw == 'e' || *raw == 'E') { + numStr += *raw; // copy E + raw++; + + if (*raw == '-' || *raw == '+') { // copy +/- + numStr += *raw; + raw++; + } + + if (!isdigit(*raw)) + return JTOK_ERR; + while ((*raw) && isdigit(*raw)) { // copy digits + numStr += *raw; + raw++; + } + } + + tokenVal = numStr; + consumed = (raw - rawStart); + return JTOK_NUMBER; + } + + case '"': { + raw++; // skip " + + string valStr; + + while (*raw) { + if (*raw < 0x20) + return JTOK_ERR; + + else if (*raw == '\\') { + raw++; // skip backslash + + switch (*raw) { + case '"': valStr += "\""; break; + case '\\': valStr += "\\"; break; + case '/': valStr += "/"; break; + case 'b': valStr += "\b"; break; + case 'f': valStr += "\f"; break; + case 'n': valStr += "\n"; break; + case 'r': valStr += "\r"; break; + case 't': valStr += "\t"; break; + + case 'u': { + unsigned int codepoint; + if (hatoui(raw + 1, raw + 1 + 4, codepoint) != + raw + 1 + 4) + return JTOK_ERR; + + if (codepoint <= 0x7f) + valStr.push_back((char)codepoint); + else if (codepoint <= 0x7FF) { + valStr.push_back((char)(0xC0 | (codepoint >> 6))); + valStr.push_back((char)(0x80 | (codepoint & 0x3F))); + } else if (codepoint <= 0xFFFF) { + valStr.push_back((char)(0xE0 | (codepoint >> 12))); + valStr.push_back((char)(0x80 | ((codepoint >> 6) & 0x3F))); + valStr.push_back((char)(0x80 | (codepoint & 0x3F))); + } + + raw += 4; + break; + } + default: + return JTOK_ERR; + + } + + raw++; // skip esc'd char + } + + else if (*raw == '"') { + raw++; // skip " + break; // stop scanning + } + + else { + valStr += *raw; + raw++; + } + } + + tokenVal = valStr; + consumed = (raw - rawStart); + return JTOK_STRING; + } + + default: + return JTOK_ERR; + } +} + +bool UniValue::read(const char *raw) +{ + clear(); + + bool expectName = false; + bool expectColon = false; + vector stack; + + string tokenVal; + unsigned int consumed; + enum jtokentype tok = JTOK_NONE; + enum jtokentype last_tok = JTOK_NONE; + do { + last_tok = tok; + + tok = getJsonToken(tokenVal, consumed, raw); + if (tok == JTOK_NONE || tok == JTOK_ERR) + return false; + raw += consumed; + + 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 (utyp == VOBJ) + expectName = true; + break; + } + + case JTOK_OBJ_CLOSE: + case JTOK_ARR_CLOSE: { + if (!stack.size() || expectColon || (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(); + expectName = false; + break; + } + + case JTOK_COLON: { + if (!stack.size() || expectName || !expectColon) + return false; + + UniValue *top = stack.back(); + if (top->getType() != VOBJ) + return false; + + expectColon = false; + break; + } + + case JTOK_COMMA: { + if (!stack.size() || expectName || expectColon || + (last_tok == JTOK_COMMA) || (last_tok == JTOK_ARR_OPEN)) + return false; + + UniValue *top = stack.back(); + if (top->getType() == VOBJ) + expectName = true; + break; + } + + case JTOK_KW_NULL: + case JTOK_KW_TRUE: + case JTOK_KW_FALSE: { + if (!stack.size() || expectName || expectColon) + return 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; + } + + UniValue *top = stack.back(); + top->values.push_back(tmpVal); + + break; + } + + case JTOK_NUMBER: { + if (!stack.size() || expectName || expectColon) + return false; + + UniValue tmpVal(VNUM, tokenVal); + UniValue *top = stack.back(); + top->values.push_back(tmpVal); + + break; + } + + case JTOK_STRING: { + if (!stack.size()) + return false; + + UniValue *top = stack.back(); + + if (expectName) { + top->keys.push_back(tokenVal); + expectName = false; + expectColon = true; + } else { + UniValue tmpVal(VSTR, tokenVal); + top->values.push_back(tmpVal); + } + + break; + } + + default: + return false; + } + } while (!stack.empty ()); + + /* Check that nothing follows the initial construct (parsed above). */ + tok = getJsonToken(tokenVal, consumed, raw); + if (tok != JTOK_NONE) + return false; + + return true; +} + diff --git a/lib/univalue_write.cpp b/lib/univalue_write.cpp new file mode 100644 index 000000000..bce3997af --- /dev/null +++ b/lib/univalue_write.cpp @@ -0,0 +1,127 @@ +// 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 +#include +#include +#include +#include "univalue.h" +#include "univalue_escapes.h" + +// TODO: Using UTF8 + +using namespace std; + +static string json_escape(const string& inS) +{ + 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 if (isprint(ch)) + outS += ch; + + else { + char tmpesc[16]; + sprintf(tmpesc, "\\u%04x", ch); + outS += tmpesc; + } + } + + return outS; +} + +string UniValue::write(unsigned int prettyIndent, + unsigned int indentLevel) const +{ + 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, string& s) +{ + s.append(prettyIndent * indentLevel, ' '); +} + +void UniValue::writeArray(unsigned int prettyIndent, unsigned int indentLevel, 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 += " "; + } + if (prettyIndent) + s += "\n"; + } + + if (prettyIndent) + indentStr(prettyIndent, indentLevel - 1, s); + s += "]"; +} + +void UniValue::writeObject(unsigned int prettyIndent, unsigned int indentLevel, 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[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/pc/libunivalue-uninstalled.pc.in b/pc/libunivalue-uninstalled.pc.in new file mode 100644 index 000000000..b7f53e875 --- /dev/null +++ b/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/pc/libunivalue.pc.in b/pc/libunivalue.pc.in new file mode 100644 index 000000000..358a2d5f7 --- /dev/null +++ b/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/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..e4dea0df7 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,7 @@ + +unitester + +*.log +*.trs + +.libs diff --git a/test/fail1.json b/test/fail1.json new file mode 100644 index 000000000..6216b865f --- /dev/null +++ b/test/fail1.json @@ -0,0 +1 @@ +"A JSON payload should be an object or array, not a string." \ No newline at end of file diff --git a/test/fail10.json b/test/fail10.json new file mode 100644 index 000000000..5d8c0047b --- /dev/null +++ b/test/fail10.json @@ -0,0 +1 @@ +{"Extra value after close": true} "misplaced quoted value" \ No newline at end of file diff --git a/test/fail11.json b/test/fail11.json new file mode 100644 index 000000000..76eb95b45 --- /dev/null +++ b/test/fail11.json @@ -0,0 +1 @@ +{"Illegal expression": 1 + 2} \ No newline at end of file diff --git a/test/fail12.json b/test/fail12.json new file mode 100644 index 000000000..77580a452 --- /dev/null +++ b/test/fail12.json @@ -0,0 +1 @@ +{"Illegal invocation": alert()} \ No newline at end of file diff --git a/test/fail13.json b/test/fail13.json new file mode 100644 index 000000000..379406b59 --- /dev/null +++ b/test/fail13.json @@ -0,0 +1 @@ +{"Numbers cannot have leading zeroes": 013} \ No newline at end of file diff --git a/test/fail14.json b/test/fail14.json new file mode 100644 index 000000000..0ed366b38 --- /dev/null +++ b/test/fail14.json @@ -0,0 +1 @@ +{"Numbers cannot be hex": 0x14} \ No newline at end of file diff --git a/test/fail15.json b/test/fail15.json new file mode 100644 index 000000000..fc8376b60 --- /dev/null +++ b/test/fail15.json @@ -0,0 +1 @@ +["Illegal backslash escape: \x15"] \ No newline at end of file diff --git a/test/fail16.json b/test/fail16.json new file mode 100644 index 000000000..3fe21d4b5 --- /dev/null +++ b/test/fail16.json @@ -0,0 +1 @@ +[\naked] \ No newline at end of file diff --git a/test/fail17.json b/test/fail17.json new file mode 100644 index 000000000..62b9214ae --- /dev/null +++ b/test/fail17.json @@ -0,0 +1 @@ +["Illegal backslash escape: \017"] \ No newline at end of file diff --git a/test/fail18.json b/test/fail18.json new file mode 100644 index 000000000..edac92716 --- /dev/null +++ b/test/fail18.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/fail19.json b/test/fail19.json new file mode 100644 index 000000000..3b9c46fa9 --- /dev/null +++ b/test/fail19.json @@ -0,0 +1 @@ +{"Missing colon" null} \ No newline at end of file diff --git a/test/fail2.json b/test/fail2.json new file mode 100644 index 000000000..6b7c11e5a --- /dev/null +++ b/test/fail2.json @@ -0,0 +1 @@ +["Unclosed array" \ No newline at end of file diff --git a/test/fail20.json b/test/fail20.json new file mode 100644 index 000000000..27c1af3e7 --- /dev/null +++ b/test/fail20.json @@ -0,0 +1 @@ +{"Double colon":: null} \ No newline at end of file diff --git a/test/fail21.json b/test/fail21.json new file mode 100644 index 000000000..62474573b --- /dev/null +++ b/test/fail21.json @@ -0,0 +1 @@ +{"Comma instead of colon", null} \ No newline at end of file diff --git a/test/fail22.json b/test/fail22.json new file mode 100644 index 000000000..a7752581b --- /dev/null +++ b/test/fail22.json @@ -0,0 +1 @@ +["Colon instead of comma": false] \ No newline at end of file diff --git a/test/fail23.json b/test/fail23.json new file mode 100644 index 000000000..494add1ca --- /dev/null +++ b/test/fail23.json @@ -0,0 +1 @@ +["Bad value", truth] \ No newline at end of file diff --git a/test/fail24.json b/test/fail24.json new file mode 100644 index 000000000..caff239bf --- /dev/null +++ b/test/fail24.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/test/fail25.json b/test/fail25.json new file mode 100644 index 000000000..8b7ad23e0 --- /dev/null +++ b/test/fail25.json @@ -0,0 +1 @@ +[" tab character in string "] \ No newline at end of file diff --git a/test/fail26.json b/test/fail26.json new file mode 100644 index 000000000..845d26a6a --- /dev/null +++ b/test/fail26.json @@ -0,0 +1 @@ +["tab\ character\ in\ string\ "] \ No newline at end of file diff --git a/test/fail27.json b/test/fail27.json new file mode 100644 index 000000000..6b01a2ca4 --- /dev/null +++ b/test/fail27.json @@ -0,0 +1,2 @@ +["line +break"] \ No newline at end of file diff --git a/test/fail28.json b/test/fail28.json new file mode 100644 index 000000000..621a0101c --- /dev/null +++ b/test/fail28.json @@ -0,0 +1,2 @@ +["line\ +break"] \ No newline at end of file diff --git a/test/fail29.json b/test/fail29.json new file mode 100644 index 000000000..47ec421bb --- /dev/null +++ b/test/fail29.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/test/fail3.json b/test/fail3.json new file mode 100644 index 000000000..168c81eb7 --- /dev/null +++ b/test/fail3.json @@ -0,0 +1 @@ +{unquoted_key: "keys must be quoted"} \ No newline at end of file diff --git a/test/fail30.json b/test/fail30.json new file mode 100644 index 000000000..8ab0bc4b8 --- /dev/null +++ b/test/fail30.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/test/fail31.json b/test/fail31.json new file mode 100644 index 000000000..1cce602b5 --- /dev/null +++ b/test/fail31.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/test/fail32.json b/test/fail32.json new file mode 100644 index 000000000..45cba7396 --- /dev/null +++ b/test/fail32.json @@ -0,0 +1 @@ +{"Comma instead if closing brace": true, \ No newline at end of file diff --git a/test/fail33.json b/test/fail33.json new file mode 100644 index 000000000..ca5eb19dc --- /dev/null +++ b/test/fail33.json @@ -0,0 +1 @@ +["mismatch"} \ No newline at end of file diff --git a/test/fail34.json b/test/fail34.json new file mode 100644 index 000000000..3f8be1728 --- /dev/null +++ b/test/fail34.json @@ -0,0 +1 @@ +{} garbage \ No newline at end of file diff --git a/test/fail4.json b/test/fail4.json new file mode 100644 index 000000000..9de168bf3 --- /dev/null +++ b/test/fail4.json @@ -0,0 +1 @@ +["extra comma",] \ No newline at end of file diff --git a/test/fail5.json b/test/fail5.json new file mode 100644 index 000000000..ddf3ce3d2 --- /dev/null +++ b/test/fail5.json @@ -0,0 +1 @@ +["double extra comma",,] \ No newline at end of file diff --git a/test/fail6.json b/test/fail6.json new file mode 100644 index 000000000..ed91580e1 --- /dev/null +++ b/test/fail6.json @@ -0,0 +1 @@ +[ , "<-- missing value"] \ No newline at end of file diff --git a/test/fail7.json b/test/fail7.json new file mode 100644 index 000000000..8a96af3e4 --- /dev/null +++ b/test/fail7.json @@ -0,0 +1 @@ +["Comma after the close"], \ No newline at end of file diff --git a/test/fail8.json b/test/fail8.json new file mode 100644 index 000000000..b28479c6e --- /dev/null +++ b/test/fail8.json @@ -0,0 +1 @@ +["Extra close"]] \ No newline at end of file diff --git a/test/fail9.json b/test/fail9.json new file mode 100644 index 000000000..5815574f3 --- /dev/null +++ b/test/fail9.json @@ -0,0 +1 @@ +{"Extra comma": true,} \ No newline at end of file diff --git a/test/pass1.json b/test/pass1.json new file mode 100644 index 000000000..70e268543 --- /dev/null +++ b/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/test/pass2.json b/test/pass2.json new file mode 100644 index 000000000..d3c63c7ad --- /dev/null +++ b/test/pass2.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/pass3.json b/test/pass3.json new file mode 100644 index 000000000..4528d51f1 --- /dev/null +++ b/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/test/unitester.cpp b/test/unitester.cpp new file mode 100644 index 000000000..835556e03 --- /dev/null +++ b/test/unitester.cpp @@ -0,0 +1,115 @@ +// 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 +#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 + +using namespace std; +string srcdir(JSON_TEST_SRC); + +static void runtest(string filename, const string& jdata) +{ + fprintf(stderr, "test %s\n", filename.c_str()); + + string prefix = filename.substr(0, 4); + + bool wantPass = (prefix == "pass"); + bool wantFail = (prefix == "fail"); + assert(wantPass || wantFail); + + UniValue val; + bool testResult = val.read(jdata); + + if (wantPass) { + assert(testResult == true); + } else { + assert(testResult == false); + } +} + +static void runtest_file(const char *filename_) +{ + string basename(filename_); + string filename = srcdir + "/" + basename; + FILE *f = fopen(filename.c_str(), "r"); + assert(f != NULL); + + string jdata; + + char buf[4096]; + while (!feof(f)) { + int bread = fread(buf, 1, sizeof(buf), f); + assert(!ferror(f)); + + 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", + "fail3.json", + "fail4.json", // extra comma + "fail5.json", + "fail6.json", + "fail7.json", + "fail8.json", + "fail9.json", // extra comma + "pass1.json", + "pass2.json", + "pass3.json", +}; + +int main (int argc, char *argv[]) +{ + for (unsigned int fidx = 0; fidx < ARRAY_SIZE(filenames); fidx++) { + runtest_file(filenames[fidx]); + } + + return 0; +} +