diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 8060b93bc..ace8bc300 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -1,525 +1,523 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-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 "rpc/server.h" #include "base58.h" #include "config.h" #include "init.h" #include "random.h" #include "sync.h" #include "ui_interface.h" #include "util.h" #include "utilstrencodings.h" #include #include // for to_upper() #include #include #include #include #include // for unique_ptr #include #include -using namespace RPCServer; - static bool fRPCRunning = false; static bool fRPCInWarmup = true; static std::string rpcWarmupStatus("RPC server started"); static CCriticalSection cs_rpcWarmup; /* Timer-creating functions */ static RPCTimerInterface *timerInterface = nullptr; /* Map of name to timer. */ static std::map> deadlineTimers; static struct CRPCSignals { boost::signals2::signal Started; boost::signals2::signal Stopped; boost::signals2::signal PreCommand; boost::signals2::signal PostCommand; } g_rpcSignals; void RPCServer::OnStarted(std::function slot) { g_rpcSignals.Started.connect(slot); } void RPCServer::OnStopped(std::function slot) { g_rpcSignals.Stopped.connect(slot); } void RPCServer::OnPreCommand(std::function slot) { g_rpcSignals.PreCommand.connect(boost::bind(slot, _1)); } void RPCServer::OnPostCommand(std::function slot) { g_rpcSignals.PostCommand.connect(boost::bind(slot, _1)); } void RPCTypeCheck(const UniValue ¶ms, const std::list &typesExpected, bool fAllowNull) { unsigned int i = 0; for (UniValue::VType t : typesExpected) { if (params.size() <= i) break; const UniValue &v = params[i]; if (!(fAllowNull && v.isNull())) { RPCTypeCheckArgument(v, t); } i++; } } void RPCTypeCheckArgument(const UniValue &value, UniValue::VType typeExpected) { if (value.type() != typeExpected) { throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected), uvTypeName(value.type()))); } } void RPCTypeCheckObj(const UniValue &o, const std::map &typesExpected, bool fAllowNull, bool fStrict) { for (const auto &t : typesExpected) { const UniValue &v = find_value(o, t.first); if (!fAllowNull && v.isNull()) throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first)); if (!(t.second.typeAny || v.type() == t.second.type || (fAllowNull && v.isNull()))) { std::string err = strprintf("Expected type %s for %s, got %s", uvTypeName(t.second.type), t.first, uvTypeName(v.type())); throw JSONRPCError(RPC_TYPE_ERROR, err); } } if (fStrict) { for (const std::string &k : o.getKeys()) { if (typesExpected.count(k) == 0) { std::string err = strprintf("Unexpected key %s", k); throw JSONRPCError(RPC_TYPE_ERROR, err); } } } } Amount AmountFromValue(const UniValue &value) { if (!value.isNum() && !value.isStr()) throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number or string"); int64_t n; if (!ParseFixedPoint(value.getValStr(), 8, &n)) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); Amount amt(n); if (!MoneyRange(amt)) throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range"); return amt; } UniValue ValueFromAmount(const Amount &amount) { int64_t amt = amount.GetSatoshis(); bool sign = amt < 0; int64_t n_abs = (sign ? -amt : amt); int64_t quotient = n_abs / COIN.GetSatoshis(); int64_t remainder = n_abs % COIN.GetSatoshis(); return UniValue(UniValue::VNUM, strprintf("%s%d.%08d", sign ? "-" : "", quotient, remainder)); } uint256 ParseHashV(const UniValue &v, std::string strName) { std::string strHex; if (v.isStr()) strHex = v.get_str(); // Note: IsHex("") is false if (!IsHex(strHex)) throw JSONRPCError(RPC_INVALID_PARAMETER, strName + " must be hexadecimal string (not '" + strHex + "')"); if (64 != strHex.length()) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be of length %d (not %d)", strName, 64, strHex.length())); uint256 result; result.SetHex(strHex); return result; } uint256 ParseHashO(const UniValue &o, std::string strKey) { return ParseHashV(find_value(o, strKey), strKey); } std::vector ParseHexV(const UniValue &v, std::string strName) { std::string strHex; if (v.isStr()) strHex = v.get_str(); if (!IsHex(strHex)) throw JSONRPCError(RPC_INVALID_PARAMETER, strName + " must be hexadecimal string (not '" + strHex + "')"); return ParseHex(strHex); } std::vector ParseHexO(const UniValue &o, std::string strKey) { return ParseHexV(find_value(o, strKey), strKey); } /** * Note: This interface may still be subject to change. */ std::string CRPCTable::help(Config &config, const std::string &strCommand) const { std::string strRet; std::string category; std::set setDone; std::vector> vCommands; for (std::map::const_iterator mi = mapCommands.begin(); mi != mapCommands.end(); ++mi) vCommands.push_back( std::make_pair(mi->second->category + mi->first, mi->second)); sort(vCommands.begin(), vCommands.end()); for (const std::pair &command : vCommands) { const CRPCCommand *pcmd = command.second; std::string strMethod = pcmd->name; // We already filter duplicates, but these deprecated screw up the sort // order if (strMethod.find("label") != std::string::npos) continue; if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand) continue; try { JSONRPCRequest jreq; jreq.fHelp = true; rpcfn_type pfn = pcmd->actor; if (setDone.insert(pfn).second) pfn(config, jreq); } catch (const std::exception &e) { // Help text is returned in an exception std::string strHelp = std::string(e.what()); if (strCommand == "") { if (strHelp.find('\n') != std::string::npos) strHelp = strHelp.substr(0, strHelp.find('\n')); if (category != pcmd->category) { if (!category.empty()) strRet += "\n"; category = pcmd->category; std::string firstLetter = category.substr(0, 1); boost::to_upper(firstLetter); strRet += "== " + firstLetter + category.substr(1) + " ==\n"; } } strRet += strHelp + "\n"; } } if (strRet == "") strRet = strprintf("help: unknown command: %s\n", strCommand); strRet = strRet.substr(0, strRet.size() - 1); return strRet; } static UniValue help(Config &config, const JSONRPCRequest &jsonRequest) { if (jsonRequest.fHelp || jsonRequest.params.size() > 1) throw std::runtime_error( "help ( \"command\" )\n" "\nList all commands, or get help for a specified command.\n" "\nArguments:\n" "1. \"command\" (string, optional) The command to get help on\n" "\nResult:\n" "\"text\" (string) The help text\n"); std::string strCommand; if (jsonRequest.params.size() > 0) strCommand = jsonRequest.params[0].get_str(); return tableRPC.help(config, strCommand); } static UniValue stop(const Config &config, const JSONRPCRequest &jsonRequest) { // Accept the deprecated and ignored 'detach' boolean argument if (jsonRequest.fHelp || jsonRequest.params.size() > 1) throw std::runtime_error("stop\n" "\nStop Bitcoin server."); // Event loop will exit after current HTTP requests have been handled, so // this reply will get back to the client. StartShutdown(); return "Bitcoin server stopping"; } /** * Call Table */ // clang-format off static const CRPCCommand vRPCCommands[] = { // category name actor (function) okSafe argNames // ------------------- ------------------------ ---------------------- ------ ---------- /* Overall control/query calls */ { "control", "help", help, true, {"command"} }, { "control", "stop", stop, true, {} }, }; // clang-format on CRPCTable::CRPCTable() { unsigned int vcidx; for (vcidx = 0; vcidx < (sizeof(vRPCCommands) / sizeof(vRPCCommands[0])); vcidx++) { const CRPCCommand *pcmd; pcmd = &vRPCCommands[vcidx]; mapCommands[pcmd->name] = pcmd; } } const CRPCCommand *CRPCTable::operator[](const std::string &name) const { std::map::const_iterator it = mapCommands.find(name); if (it == mapCommands.end()) return nullptr; return (*it).second; } bool CRPCTable::appendCommand(const std::string &name, const CRPCCommand *pcmd) { if (IsRPCRunning()) return false; // don't allow overwriting for now std::map::const_iterator it = mapCommands.find(name); if (it != mapCommands.end()) return false; mapCommands[name] = pcmd; return true; } bool StartRPC() { LogPrint("rpc", "Starting RPC\n"); fRPCRunning = true; g_rpcSignals.Started(); return true; } void InterruptRPC() { LogPrint("rpc", "Interrupting RPC\n"); // Interrupt e.g. running longpolls fRPCRunning = false; } void StopRPC() { LogPrint("rpc", "Stopping RPC\n"); deadlineTimers.clear(); DeleteAuthCookie(); g_rpcSignals.Stopped(); } bool IsRPCRunning() { return fRPCRunning; } void SetRPCWarmupStatus(const std::string &newStatus) { LOCK(cs_rpcWarmup); rpcWarmupStatus = newStatus; } void SetRPCWarmupFinished() { LOCK(cs_rpcWarmup); assert(fRPCInWarmup); fRPCInWarmup = false; } bool RPCIsInWarmup(std::string *outStatus) { LOCK(cs_rpcWarmup); if (outStatus) *outStatus = rpcWarmupStatus; return fRPCInWarmup; } void JSONRPCRequest::parse(const UniValue &valRequest) { // Parse request if (!valRequest.isObject()) throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); const UniValue &request = valRequest.get_obj(); // Parse id now so errors from here on will have the id id = find_value(request, "id"); // Parse method UniValue valMethod = find_value(request, "method"); if (valMethod.isNull()) throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); if (!valMethod.isStr()) throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); strMethod = valMethod.get_str(); if (strMethod != "getblocktemplate") LogPrint("rpc", "ThreadRPCServer method=%s\n", SanitizeString(strMethod)); // Parse params UniValue valParams = find_value(request, "params"); if (valParams.isArray() || valParams.isObject()) params = valParams; else if (valParams.isNull()) params = UniValue(UniValue::VARR); else throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object"); } static UniValue JSONRPCExecOne(Config &config, const UniValue &req) { UniValue rpc_result(UniValue::VOBJ); JSONRPCRequest jreq; try { jreq.parse(req); UniValue result = tableRPC.execute(config, jreq); rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id); } catch (const UniValue &objError) { rpc_result = JSONRPCReplyObj(NullUniValue, objError, jreq.id); } catch (const std::exception &e) { rpc_result = JSONRPCReplyObj( NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); } return rpc_result; } std::string JSONRPCExecBatch(Config &config, const UniValue &vReq) { UniValue ret(UniValue::VARR); for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++) { ret.push_back(JSONRPCExecOne(config, vReq[reqIdx])); } return ret.write() + "\n"; } /** * Process named arguments into a vector of positional arguments, based on the * passed-in specification for the RPC call's arguments. */ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest &in, const std::vector &argNames) { JSONRPCRequest out = in; out.params = UniValue(UniValue::VARR); // Build a map of parameters, and remove ones that have been processed, so // that we can throw a focused error if there is an unknown one. const std::vector &keys = in.params.getKeys(); const std::vector &values = in.params.getValues(); std::unordered_map argsIn; for (size_t i = 0; i < keys.size(); ++i) { argsIn[keys[i]] = &values[i]; } // Process expected parameters. int hole = 0; for (const std::string &argName : argNames) { auto fr = argsIn.find(argName); if (fr != argsIn.end()) { for (int i = 0; i < hole; ++i) { // Fill hole between specified parameters with JSON nulls, but // not at the end (for backwards compatibility with calls that // act based on number of specified parameters). out.params.push_back(UniValue()); } hole = 0; out.params.push_back(*fr->second); argsIn.erase(fr); } else { hole += 1; } } // If there are still arguments in the argsIn map, this is an error. if (!argsIn.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first); } // Return request with named arguments transformed to positional arguments return out; } UniValue CRPCTable::execute(Config &config, const JSONRPCRequest &request) const { // Return immediately if in warmup { LOCK(cs_rpcWarmup); if (fRPCInWarmup) throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); } // Find method const CRPCCommand *pcmd = tableRPC[request.strMethod]; if (!pcmd) throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); g_rpcSignals.PreCommand(*pcmd); try { // Execute, convert arguments to array if necessary if (request.params.isObject()) { return pcmd->actor( config, transformNamedArguments(request, pcmd->argNames)); } else { return pcmd->actor(config, request); } } catch (const std::exception &e) { throw JSONRPCError(RPC_MISC_ERROR, e.what()); } g_rpcSignals.PostCommand(*pcmd); } std::vector CRPCTable::listCommands() const { std::vector commandList; typedef std::map commandMap; std::transform(mapCommands.begin(), mapCommands.end(), std::back_inserter(commandList), boost::bind(&commandMap::value_type::first, _1)); return commandList; } std::string HelpExampleCli(const std::string &methodname, const std::string &args) { return "> bitcoin-cli " + methodname + " " + args + "\n"; } std::string HelpExampleRpc(const std::string &methodname, const std::string &args) { return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", " "\"id\":\"curltest\", " "\"method\": \"" + methodname + "\", \"params\": [" + args + "] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"; } void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface) { if (!timerInterface) timerInterface = iface; } void RPCSetTimerInterface(RPCTimerInterface *iface) { timerInterface = iface; } void RPCUnsetTimerInterface(RPCTimerInterface *iface) { if (timerInterface == iface) timerInterface = nullptr; } void RPCRunLater(const std::string &name, std::function func, int64_t nSeconds) { if (!timerInterface) throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC"); deadlineTimers.erase(name); LogPrint("rpc", "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name()); deadlineTimers.emplace( name, std::unique_ptr( timerInterface->NewTimer(func, nSeconds * 1000))); } int RPCSerializationFlags() { return 0; } CRPCTable tableRPC; diff --git a/src/univalue/.travis.yml b/src/univalue/.travis.yml index 132743d34..43a1ed362 100644 --- a/src/univalue/.travis.yml +++ b/src/univalue/.travis.yml @@ -1,52 +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 [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew uninstall libtool; brew install libtool; fi - 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/Makefile.am b/src/univalue/Makefile.am index 6c1ec81e6..e283fc890 100644 --- a/src/univalue/Makefile.am +++ b/src/univalue/Makefile.am @@ -1,93 +1,116 @@ 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/unitester +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) +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)/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)/round1.json \ - $(TEST_DATA_DIR)/round2.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 b/src/univalue/README deleted file mode 100644 index 48167b083..000000000 --- a/src/univalue/README +++ /dev/null @@ -1,7 +0,0 @@ - - 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/src/univalue/README.md b/src/univalue/README.md new file mode 100644 index 000000000..36aa786a4 --- /dev/null +++ b/src/univalue/README.md @@ -0,0 +1,32 @@ + +# 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). + +## Installation + +This project is a standard GNU +[autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html) +project. Build and install instructions are available in the `INSTALL` +file provided with GNU autotools. + +``` +$ ./autogen.sh +$ ./configure +$ make +``` + +## Design + +UniValue provides a single dynamic RAII C++ object class, +and minimizes template use (contra json_spirit). + diff --git a/src/univalue/configure.ac b/src/univalue/configure.ac index 93d3ba945..8298332ac 100644 --- a/src/univalue/configure.ac +++ b/src/univalue/configure.ac @@ -1,69 +1,69 @@ m4_define([libunivalue_major_version], [1]) m4_define([libunivalue_minor_version], [1]) -m4_define([libunivalue_micro_version], [2]) -m4_define([libunivalue_interface_age], [2]) +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.2], +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 index 17f361941..85fe20924 100644 --- a/src/univalue/gen/gen.cpp +++ b/src/univalue/gen/gen.cpp @@ -1,84 +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 #include #include "univalue.h" -using namespace std; - 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 index e8ce28351..4fd2223b3 100644 --- a/src/univalue/include/univalue.h +++ b/src/univalue/include/univalue.h @@ -1,283 +1,303 @@ // 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 #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); + void getObjMap(std::map& kv) const; + bool checkObject(const std::map& memberTypes) const; 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); } + const UniValue& operator[](size_t index) const; + bool exists(const std::string& key) const { size_t i; return findKey(key, i); } bool isNull() const { return (typ == VNULL); } bool isTrue() const { return (typ == VBOOL) && (val == "1"); } bool isFalse() const { return (typ == VBOOL) && (val != "1"); } bool isBool() const { return (typ == VBOOL); } bool isStr() const { return (typ == VSTR); } bool isNum() const { return (typ == VNUM); } bool isArray() const { return (typ == VARR); } bool isObject() const { return (typ == VOBJ); } bool push_back(const UniValue& val); bool push_back(const std::string& val_) { UniValue tmpVal(VSTR, val_); return push_back(tmpVal); } bool push_back(const char *val_) { std::string s(val_); return push_back(s); } + bool push_back(uint64_t val_) { + UniValue tmpVal(val_); + return push_back(tmpVal); + } + bool push_back(int64_t val_) { + UniValue tmpVal(val_); + return push_back(tmpVal); + } + bool push_back(int val_) { + UniValue tmpVal(val_); + return push_back(tmpVal); + } + bool push_back(double val_) { + UniValue tmpVal(val_); + return push_back(tmpVal); + } bool push_backV(const std::vector& vec); + void __pushKV(const std::string& key, const UniValue& val); bool pushKV(const std::string& key, const UniValue& val); bool pushKV(const std::string& key, const std::string& val_) { UniValue tmpVal(VSTR, val_); return pushKV(key, tmpVal); } bool pushKV(const std::string& key, const char *val_) { std::string _val(val_); return pushKV(key, _val); } bool pushKV(const std::string& key, int64_t val_) { UniValue tmpVal(val_); return pushKV(key, tmpVal); } bool pushKV(const std::string& key, uint64_t val_) { UniValue tmpVal(val_); return pushKV(key, tmpVal); } bool pushKV(const std::string& key, 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 char *raw, size_t len); + bool read(const char *raw) { return read(raw, strlen(raw)); } bool read(const std::string& rawStr) { - return read(rawStr.c_str()); + return read(rawStr.data(), rawStr.size()); } private: UniValue::VType typ; std::string val; // numbers are stored as C++ strings std::vector keys; std::vector values; - int findKey(const std::string& key) const; + bool findKey(const std::string& key, size_t& retIdx) const; void writeArray(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; void writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; public: // Strict type-specific getters, these throw std::runtime_error if the // value is of unexpected type const std::vector& getKeys() const; const std::vector& getValues() const; bool get_bool() const; const std::string& get_str() const; int get_int() const; int64_t get_int64() const; double get_real() const; const UniValue& get_obj() const; const UniValue& get_array() const; enum VType type() const { return getType(); } bool push_back(std::pair pear) { return pushKV(pear.first, pear.second); } friend const UniValue& find_value( const UniValue& obj, const std::string& name); }; // // The following were added for compatibility with json_spirit. // Most duplicate other methods, and should be removed. // static inline std::pair Pair(const char *cKey, const char *cVal) { std::string key(cKey); UniValue uVal(cVal); return std::make_pair(key, uVal); } static inline std::pair Pair(const char *cKey, std::string strVal) { std::string key(cKey); UniValue uVal(strVal); return std::make_pair(key, uVal); } static inline std::pair Pair(const char *cKey, uint64_t u64Val) { std::string key(cKey); UniValue uVal(u64Val); return std::make_pair(key, uVal); } static inline std::pair Pair(const char *cKey, int64_t i64Val) { std::string key(cKey); UniValue uVal(i64Val); return std::make_pair(key, uVal); } static inline std::pair Pair(const char *cKey, bool iVal) { std::string key(cKey); UniValue uVal(iVal); return std::make_pair(key, uVal); } static inline std::pair Pair(const char *cKey, int iVal) { std::string key(cKey); UniValue uVal(iVal); return std::make_pair(key, uVal); } static inline std::pair Pair(const char *cKey, double dVal) { std::string key(cKey); UniValue uVal(dVal); return std::make_pair(key, uVal); } static inline std::pair Pair(const char *cKey, const UniValue& uVal) { std::string key(cKey); return std::make_pair(key, uVal); } static inline std::pair Pair(std::string key, const UniValue& uVal) { return std::make_pair(key, uVal); } enum jtokentype { JTOK_ERR = -1, JTOK_NONE = 0, // eof JTOK_OBJ_OPEN, JTOK_OBJ_CLOSE, JTOK_ARR_OPEN, JTOK_ARR_CLOSE, JTOK_COLON, JTOK_COMMA, JTOK_KW_NULL, JTOK_KW_TRUE, JTOK_KW_FALSE, JTOK_NUMBER, JTOK_STRING, }; extern enum jtokentype getJsonToken(std::string& tokenVal, - unsigned int& consumed, const char *raw); + unsigned int& consumed, const char *raw, const char *end); extern const char *uvTypeName(UniValue::VType t); static inline bool jsonTokenIsValue(enum jtokentype jtt) { switch (jtt) { case JTOK_KW_NULL: case JTOK_KW_TRUE: case JTOK_KW_FALSE: case JTOK_NUMBER: case JTOK_STRING: return true; default: return false; } // not reached } static inline bool json_isspace(int ch) { switch (ch) { case 0x20: case 0x09: case 0x0a: case 0x0d: return true; default: return false; } // not reached } extern const UniValue NullUniValue; const UniValue& find_value( const UniValue& obj, const std::string& name); #endif // __UNIVALUE_H__ diff --git a/src/univalue/lib/univalue.cpp b/src/univalue/lib/univalue.cpp index 5a2860c13..4c9c15d63 100644 --- a/src/univalue/lib/univalue.cpp +++ b/src/univalue/lib/univalue.cpp @@ -1,357 +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 -#include #include -#include #include -#include #include -#include #include "univalue.h" -namespace -{ -static bool ParsePrechecks(const std::string& str) -{ - if (str.empty()) // No empty string allowed - return false; - if (str.size() >= 1 && (json_isspace(str[0]) || json_isspace(str[str.size()-1]))) // No padding allowed - return false; - if (str.size() != strlen(str.c_str())) // No embedded NUL characters allowed - return false; - return true; -} - -bool ParseInt32(const std::string& str, int32_t *out) -{ - if (!ParsePrechecks(str)) - return false; - char *endp = NULL; - errno = 0; // strtol will not set errno if valid - long int n = strtol(str.c_str(), &endp, 10); - if(out) *out = (int32_t)n; - // Note that strtol returns a *long int*, so even if strtol doesn't report a over/underflow - // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit - // platforms the size of these types may be different. - return endp && *endp == 0 && !errno && - n >= std::numeric_limits::min() && - n <= std::numeric_limits::max(); -} - -bool ParseInt64(const std::string& str, int64_t *out) -{ - if (!ParsePrechecks(str)) - return false; - char *endp = NULL; - errno = 0; // strtoll will not set errno if valid - long long int n = strtoll(str.c_str(), &endp, 10); - if(out) *out = (int64_t)n; - // Note that strtoll returns a *long long int*, so even if strtol doesn't report a over/underflow - // we still have to check that the returned value is within the range of an *int64_t*. - return endp && *endp == 0 && !errno && - n >= std::numeric_limits::min() && - n <= std::numeric_limits::max(); -} - -bool ParseDouble(const std::string& str, double *out) -{ - if (!ParsePrechecks(str)) - return false; - if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') // No hexadecimal floats allowed - return false; - std::istringstream text(str); - text.imbue(std::locale::classic()); - double result; - text >> result; - if(out) *out = result; - return text.eof() && !text.fail(); -} -} - -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) +static bool validNumStr(const std::string& s) { - string tokenVal; + std::string tokenVal; unsigned int consumed; - enum jtokentype tt = getJsonToken(tokenVal, consumed, s.c_str()); + enum jtokentype tt = getJsonToken(tokenVal, consumed, s.data(), s.data() + s.size()); return (tt == JTOK_NUMBER); } -bool UniValue::setNumStr(const string& val_) +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_) { - ostringstream oss; + std::ostringstream oss; oss << val_; return setNumStr(oss.str()); } bool UniValue::setInt(int64_t val_) { - ostringstream oss; + std::ostringstream oss; oss << val_; return setNumStr(oss.str()); } bool UniValue::setFloat(double val_) { - ostringstream oss; + std::ostringstream oss; oss << std::setprecision(16) << val_; bool ret = setNumStr(oss.str()); typ = VNUM; return ret; } -bool UniValue::setStr(const string& val_) +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& 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; - keys.push_back(key); - values.push_back(val_); + 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 (unsigned int i = 0; i < obj.keys.size(); i++) { - keys.push_back(obj.keys[i]); - values.push_back(obj.values.at(i)); - } + for (size_t i = 0; i < obj.keys.size(); i++) + __pushKV(obj.keys[i], obj.values.at(i)); return true; } -int UniValue::findKey(const std::string& key) const +void UniValue::getObjMap(std::map& 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 (unsigned int i = 0; i < keys.size(); i++) { - if (keys[i] == key) - return (int) i; + for (size_t i = 0; i < keys.size(); i++) { + if (keys[i] == key) { + retIdx = i; + return true; + } } - return -1; + return false; } -bool UniValue::checkObject(const std::map& t) +bool UniValue::checkObject(const std::map& t) const { + if (typ != VOBJ) + return false; + for (std::map::const_iterator it = t.begin(); it != t.end(); ++it) { - int idx = findKey(it->first); - if (idx < 0) + 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; - int index = findKey(key); - if (index < 0) + size_t index = 0; + if (!findKey(key, index)) return NullUniValue; return values.at(index); } -const UniValue& UniValue::operator[](unsigned int index) const +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; } -const std::vector& UniValue::getKeys() const -{ - if (typ != VOBJ) - throw std::runtime_error("JSON value is not an object as expected"); - return keys; -} - -const std::vector& UniValue::getValues() const -{ - if (typ != VOBJ && typ != VARR) - throw std::runtime_error("JSON value is not an object or array as expected"); - return values; -} - -bool UniValue::get_bool() const -{ - if (typ != VBOOL) - throw std::runtime_error("JSON value is not a boolean as expected"); - return getBool(); -} - -const std::string& UniValue::get_str() const -{ - if (typ != VSTR) - throw std::runtime_error("JSON value is not a string as expected"); - return getValStr(); -} - -int UniValue::get_int() const -{ - if (typ != VNUM) - throw std::runtime_error("JSON value is not an integer as expected"); - int32_t retval; - if (!ParseInt32(getValStr(), &retval)) - throw std::runtime_error("JSON integer out of range"); - return retval; -} - -int64_t UniValue::get_int64() const -{ - if (typ != VNUM) - throw std::runtime_error("JSON value is not an integer as expected"); - int64_t retval; - if (!ParseInt64(getValStr(), &retval)) - throw std::runtime_error("JSON integer out of range"); - return retval; -} - -double UniValue::get_real() const -{ - if (typ != VNUM) - throw std::runtime_error("JSON value is not a number as expected"); - double retval; - if (!ParseDouble(getValStr(), &retval)) - throw std::runtime_error("JSON double out of range"); - return retval; -} - -const UniValue& UniValue::get_obj() const -{ - if (typ != VOBJ) - throw std::runtime_error("JSON value is not an object as expected"); - return *this; -} - -const UniValue& UniValue::get_array() const -{ - if (typ != VARR) - throw std::runtime_error("JSON value is not an array as expected"); - return *this; -} - diff --git a/src/univalue/lib/univalue.cpp b/src/univalue/lib/univalue_get.cpp similarity index 53% copy from src/univalue/lib/univalue.cpp copy to src/univalue/lib/univalue_get.cpp index 5a2860c13..eabcf2dad 100644 --- a/src/univalue/lib/univalue.cpp +++ b/src/univalue/lib/univalue_get.cpp @@ -1,357 +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 #include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include "univalue.h" -namespace +namespace { static bool ParsePrechecks(const std::string& str) { if (str.empty()) // No empty string allowed return false; if (str.size() >= 1 && (json_isspace(str[0]) || json_isspace(str[str.size()-1]))) // No padding allowed return false; if (str.size() != strlen(str.c_str())) // No embedded NUL characters allowed return false; return true; } bool ParseInt32(const std::string& str, int32_t *out) { if (!ParsePrechecks(str)) return false; char *endp = NULL; errno = 0; // strtol will not set errno if valid long int n = strtol(str.c_str(), &endp, 10); if(out) *out = (int32_t)n; // Note that strtol returns a *long int*, so even if strtol doesn't report a over/underflow // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit // platforms the size of these types may be different. return endp && *endp == 0 && !errno && n >= std::numeric_limits::min() && n <= std::numeric_limits::max(); } bool ParseInt64(const std::string& str, int64_t *out) { if (!ParsePrechecks(str)) return false; char *endp = NULL; errno = 0; // strtoll will not set errno if valid long long int n = strtoll(str.c_str(), &endp, 10); if(out) *out = (int64_t)n; // Note that strtoll returns a *long long int*, so even if strtol doesn't report a over/underflow // we still have to check that the returned value is within the range of an *int64_t*. return endp && *endp == 0 && !errno && n >= std::numeric_limits::min() && n <= std::numeric_limits::max(); } bool ParseDouble(const std::string& str, double *out) { if (!ParsePrechecks(str)) return false; if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') // No hexadecimal floats allowed return false; std::istringstream text(str); text.imbue(std::locale::classic()); double result; text >> result; if(out) *out = result; return text.eof() && !text.fail(); } } -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_) -{ - ostringstream oss; - - oss << val_; - - return setNumStr(oss.str()); -} - -bool UniValue::setInt(int64_t val_) -{ - ostringstream oss; - - oss << val_; - - return setNumStr(oss.str()); -} - -bool UniValue::setFloat(double val_) -{ - 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.at(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.at(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.at(index); -} - -const UniValue& UniValue::operator[](unsigned int 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; -} - const std::vector& UniValue::getKeys() const { if (typ != VOBJ) throw std::runtime_error("JSON value is not an object as expected"); return keys; } const std::vector& UniValue::getValues() const { if (typ != VOBJ && typ != VARR) throw std::runtime_error("JSON value is not an object or array as expected"); return values; } bool UniValue::get_bool() const { if (typ != VBOOL) throw std::runtime_error("JSON value is not a boolean as expected"); return getBool(); } const std::string& UniValue::get_str() const { if (typ != VSTR) throw std::runtime_error("JSON value is not a string as expected"); return getValStr(); } int UniValue::get_int() const { if (typ != VNUM) throw std::runtime_error("JSON value is not an integer as expected"); int32_t retval; if (!ParseInt32(getValStr(), &retval)) throw std::runtime_error("JSON integer out of range"); return retval; } int64_t UniValue::get_int64() const { if (typ != VNUM) throw std::runtime_error("JSON value is not an integer as expected"); int64_t retval; if (!ParseInt64(getValStr(), &retval)) throw std::runtime_error("JSON integer out of range"); return retval; } double UniValue::get_real() const { if (typ != VNUM) throw std::runtime_error("JSON value is not a number as expected"); double retval; if (!ParseDouble(getValStr(), &retval)) throw std::runtime_error("JSON double out of range"); return retval; } const UniValue& UniValue::get_obj() const { if (typ != VOBJ) throw std::runtime_error("JSON value is not an object as expected"); return *this; } const UniValue& UniValue::get_array() const { if (typ != VARR) throw std::runtime_error("JSON value is not an array as expected"); return *this; } diff --git a/src/univalue/lib/univalue_read.cpp b/src/univalue/lib/univalue_read.cpp index 95bac6958..14834db24 100644 --- a/src/univalue/lib/univalue_read.cpp +++ b/src/univalue/lib/univalue_read.cpp @@ -1,441 +1,449 @@ // 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" #include "univalue_utffilter.h" -using namespace std; - 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(string& tokenVal, unsigned int& consumed, - const char *raw) +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) && (json_isspace(*raw))) // skip whitespace + while (raw < end && (json_isspace(*raw))) // skip whitespace raw++; - switch (*raw) { - - case 0: + 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 - string numStr; + 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 == '-') && (!json_isdigit(*raw))) + if ((*first == '-') && (raw < end) && (!json_isdigit(*raw))) return JTOK_ERR; - while ((*raw) && json_isdigit(*raw)) { // copy digits + while (raw < end && json_isdigit(*raw)) { // copy digits numStr += *raw; raw++; } // part 2: frac - if (*raw == '.') { + if (raw < end && *raw == '.') { numStr += *raw; // copy . raw++; - if (!json_isdigit(*raw)) + if (raw >= end || !json_isdigit(*raw)) return JTOK_ERR; - while ((*raw) && json_isdigit(*raw)) { // copy digits + while (raw < end && json_isdigit(*raw)) { // copy digits numStr += *raw; raw++; } } // part 3: exp - if (*raw == 'e' || *raw == 'E') { + if (raw < end && (*raw == 'e' || *raw == 'E')) { numStr += *raw; // copy E raw++; - if (*raw == '-' || *raw == '+') { // copy +/- + if (raw < end && (*raw == '-' || *raw == '+')) { // copy +/- numStr += *raw; raw++; } - if (!json_isdigit(*raw)) + if (raw >= end || !json_isdigit(*raw)) return JTOK_ERR; - while ((*raw) && json_isdigit(*raw)) { // copy digits + while (raw < end && json_isdigit(*raw)) { // copy digits numStr += *raw; raw++; } } tokenVal = numStr; consumed = (raw - rawStart); return JTOK_NUMBER; } case '"': { raw++; // skip " - string valStr; + std::string valStr; JSONUTF8StringFilter writer(valStr); - while (*raw) { - if ((unsigned char)*raw < 0x20) + 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 (hatoui(raw + 1, raw + 1 + 4, 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) +bool UniValue::read(const char *raw, size_t size) { clear(); uint32_t expectMask = 0; - vector stack; + std::vector stack; - string tokenVal; + 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); + 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 (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: { - if (!stack.size()) - 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; } + if (!stack.size()) { + *this = tmpVal; + break; + } + UniValue *top = stack.back(); top->values.push_back(tmpVal); setExpect(NOT_VALUE); break; } case JTOK_NUMBER: { - if (!stack.size()) - return false; - 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 (!stack.size()) - return false; - - UniValue *top = stack.back(); - 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); + 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 index 0e330dce9..20d404300 100644 --- a/src/univalue/lib/univalue_utffilter.h +++ b/src/univalue/lib/univalue_utffilter.h @@ -1,119 +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 /** * Filter that generates and validates UTF-8, as well as collates UTF-16 * surrogate pairs as specified in RFC4627. */ class JSONUTF8StringFilter { public: - JSONUTF8StringFilter(std::string &s): + 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) + 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 (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 + 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)); + 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); + 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) + 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))); + 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 index cfbdad328..827eb9b27 100644 --- a/src/univalue/lib/univalue_write.cpp +++ b/src/univalue/lib/univalue_write.cpp @@ -1,117 +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 #include #include #include "univalue.h" #include "univalue_escapes.h" -using namespace std; - -static string json_escape(const string& inS) +static std::string json_escape(const std::string& inS) { - string outS; + 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; } -string UniValue::write(unsigned int prettyIndent, - unsigned int indentLevel) const +std::string UniValue::write(unsigned int prettyIndent, + unsigned int indentLevel) const { - string s; + 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, string& 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, string& s) const +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 += " "; } if (prettyIndent) s += "\n"; } if (prettyIndent) indentStr(prettyIndent, indentLevel - 1, s); s += "]"; } -void UniValue::writeObject(unsigned int prettyIndent, unsigned int indentLevel, string& s) const +void UniValue::writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const { s += "{"; if (prettyIndent) s += "\n"; for (unsigned int i = 0; i < keys.size(); i++) { if (prettyIndent) indentStr(prettyIndent, indentLevel, s); s += "\"" + json_escape(keys[i]) + "\":"; if (prettyIndent) s += " "; s += values.at(i).write(prettyIndent, indentLevel + 1); if (i != (values.size() - 1)) s += ","; if (prettyIndent) s += "\n"; } if (prettyIndent) indentStr(prettyIndent, indentLevel - 1, s); s += "}"; } diff --git a/src/univalue/test/.gitignore b/src/univalue/test/.gitignore index 3d9347fe7..7b27cf0da 100644 --- a/src/univalue/test/.gitignore +++ b/src/univalue/test/.gitignore @@ -1,4 +1,8 @@ + +object unitester +test_json +no_nul *.trs *.log diff --git a/src/univalue/test/fail1.json b/src/univalue/test/fail1.json index 6216b865f..8feb01a6d 100644 --- a/src/univalue/test/fail1.json +++ b/src/univalue/test/fail1.json @@ -1 +1 @@ -"A JSON payload should be an object or array, not a string." \ No newline at end of file +"This is a string that never ends, yes it goes on and on, my friends. diff --git a/src/univalue/test/fail42.json b/src/univalue/test/fail42.json new file mode 100644 index 000000000..9c7565adb Binary files /dev/null and b/src/univalue/test/fail42.json differ diff --git a/src/univalue/test/fail44.json b/src/univalue/test/fail44.json new file mode 100644 index 000000000..80edceddf --- /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/no_nul.cpp b/src/univalue/test/no_nul.cpp new file mode 100644 index 000000000..83d292200 --- /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 000000000..02446292a --- /dev/null +++ b/src/univalue/test/object.cpp @@ -0,0 +1,395 @@ +// 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 +#include +#include +#include +#include +#include +#include + +#define BOOST_FIXTURE_TEST_SUITE(a, b) +#define BOOST_AUTO_TEST_CASE(funcName) void funcName() +#define BOOST_AUTO_TEST_SUITE_END() +#define BOOST_CHECK(expr) assert(expr) +#define BOOST_CHECK_EQUAL(v1, v2) assert((v1) == (v2)) +#define BOOST_CHECK_THROW(stmt, excMatch) { \ + try { \ + (stmt); \ + } catch (excMatch & e) { \ + } catch (...) { \ + assert(0); \ + } \ + } +#define BOOST_CHECK_NO_THROW(stmt) { \ + try { \ + (stmt); \ + } catch (...) { \ + assert(0); \ + } \ + } + +BOOST_FIXTURE_TEST_SUITE(univalue_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(univalue_constructor) +{ + UniValue v1; + BOOST_CHECK(v1.isNull()); + + UniValue v2(UniValue::VSTR); + BOOST_CHECK(v2.isStr()); + + UniValue v3(UniValue::VSTR, "foo"); + BOOST_CHECK(v3.isStr()); + BOOST_CHECK_EQUAL(v3.getValStr(), "foo"); + + UniValue numTest; + BOOST_CHECK(numTest.setNumStr("82")); + BOOST_CHECK(numTest.isNum()); + BOOST_CHECK_EQUAL(numTest.getValStr(), "82"); + + uint64_t vu64 = 82; + UniValue v4(vu64); + BOOST_CHECK(v4.isNum()); + BOOST_CHECK_EQUAL(v4.getValStr(), "82"); + + int64_t vi64 = -82; + UniValue v5(vi64); + BOOST_CHECK(v5.isNum()); + BOOST_CHECK_EQUAL(v5.getValStr(), "-82"); + + int vi = -688; + UniValue v6(vi); + BOOST_CHECK(v6.isNum()); + BOOST_CHECK_EQUAL(v6.getValStr(), "-688"); + + double vd = -7.21; + UniValue v7(vd); + BOOST_CHECK(v7.isNum()); + BOOST_CHECK_EQUAL(v7.getValStr(), "-7.21"); + + std::string vs("yawn"); + UniValue v8(vs); + BOOST_CHECK(v8.isStr()); + BOOST_CHECK_EQUAL(v8.getValStr(), "yawn"); + + const char *vcs = "zappa"; + UniValue v9(vcs); + BOOST_CHECK(v9.isStr()); + BOOST_CHECK_EQUAL(v9.getValStr(), "zappa"); +} + +BOOST_AUTO_TEST_CASE(univalue_typecheck) +{ + UniValue v1; + BOOST_CHECK(v1.setNumStr("1")); + BOOST_CHECK(v1.isNum()); + BOOST_CHECK_THROW(v1.get_bool(), std::runtime_error); + + UniValue v2; + BOOST_CHECK(v2.setBool(true)); + BOOST_CHECK_EQUAL(v2.get_bool(), true); + BOOST_CHECK_THROW(v2.get_int(), std::runtime_error); + + UniValue v3; + BOOST_CHECK(v3.setNumStr("32482348723847471234")); + BOOST_CHECK_THROW(v3.get_int64(), std::runtime_error); + BOOST_CHECK(v3.setNumStr("1000")); + BOOST_CHECK_EQUAL(v3.get_int64(), 1000); + + UniValue v4; + BOOST_CHECK(v4.setNumStr("2147483648")); + BOOST_CHECK_EQUAL(v4.get_int64(), 2147483648); + BOOST_CHECK_THROW(v4.get_int(), std::runtime_error); + BOOST_CHECK(v4.setNumStr("1000")); + BOOST_CHECK_EQUAL(v4.get_int(), 1000); + BOOST_CHECK_THROW(v4.get_str(), std::runtime_error); + BOOST_CHECK_EQUAL(v4.get_real(), 1000); + BOOST_CHECK_THROW(v4.get_array(), std::runtime_error); + BOOST_CHECK_THROW(v4.getKeys(), std::runtime_error); + BOOST_CHECK_THROW(v4.getValues(), std::runtime_error); + BOOST_CHECK_THROW(v4.get_obj(), std::runtime_error); + + UniValue v5; + BOOST_CHECK(v5.read("[true, 10]")); + BOOST_CHECK_NO_THROW(v5.get_array()); + std::vector vals = v5.getValues(); + BOOST_CHECK_THROW(vals[0].get_int(), std::runtime_error); + BOOST_CHECK_EQUAL(vals[0].get_bool(), true); + + BOOST_CHECK_EQUAL(vals[1].get_int(), 10); + BOOST_CHECK_THROW(vals[1].get_bool(), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(univalue_set) +{ + UniValue v(UniValue::VSTR, "foo"); + v.clear(); + BOOST_CHECK(v.isNull()); + BOOST_CHECK_EQUAL(v.getValStr(), ""); + + BOOST_CHECK(v.setObject()); + BOOST_CHECK(v.isObject()); + BOOST_CHECK_EQUAL(v.size(), 0); + BOOST_CHECK_EQUAL(v.getType(), UniValue::VOBJ); + BOOST_CHECK(v.empty()); + + BOOST_CHECK(v.setArray()); + BOOST_CHECK(v.isArray()); + BOOST_CHECK_EQUAL(v.size(), 0); + + BOOST_CHECK(v.setStr("zum")); + BOOST_CHECK(v.isStr()); + BOOST_CHECK_EQUAL(v.getValStr(), "zum"); + + BOOST_CHECK(v.setFloat(-1.01)); + BOOST_CHECK(v.isNum()); + BOOST_CHECK_EQUAL(v.getValStr(), "-1.01"); + + BOOST_CHECK(v.setInt((int)1023)); + BOOST_CHECK(v.isNum()); + BOOST_CHECK_EQUAL(v.getValStr(), "1023"); + + BOOST_CHECK(v.setInt((int64_t)-1023LL)); + BOOST_CHECK(v.isNum()); + BOOST_CHECK_EQUAL(v.getValStr(), "-1023"); + + BOOST_CHECK(v.setInt((uint64_t)1023ULL)); + BOOST_CHECK(v.isNum()); + BOOST_CHECK_EQUAL(v.getValStr(), "1023"); + + BOOST_CHECK(v.setNumStr("-688")); + BOOST_CHECK(v.isNum()); + BOOST_CHECK_EQUAL(v.getValStr(), "-688"); + + BOOST_CHECK(v.setBool(false)); + BOOST_CHECK_EQUAL(v.isBool(), true); + BOOST_CHECK_EQUAL(v.isTrue(), false); + BOOST_CHECK_EQUAL(v.isFalse(), true); + BOOST_CHECK_EQUAL(v.getBool(), false); + + BOOST_CHECK(v.setBool(true)); + BOOST_CHECK_EQUAL(v.isBool(), true); + BOOST_CHECK_EQUAL(v.isTrue(), true); + BOOST_CHECK_EQUAL(v.isFalse(), false); + BOOST_CHECK_EQUAL(v.getBool(), true); + + BOOST_CHECK(!v.setNumStr("zombocom")); + + BOOST_CHECK(v.setNull()); + BOOST_CHECK(v.isNull()); +} + +BOOST_AUTO_TEST_CASE(univalue_array) +{ + UniValue arr(UniValue::VARR); + + UniValue v((int64_t)1023LL); + BOOST_CHECK(arr.push_back(v)); + + std::string vStr("zippy"); + BOOST_CHECK(arr.push_back(vStr)); + + const char *s = "pippy"; + BOOST_CHECK(arr.push_back(s)); + + std::vector vec; + v.setStr("boing"); + vec.push_back(v); + + v.setStr("going"); + vec.push_back(v); + + BOOST_CHECK(arr.push_backV(vec)); + + BOOST_CHECK(arr.push_back((uint64_t) 400ULL)); + BOOST_CHECK(arr.push_back((int64_t) -400LL)); + BOOST_CHECK(arr.push_back((int) -401)); + BOOST_CHECK(arr.push_back(-40.1)); + + BOOST_CHECK_EQUAL(arr.empty(), false); + BOOST_CHECK_EQUAL(arr.size(), 9); + + BOOST_CHECK_EQUAL(arr[0].getValStr(), "1023"); + BOOST_CHECK_EQUAL(arr[1].getValStr(), "zippy"); + BOOST_CHECK_EQUAL(arr[2].getValStr(), "pippy"); + BOOST_CHECK_EQUAL(arr[3].getValStr(), "boing"); + BOOST_CHECK_EQUAL(arr[4].getValStr(), "going"); + BOOST_CHECK_EQUAL(arr[5].getValStr(), "400"); + BOOST_CHECK_EQUAL(arr[6].getValStr(), "-400"); + BOOST_CHECK_EQUAL(arr[7].getValStr(), "-401"); + BOOST_CHECK_EQUAL(arr[8].getValStr(), "-40.1"); + + BOOST_CHECK_EQUAL(arr[999].getValStr(), ""); + + arr.clear(); + BOOST_CHECK(arr.empty()); + BOOST_CHECK_EQUAL(arr.size(), 0); +} + +BOOST_AUTO_TEST_CASE(univalue_object) +{ + UniValue obj(UniValue::VOBJ); + std::string strKey, strVal; + UniValue v; + + strKey = "age"; + v.setInt(100); + BOOST_CHECK(obj.pushKV(strKey, v)); + + strKey = "first"; + strVal = "John"; + BOOST_CHECK(obj.pushKV(strKey, strVal)); + + strKey = "last"; + const char *cVal = "Smith"; + BOOST_CHECK(obj.pushKV(strKey, cVal)); + + strKey = "distance"; + BOOST_CHECK(obj.pushKV(strKey, (int64_t) 25)); + + strKey = "time"; + BOOST_CHECK(obj.pushKV(strKey, (uint64_t) 3600)); + + strKey = "calories"; + BOOST_CHECK(obj.pushKV(strKey, (int) 12)); + + strKey = "temperature"; + BOOST_CHECK(obj.pushKV(strKey, (double) 90.012)); + + 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(), 9); + + 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["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("cat1")); + BOOST_CHECK(obj.exists("cat2")); + + BOOST_CHECK(!obj.exists("nyuknyuknyuk")); + + std::map objTypes; + objTypes["age"] = UniValue::VNUM; + objTypes["first"] = UniValue::VSTR; + objTypes["last"] = UniValue::VSTR; + objTypes["distance"] = UniValue::VNUM; + objTypes["time"] = UniValue::VNUM; + objTypes["calories"] = UniValue::VNUM; + objTypes["temperature"] = UniValue::VNUM; + objTypes["cat1"] = UniValue::VNUM; + objTypes["cat2"] = UniValue::VNUM; + BOOST_CHECK(obj.checkObject(objTypes)); + + objTypes["cat2"] = UniValue::VSTR; + BOOST_CHECK(!obj.checkObject(objTypes)); + + obj.clear(); + BOOST_CHECK(obj.empty()); + BOOST_CHECK_EQUAL(obj.size(), 0); + BOOST_CHECK_EQUAL(obj.getType(), UniValue::VNULL); + + BOOST_CHECK_EQUAL(obj.setObject(), true); + UniValue uv; + uv.setInt(42); + obj.__pushKV("age", uv); + BOOST_CHECK_EQUAL(obj.size(), 1); + BOOST_CHECK_EQUAL(obj["age"].getValStr(), "42"); + + uv.setInt(43); + obj.pushKV("age", uv); + BOOST_CHECK_EQUAL(obj.size(), 1); + BOOST_CHECK_EQUAL(obj["age"].getValStr(), "43"); + + obj.pushKV("name", "foo bar"); + + std::map kv; + obj.getObjMap(kv); + BOOST_CHECK_EQUAL(kv["age"].getValStr(), "43"); + BOOST_CHECK_EQUAL(kv["name"].getValStr(), "foo bar"); + +} + +static const char *json1 = +"[1.10000000,{\"key1\":\"str\\u0000\",\"key2\":800,\"key3\":{\"name\":\"martian http://test.com\"}}]"; + +BOOST_AUTO_TEST_CASE(univalue_readwrite) +{ + UniValue v; + BOOST_CHECK(v.read(json1)); + + std::string strJson1(json1); + BOOST_CHECK(v.read(strJson1)); + + BOOST_CHECK(v.isArray()); + BOOST_CHECK_EQUAL(v.size(), 2); + + BOOST_CHECK_EQUAL(v[0].getValStr(), "1.10000000"); + + UniValue obj = v[1]; + BOOST_CHECK(obj.isObject()); + BOOST_CHECK_EQUAL(obj.size(), 3); + + BOOST_CHECK(obj["key1"].isStr()); + std::string correctValue("str"); + correctValue.push_back('\0'); + BOOST_CHECK_EQUAL(obj["key1"].getValStr(), correctValue); + BOOST_CHECK(obj["key2"].isNum()); + BOOST_CHECK_EQUAL(obj["key2"].getValStr(), "800"); + BOOST_CHECK(obj["key3"].isObject()); + + BOOST_CHECK_EQUAL(strJson1, v.write()); + + /* Check for (correctly reporting) a parsing error if the initial + JSON construct is followed by more stuff. Note that whitespace + is, of course, exempt. */ + + BOOST_CHECK(v.read(" {}\n ")); + BOOST_CHECK(v.isObject()); + BOOST_CHECK(v.read(" []\n ")); + BOOST_CHECK(v.isArray()); + + BOOST_CHECK(!v.read("@{}")); + BOOST_CHECK(!v.read("{} garbage")); + BOOST_CHECK(!v.read("[]{}")); + BOOST_CHECK(!v.read("{}[]")); + BOOST_CHECK(!v.read("{} 42")); +} + +BOOST_AUTO_TEST_SUITE_END() + +int main (int argc, char *argv[]) +{ + univalue_constructor(); + univalue_typecheck(); + univalue_set(); + univalue_array(); + univalue_object(); + univalue_readwrite(); + return 0; +} + diff --git a/src/univalue/test/round3.json b/src/univalue/test/round3.json new file mode 100644 index 000000000..7182dc2f9 --- /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 000000000..7f8f011eb --- /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 000000000..27ba77dda --- /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 000000000..c508d5366 --- /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 000000000..19765bd50 --- /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 000000000..2943bae2b --- /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 +#include +#include "univalue.h" + +using namespace std; + +int main (int argc, char *argv[]) +{ + UniValue val; + if (val.read(string(istreambuf_iterator(cin), + istreambuf_iterator()))) { + 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 index 05f3842cd..75c0dc225 100644 --- a/src/univalue/test/unitester.cpp +++ b/src/univalue/test/unitester.cpp @@ -1,163 +1,169 @@ // 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); +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(string filename, const string& jdata) +static void runtest(std::string filename, const std::string& jdata) { - string prefix = filename.substr(0, 4); + 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_) { - string basename(filename_); - string filename = srcdir + "/" + basename; + std::string basename(filename_); + std::string filename = srcdir + "/" + basename; FILE *f = fopen(filename.c_str(), "r"); assert(f != NULL); - string jdata; + std::string jdata; char buf[4096]; while (!feof(f)) { int bread = fread(buf, 1, sizeof(buf), f); assert(!ferror(f)); - string s(buf, bread); + 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 "fail3.json", "fail4.json", // extra comma "fail5.json", "fail6.json", "fail7.json", "fail8.json", "fail9.json", // extra comma "pass1.json", "pass2.json", "pass3.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; }