Jeff Garzik
10 years ago
19 changed files with 1846 additions and 182 deletions
@ -0,0 +1,597 @@ |
|||||||
|
// Copyright (c) 2009-2014 The Bitcoin developers
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include "base58.h" |
||||||
|
#include "util.h" |
||||||
|
#include "core.h" |
||||||
|
#include "main.h" // for MAX_BLOCK_SIZE |
||||||
|
#include "keystore.h" |
||||||
|
#include "ui_interface.h" // for _(...) |
||||||
|
#include "univalue/univalue.h" |
||||||
|
#include "core_io.h" |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <boost/assign/list_of.hpp> |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
using namespace boost::assign; |
||||||
|
|
||||||
|
static bool fCreateBlank; |
||||||
|
static map<string,UniValue> registers; |
||||||
|
CClientUIInterface uiInterface; |
||||||
|
|
||||||
|
static bool AppInitRawTx(int argc, char* argv[]) |
||||||
|
{ |
||||||
|
//
|
||||||
|
// Parameters
|
||||||
|
//
|
||||||
|
ParseParameters(argc, argv); |
||||||
|
|
||||||
|
// Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
|
||||||
|
if (!SelectParamsFromCommandLine()) { |
||||||
|
fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
fCreateBlank = GetBoolArg("-create", false); |
||||||
|
|
||||||
|
if (argc<2 || mapArgs.count("-?") || mapArgs.count("-help")) |
||||||
|
{ |
||||||
|
// First part of help message is specific to this utility
|
||||||
|
std::string strUsage = _("Bitcoin Core bitcoin-tx utility version") + " " + FormatFullVersion() + "\n\n" + |
||||||
|
_("Usage:") + "\n" + |
||||||
|
" bitcoin-tx [options] <hex-tx> [commands] " + _("Update hex-encoded bitcoin transaction") + "\n" + |
||||||
|
" bitcoin-tx [options] -create [commands] " + _("Create hex-encoded bitcoin transaction") + "\n" + |
||||||
|
"\n"; |
||||||
|
|
||||||
|
fprintf(stdout, "%s", strUsage.c_str()); |
||||||
|
|
||||||
|
strUsage = _("Options:") + "\n"; |
||||||
|
strUsage += " -? " + _("This help message") + "\n"; |
||||||
|
strUsage += " -create " + _("Create new, empty TX.") + "\n"; |
||||||
|
strUsage += " -json " + _("Select JSON output") + "\n"; |
||||||
|
strUsage += " -regtest " + _("Enter regression test mode, which uses a special chain in which blocks can be solved instantly.") + "\n"; |
||||||
|
strUsage += " -testnet " + _("Use the test network") + "\n"; |
||||||
|
strUsage += "\n"; |
||||||
|
|
||||||
|
fprintf(stdout, "%s", strUsage.c_str()); |
||||||
|
|
||||||
|
|
||||||
|
strUsage = _("Commands:") + "\n"; |
||||||
|
strUsage += " delin=N " + _("Delete input N from TX") + "\n"; |
||||||
|
strUsage += " delout=N " + _("Delete output N from TX") + "\n"; |
||||||
|
strUsage += " in=TXID:VOUT " + _("Add input to TX") + "\n"; |
||||||
|
strUsage += " locktime=N " + _("Set TX lock time to N") + "\n"; |
||||||
|
strUsage += " nversion=N " + _("Set TX version to N") + "\n"; |
||||||
|
strUsage += " outaddr=VALUE:ADDRESS " + _("Add address-based output to TX") + "\n"; |
||||||
|
strUsage += " outscript=VALUE:SCRIPT " + _("Add raw script output to TX") + "\n"; |
||||||
|
strUsage += " sign=SIGHASH-FLAGS " + _("Add zero or more signatures to transaction") + "\n"; |
||||||
|
strUsage += " This command requires JSON registers:\n"; |
||||||
|
strUsage += " prevtxs=JSON object\n"; |
||||||
|
strUsage += " privatekeys=JSON object\n"; |
||||||
|
strUsage += " See signrawtransaction docs for format of sighash flags, JSON objects.\n"; |
||||||
|
strUsage += "\n"; |
||||||
|
fprintf(stdout, "%s", strUsage.c_str()); |
||||||
|
|
||||||
|
strUsage = _("Register Commands:") + "\n"; |
||||||
|
strUsage += " load=NAME:FILENAME " + _("Load JSON file FILENAME into register NAME") + "\n"; |
||||||
|
strUsage += " set=NAME:JSON-STRING " + _("Set register NAME to given JSON-STRING") + "\n"; |
||||||
|
strUsage += "\n"; |
||||||
|
fprintf(stdout, "%s", strUsage.c_str()); |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static void RegisterSetJson(const string& key, const string& rawJson) |
||||||
|
{ |
||||||
|
UniValue val; |
||||||
|
if (!val.read(rawJson)) { |
||||||
|
string strErr = "Cannot parse JSON for key " + key; |
||||||
|
throw runtime_error(strErr); |
||||||
|
} |
||||||
|
|
||||||
|
registers[key] = val; |
||||||
|
} |
||||||
|
|
||||||
|
static void RegisterSet(const string& strInput) |
||||||
|
{ |
||||||
|
// separate NAME:VALUE in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("Register input requires NAME:VALUE"); |
||||||
|
|
||||||
|
string key = strInput.substr(0, pos); |
||||||
|
string valStr = strInput.substr(pos + 1, string::npos); |
||||||
|
|
||||||
|
RegisterSetJson(key, valStr); |
||||||
|
} |
||||||
|
|
||||||
|
static void RegisterLoad(const string& strInput) |
||||||
|
{ |
||||||
|
// separate NAME:FILENAME in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("Register load requires NAME:FILENAME"); |
||||||
|
|
||||||
|
string key = strInput.substr(0, pos); |
||||||
|
string filename = strInput.substr(pos + 1, string::npos); |
||||||
|
|
||||||
|
FILE *f = fopen(filename.c_str(), "r"); |
||||||
|
if (!f) { |
||||||
|
string strErr = "Cannot open file " + filename; |
||||||
|
throw runtime_error(strErr); |
||||||
|
} |
||||||
|
|
||||||
|
// load file chunks into one big buffer
|
||||||
|
string valStr; |
||||||
|
while ((!feof(f)) && (!ferror(f))) { |
||||||
|
char buf[4096]; |
||||||
|
int bread = fread(buf, 1, sizeof(buf), f); |
||||||
|
if (bread <= 0) |
||||||
|
break; |
||||||
|
|
||||||
|
valStr.insert(valStr.size(), buf, bread); |
||||||
|
} |
||||||
|
|
||||||
|
if (ferror(f)) { |
||||||
|
string strErr = "Error reading file " + filename; |
||||||
|
throw runtime_error(strErr); |
||||||
|
} |
||||||
|
|
||||||
|
fclose(f); |
||||||
|
|
||||||
|
// evaluate as JSON buffer register
|
||||||
|
RegisterSetJson(key, valStr); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxVersion(CMutableTransaction& tx, const string& cmdVal) |
||||||
|
{ |
||||||
|
int64_t newVersion = atoi64(cmdVal); |
||||||
|
if (newVersion < 1 || newVersion > CTransaction::CURRENT_VERSION) |
||||||
|
throw runtime_error("Invalid TX version requested"); |
||||||
|
|
||||||
|
tx.nVersion = (int) newVersion; |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxLocktime(CMutableTransaction& tx, const string& cmdVal) |
||||||
|
{ |
||||||
|
int64_t newLocktime = atoi64(cmdVal); |
||||||
|
if (newLocktime < 0LL || newLocktime > 0xffffffffLL) |
||||||
|
throw runtime_error("Invalid TX locktime requested"); |
||||||
|
|
||||||
|
tx.nLockTime = (unsigned int) newLocktime; |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxAddInput(CMutableTransaction& tx, const string& strInput) |
||||||
|
{ |
||||||
|
// separate TXID:VOUT in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("TX input missing separator"); |
||||||
|
|
||||||
|
// extract and validate TXID
|
||||||
|
string strTxid = strInput.substr(0, pos); |
||||||
|
if ((strTxid.size() != 64) || !IsHex(strTxid)) |
||||||
|
throw runtime_error("invalid TX input txid"); |
||||||
|
uint256 txid(strTxid); |
||||||
|
|
||||||
|
static const unsigned int minTxOutSz = 9; |
||||||
|
static const unsigned int maxVout = MAX_BLOCK_SIZE / minTxOutSz; |
||||||
|
|
||||||
|
// extract and validate vout
|
||||||
|
string strVout = strInput.substr(pos + 1, string::npos); |
||||||
|
int vout = atoi(strVout); |
||||||
|
if ((vout < 0) || (vout > (int)maxVout)) |
||||||
|
throw runtime_error("invalid TX input vout"); |
||||||
|
|
||||||
|
// append to transaction input list
|
||||||
|
CTxIn txin(txid, vout); |
||||||
|
tx.vin.push_back(txin); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput) |
||||||
|
{ |
||||||
|
// separate VALUE:ADDRESS in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("TX output missing separator"); |
||||||
|
|
||||||
|
// extract and validate VALUE
|
||||||
|
string strValue = strInput.substr(0, pos); |
||||||
|
int64_t value; |
||||||
|
if (!ParseMoney(strValue, value)) |
||||||
|
throw runtime_error("invalid TX output value"); |
||||||
|
|
||||||
|
// extract and validate ADDRESS
|
||||||
|
string strAddr = strInput.substr(pos + 1, string::npos); |
||||||
|
CBitcoinAddress addr(strAddr); |
||||||
|
if (!addr.IsValid()) |
||||||
|
throw runtime_error("invalid TX output address"); |
||||||
|
|
||||||
|
// build standard output script via SetDestination()
|
||||||
|
CScript scriptPubKey; |
||||||
|
scriptPubKey.SetDestination(addr.Get()); |
||||||
|
|
||||||
|
// construct TxOut, append to transaction output list
|
||||||
|
CTxOut txout(value, scriptPubKey); |
||||||
|
tx.vout.push_back(txout); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput) |
||||||
|
{ |
||||||
|
// separate VALUE:SCRIPT in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("TX output missing separator"); |
||||||
|
|
||||||
|
// extract and validate VALUE
|
||||||
|
string strValue = strInput.substr(0, pos); |
||||||
|
int64_t value; |
||||||
|
if (!ParseMoney(strValue, value)) |
||||||
|
throw runtime_error("invalid TX output value"); |
||||||
|
|
||||||
|
// extract and validate script
|
||||||
|
string strScript = strInput.substr(pos + 1, string::npos); |
||||||
|
CScript scriptPubKey = ParseScript(strScript); // throws on err
|
||||||
|
|
||||||
|
// construct TxOut, append to transaction output list
|
||||||
|
CTxOut txout(value, scriptPubKey); |
||||||
|
tx.vout.push_back(txout); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxDelInput(CMutableTransaction& tx, const string& strInIdx) |
||||||
|
{ |
||||||
|
// parse requested deletion index
|
||||||
|
int inIdx = atoi(strInIdx); |
||||||
|
if (inIdx < 0 || inIdx >= (int)tx.vin.size()) { |
||||||
|
string strErr = "Invalid TX input index '" + strInIdx + "'"; |
||||||
|
throw runtime_error(strErr.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
// delete input from transaction
|
||||||
|
tx.vin.erase(tx.vin.begin() + inIdx); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxDelOutput(CMutableTransaction& tx, const string& strOutIdx) |
||||||
|
{ |
||||||
|
// parse requested deletion index
|
||||||
|
int outIdx = atoi(strOutIdx); |
||||||
|
if (outIdx < 0 || outIdx >= (int)tx.vout.size()) { |
||||||
|
string strErr = "Invalid TX output index '" + strOutIdx + "'"; |
||||||
|
throw runtime_error(strErr.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
// delete output from transaction
|
||||||
|
tx.vout.erase(tx.vout.begin() + outIdx); |
||||||
|
} |
||||||
|
|
||||||
|
static const unsigned int N_SIGHASH_OPTS = 6; |
||||||
|
static const struct { |
||||||
|
const char *flagStr; |
||||||
|
int flags; |
||||||
|
} sighashOptions[N_SIGHASH_OPTS] = { |
||||||
|
"ALL", SIGHASH_ALL, |
||||||
|
"NONE", SIGHASH_NONE, |
||||||
|
"SINGLE", SIGHASH_SINGLE, |
||||||
|
"ALL|ANYONECANPAY", SIGHASH_ALL|SIGHASH_ANYONECANPAY, |
||||||
|
"NONE|ANYONECANPAY", SIGHASH_NONE|SIGHASH_ANYONECANPAY, |
||||||
|
"SINGLE|ANYONECANPAY", SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, |
||||||
|
}; |
||||||
|
|
||||||
|
static bool findSighashFlags(int& flags, const string& flagStr) |
||||||
|
{ |
||||||
|
flags = 0; |
||||||
|
|
||||||
|
for (unsigned int i = 0; i < N_SIGHASH_OPTS; i++) { |
||||||
|
if (flagStr == sighashOptions[i].flagStr) { |
||||||
|
flags = sighashOptions[i].flags; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
uint256 ParseHashUO(map<string,UniValue>& o, string strKey) |
||||||
|
{ |
||||||
|
if (!o.count(strKey)) |
||||||
|
return 0; |
||||||
|
return ParseHashUV(o[strKey], strKey); |
||||||
|
} |
||||||
|
|
||||||
|
vector<unsigned char> ParseHexUO(map<string,UniValue>& o, string strKey) |
||||||
|
{ |
||||||
|
if (!o.count(strKey)) { |
||||||
|
vector<unsigned char> emptyVec; |
||||||
|
return emptyVec; |
||||||
|
} |
||||||
|
return ParseHexUV(o[strKey], strKey); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxSign(CMutableTransaction& tx, const string& flagStr) |
||||||
|
{ |
||||||
|
int nHashType = SIGHASH_ALL; |
||||||
|
|
||||||
|
if (flagStr.size() > 0) |
||||||
|
if (!findSighashFlags(nHashType, flagStr)) |
||||||
|
throw runtime_error("unknown sighash flag/sign option"); |
||||||
|
|
||||||
|
vector<CTransaction> txVariants; |
||||||
|
txVariants.push_back(tx); |
||||||
|
|
||||||
|
// mergedTx will end up with all the signatures; it
|
||||||
|
// starts as a clone of the raw tx:
|
||||||
|
CMutableTransaction mergedTx(txVariants[0]); |
||||||
|
bool fComplete = true; |
||||||
|
CCoinsView viewDummy; |
||||||
|
CCoinsViewCache view(viewDummy); |
||||||
|
|
||||||
|
if (!registers.count("privatekeys")) |
||||||
|
throw runtime_error("privatekeys register variable must be set."); |
||||||
|
bool fGivenKeys = false; |
||||||
|
CBasicKeyStore tempKeystore; |
||||||
|
UniValue keysObj = registers["privatekeys"]; |
||||||
|
fGivenKeys = true; |
||||||
|
|
||||||
|
for (unsigned int kidx = 0; kidx < keysObj.count(); kidx++) { |
||||||
|
if (!keysObj[kidx].isStr()) |
||||||
|
throw runtime_error("privatekey not a string"); |
||||||
|
CBitcoinSecret vchSecret; |
||||||
|
bool fGood = vchSecret.SetString(keysObj[kidx].getValStr()); |
||||||
|
if (!fGood) |
||||||
|
throw runtime_error("privatekey not valid"); |
||||||
|
|
||||||
|
CKey key = vchSecret.GetKey(); |
||||||
|
tempKeystore.AddKey(key); |
||||||
|
} |
||||||
|
|
||||||
|
// Add previous txouts given in the RPC call:
|
||||||
|
if (!registers.count("prevtxs")) |
||||||
|
throw runtime_error("prevtxs register variable must be set."); |
||||||
|
UniValue prevtxsObj = registers["privatekeys"]; |
||||||
|
{ |
||||||
|
for (unsigned int previdx = 0; previdx < prevtxsObj.count(); previdx++) { |
||||||
|
UniValue prevOut = prevtxsObj[previdx]; |
||||||
|
if (!prevOut.isObject()) |
||||||
|
throw runtime_error("expected prevtxs internal object"); |
||||||
|
|
||||||
|
map<string,UniValue::VType> types = map_list_of("txid", UniValue::VSTR)("vout",UniValue::VNUM)("scriptPubKey",UniValue::VSTR); |
||||||
|
if (!prevOut.checkObject(types)) |
||||||
|
throw runtime_error("prevtxs internal object typecheck fail"); |
||||||
|
|
||||||
|
uint256 txid = ParseHashUV(prevOut, "txid"); |
||||||
|
|
||||||
|
int nOut = atoi(prevOut["vout"].getValStr()); |
||||||
|
if (nOut < 0) |
||||||
|
throw runtime_error("vout must be positive"); |
||||||
|
|
||||||
|
vector<unsigned char> pkData(ParseHexUV(prevOut, "scriptPubKey")); |
||||||
|
CScript scriptPubKey(pkData.begin(), pkData.end()); |
||||||
|
|
||||||
|
CCoins coins; |
||||||
|
if (view.GetCoins(txid, coins)) { |
||||||
|
if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) { |
||||||
|
string err("Previous output scriptPubKey mismatch:\n"); |
||||||
|
err = err + coins.vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+ |
||||||
|
scriptPubKey.ToString(); |
||||||
|
throw runtime_error(err); |
||||||
|
} |
||||||
|
// what todo if txid is known, but the actual output isn't?
|
||||||
|
} |
||||||
|
if ((unsigned int)nOut >= coins.vout.size()) |
||||||
|
coins.vout.resize(nOut+1); |
||||||
|
coins.vout[nOut].scriptPubKey = scriptPubKey; |
||||||
|
coins.vout[nOut].nValue = 0; // we don't know the actual output value
|
||||||
|
view.SetCoins(txid, coins); |
||||||
|
|
||||||
|
// if redeemScript given and private keys given,
|
||||||
|
// add redeemScript to the tempKeystore so it can be signed:
|
||||||
|
if (fGivenKeys && scriptPubKey.IsPayToScriptHash() && |
||||||
|
prevOut.exists("redeemScript")) { |
||||||
|
UniValue v = prevOut["redeemScript"]; |
||||||
|
vector<unsigned char> rsData(ParseHexUV(v, "redeemScript")); |
||||||
|
CScript redeemScript(rsData.begin(), rsData.end()); |
||||||
|
tempKeystore.AddCScript(redeemScript); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const CKeyStore& keystore = tempKeystore; |
||||||
|
|
||||||
|
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); |
||||||
|
|
||||||
|
// Sign what we can:
|
||||||
|
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { |
||||||
|
CTxIn& txin = mergedTx.vin[i]; |
||||||
|
CCoins coins; |
||||||
|
if (!view.GetCoins(txin.prevout.hash, coins) || !coins.IsAvailable(txin.prevout.n)) { |
||||||
|
fComplete = false; |
||||||
|
continue; |
||||||
|
} |
||||||
|
const CScript& prevPubKey = coins.vout[txin.prevout.n].scriptPubKey; |
||||||
|
|
||||||
|
txin.scriptSig.clear(); |
||||||
|
// Only sign SIGHASH_SINGLE if there's a corresponding output:
|
||||||
|
if (!fHashSingle || (i < mergedTx.vout.size())) |
||||||
|
SignSignature(keystore, prevPubKey, mergedTx, i, nHashType); |
||||||
|
|
||||||
|
// ... and merge in other signatures:
|
||||||
|
BOOST_FOREACH(const CTransaction& txv, txVariants) { |
||||||
|
txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig); |
||||||
|
} |
||||||
|
if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, STANDARD_SCRIPT_VERIFY_FLAGS, 0)) |
||||||
|
fComplete = false; |
||||||
|
} |
||||||
|
|
||||||
|
if (fComplete) { |
||||||
|
// do nothing... for now
|
||||||
|
// perhaps store this for later optional JSON output
|
||||||
|
} |
||||||
|
|
||||||
|
tx = mergedTx; |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTx(CMutableTransaction& tx, const string& command, |
||||||
|
const string& commandVal) |
||||||
|
{ |
||||||
|
if (command == "nversion") |
||||||
|
MutateTxVersion(tx, commandVal); |
||||||
|
else if (command == "locktime") |
||||||
|
MutateTxLocktime(tx, commandVal); |
||||||
|
|
||||||
|
else if (command == "delin") |
||||||
|
MutateTxDelInput(tx, commandVal); |
||||||
|
else if (command == "in") |
||||||
|
MutateTxAddInput(tx, commandVal); |
||||||
|
|
||||||
|
else if (command == "delout") |
||||||
|
MutateTxDelOutput(tx, commandVal); |
||||||
|
else if (command == "outaddr") |
||||||
|
MutateTxAddOutAddr(tx, commandVal); |
||||||
|
else if (command == "outscript") |
||||||
|
MutateTxAddOutScript(tx, commandVal); |
||||||
|
|
||||||
|
else if (command == "sign") |
||||||
|
MutateTxSign(tx, commandVal); |
||||||
|
|
||||||
|
else if (command == "load") |
||||||
|
RegisterLoad(commandVal); |
||||||
|
|
||||||
|
else if (command == "set") |
||||||
|
RegisterSet(commandVal); |
||||||
|
|
||||||
|
else |
||||||
|
throw runtime_error("unknown command"); |
||||||
|
} |
||||||
|
|
||||||
|
static void OutputTxJSON(const CTransaction& tx) |
||||||
|
{ |
||||||
|
UniValue entry(UniValue::VOBJ); |
||||||
|
TxToUniv(tx, 0, entry); |
||||||
|
|
||||||
|
string jsonOutput = entry.write(4); |
||||||
|
fprintf(stdout, "%s\n", jsonOutput.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
static void OutputTxHex(const CTransaction& tx) |
||||||
|
{ |
||||||
|
string strHex = EncodeHexTx(tx); |
||||||
|
|
||||||
|
fprintf(stdout, "%s\n", strHex.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
static void OutputTx(const CTransaction& tx) |
||||||
|
{ |
||||||
|
if (GetBoolArg("-json", false)) |
||||||
|
OutputTxJSON(tx); |
||||||
|
else |
||||||
|
OutputTxHex(tx); |
||||||
|
} |
||||||
|
|
||||||
|
static int CommandLineRawTx(int argc, char* argv[]) |
||||||
|
{ |
||||||
|
string strPrint; |
||||||
|
int nRet = 0; |
||||||
|
try { |
||||||
|
// Skip switches
|
||||||
|
while (argc > 1 && IsSwitchChar(argv[1][0])) { |
||||||
|
argc--; |
||||||
|
argv++; |
||||||
|
} |
||||||
|
|
||||||
|
CTransaction txDecodeTmp; |
||||||
|
int startArg; |
||||||
|
|
||||||
|
if (!fCreateBlank) { |
||||||
|
// require at least one param
|
||||||
|
if (argc < 2) |
||||||
|
throw runtime_error("too few parameters"); |
||||||
|
|
||||||
|
// param: hex-encoded bitcoin transaction
|
||||||
|
string strHexTx(argv[1]); |
||||||
|
|
||||||
|
if (!DecodeHexTx(txDecodeTmp, strHexTx)) |
||||||
|
throw runtime_error("invalid transaction encoding"); |
||||||
|
|
||||||
|
startArg = 2; |
||||||
|
} else |
||||||
|
startArg = 1; |
||||||
|
|
||||||
|
CMutableTransaction tx(txDecodeTmp); |
||||||
|
|
||||||
|
for (int i = startArg; i < argc; i++) { |
||||||
|
string arg = argv[i]; |
||||||
|
string key, value; |
||||||
|
size_t eqpos = arg.find('='); |
||||||
|
if (eqpos == string::npos) |
||||||
|
key = arg; |
||||||
|
else { |
||||||
|
key = arg.substr(0, eqpos); |
||||||
|
value = arg.substr(eqpos + 1); |
||||||
|
} |
||||||
|
|
||||||
|
MutateTx(tx, key, value); |
||||||
|
} |
||||||
|
|
||||||
|
OutputTx(tx); |
||||||
|
} |
||||||
|
|
||||||
|
catch (boost::thread_interrupted) { |
||||||
|
throw; |
||||||
|
} |
||||||
|
catch (std::exception& e) { |
||||||
|
strPrint = string("error: ") + e.what(); |
||||||
|
nRet = EXIT_FAILURE; |
||||||
|
} |
||||||
|
catch (...) { |
||||||
|
PrintExceptionContinue(NULL, "CommandLineRawTx()"); |
||||||
|
throw; |
||||||
|
} |
||||||
|
|
||||||
|
if (strPrint != "") { |
||||||
|
fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); |
||||||
|
} |
||||||
|
return nRet; |
||||||
|
} |
||||||
|
|
||||||
|
int main(int argc, char* argv[]) |
||||||
|
{ |
||||||
|
SetupEnvironment(); |
||||||
|
|
||||||
|
try { |
||||||
|
if(!AppInitRawTx(argc, argv)) |
||||||
|
return EXIT_FAILURE; |
||||||
|
} |
||||||
|
catch (std::exception& e) { |
||||||
|
PrintExceptionContinue(&e, "AppInitRawTx()"); |
||||||
|
return EXIT_FAILURE; |
||||||
|
} catch (...) { |
||||||
|
PrintExceptionContinue(NULL, "AppInitRawTx()"); |
||||||
|
return EXIT_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
int ret = EXIT_FAILURE; |
||||||
|
try { |
||||||
|
ret = CommandLineRawTx(argc, argv); |
||||||
|
} |
||||||
|
catch (std::exception& e) { |
||||||
|
PrintExceptionContinue(&e, "CommandLineRawTx()"); |
||||||
|
} catch (...) { |
||||||
|
PrintExceptionContinue(NULL, "CommandLineRawTx()"); |
||||||
|
} |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,23 @@ |
|||||||
|
#ifndef __BITCOIN_CORE_IO_H__ |
||||||
|
#define __BITCOIN_CORE_IO_H__ |
||||||
|
|
||||||
|
#include <string> |
||||||
|
|
||||||
|
class uint256; |
||||||
|
class CScript; |
||||||
|
class CTransaction; |
||||||
|
class UniValue; |
||||||
|
|
||||||
|
// core_read.cpp
|
||||||
|
extern CScript ParseScript(std::string s); |
||||||
|
extern bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx); |
||||||
|
extern uint256 ParseHashUV(const UniValue& v, const std::string& strName); |
||||||
|
extern std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName); |
||||||
|
|
||||||
|
// core_write.cpp
|
||||||
|
extern std::string EncodeHexTx(const CTransaction& tx); |
||||||
|
extern void ScriptPubKeyToUniv(const CScript& scriptPubKey, |
||||||
|
UniValue& out, bool fIncludeHex); |
||||||
|
extern void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry); |
||||||
|
|
||||||
|
#endif // __BITCOIN_CORE_IO_H__
|
@ -0,0 +1,127 @@ |
|||||||
|
|
||||||
|
#include <vector> |
||||||
|
#include "core_io.h" |
||||||
|
#include "core.h" |
||||||
|
#include "serialize.h" |
||||||
|
#include "script.h" |
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
#include <boost/assign/list_of.hpp> |
||||||
|
#include <boost/algorithm/string/classification.hpp> |
||||||
|
#include <boost/algorithm/string/predicate.hpp> |
||||||
|
#include <boost/algorithm/string/split.hpp> |
||||||
|
#include <boost/algorithm/string/replace.hpp> |
||||||
|
#include "univalue/univalue.h" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
using namespace boost; |
||||||
|
using namespace boost::algorithm; |
||||||
|
|
||||||
|
CScript ParseScript(std::string s) |
||||||
|
{ |
||||||
|
CScript result; |
||||||
|
|
||||||
|
static map<string, opcodetype> mapOpNames; |
||||||
|
|
||||||
|
if (mapOpNames.empty()) |
||||||
|
{ |
||||||
|
for (int op = 0; op <= OP_NOP10; op++) |
||||||
|
{ |
||||||
|
// Allow OP_RESERVED to get into mapOpNames
|
||||||
|
if (op < OP_NOP && op != OP_RESERVED) |
||||||
|
continue; |
||||||
|
|
||||||
|
const char* name = GetOpName((opcodetype)op); |
||||||
|
if (strcmp(name, "OP_UNKNOWN") == 0) |
||||||
|
continue; |
||||||
|
string strName(name); |
||||||
|
mapOpNames[strName] = (opcodetype)op; |
||||||
|
// Convenience: OP_ADD and just ADD are both recognized:
|
||||||
|
replace_first(strName, "OP_", ""); |
||||||
|
mapOpNames[strName] = (opcodetype)op; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
vector<string> words; |
||||||
|
split(words, s, is_any_of(" \t\n"), token_compress_on); |
||||||
|
|
||||||
|
for (std::vector<std::string>::const_iterator w = words.begin(); w != words.end(); ++w) |
||||||
|
{ |
||||||
|
if (w->empty()) |
||||||
|
{ |
||||||
|
// Empty string, ignore. (boost::split given '' will return one word)
|
||||||
|
} |
||||||
|
else if (all(*w, is_digit()) || |
||||||
|
(starts_with(*w, "-") && all(string(w->begin()+1, w->end()), is_digit()))) |
||||||
|
{ |
||||||
|
// Number
|
||||||
|
int64_t n = atoi64(*w); |
||||||
|
result << n; |
||||||
|
} |
||||||
|
else if (starts_with(*w, "0x") && (w->begin()+2 != w->end()) && IsHex(string(w->begin()+2, w->end()))) |
||||||
|
{ |
||||||
|
// Raw hex data, inserted NOT pushed onto stack:
|
||||||
|
std::vector<unsigned char> raw = ParseHex(string(w->begin()+2, w->end())); |
||||||
|
result.insert(result.end(), raw.begin(), raw.end()); |
||||||
|
} |
||||||
|
else if (w->size() >= 2 && starts_with(*w, "'") && ends_with(*w, "'")) |
||||||
|
{ |
||||||
|
// Single-quoted string, pushed as data. NOTE: this is poor-man's
|
||||||
|
// parsing, spaces/tabs/newlines in single-quoted strings won't work.
|
||||||
|
std::vector<unsigned char> value(w->begin()+1, w->end()-1); |
||||||
|
result << value; |
||||||
|
} |
||||||
|
else if (mapOpNames.count(*w)) |
||||||
|
{ |
||||||
|
// opcode, e.g. OP_ADD or ADD:
|
||||||
|
result << mapOpNames[*w]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
throw runtime_error("script parse error"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx) |
||||||
|
{ |
||||||
|
if (!IsHex(strHexTx)) |
||||||
|
return false; |
||||||
|
|
||||||
|
vector<unsigned char> txData(ParseHex(strHexTx)); |
||||||
|
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); |
||||||
|
try { |
||||||
|
ssData >> tx; |
||||||
|
} |
||||||
|
catch (std::exception &e) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
uint256 ParseHashUV(const UniValue& v, const string& strName) |
||||||
|
{ |
||||||
|
string strHex; |
||||||
|
if (v.isStr()) |
||||||
|
strHex = v.getValStr(); |
||||||
|
if (!IsHex(strHex)) // Note: IsHex("") is false
|
||||||
|
throw runtime_error(strName+" must be hexadecimal string (not '"+strHex+"')"); |
||||||
|
|
||||||
|
uint256 result; |
||||||
|
result.SetHex(strHex); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
vector<unsigned char> ParseHexUV(const UniValue& v, const string& strName) |
||||||
|
{ |
||||||
|
string strHex; |
||||||
|
if (v.isStr()) |
||||||
|
strHex = v.getValStr(); |
||||||
|
if (!IsHex(strHex)) |
||||||
|
throw runtime_error(strName+" must be hexadecimal string (not '"+strHex+"')"); |
||||||
|
return ParseHex(strHex); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,89 @@ |
|||||||
|
|
||||||
|
#include <vector> |
||||||
|
#include "core_io.h" |
||||||
|
#include "univalue/univalue.h" |
||||||
|
#include "script.h" |
||||||
|
#include "core.h" |
||||||
|
#include "serialize.h" |
||||||
|
#include "util.h" |
||||||
|
#include "base58.h" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
string EncodeHexTx(const CTransaction& tx) |
||||||
|
{ |
||||||
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); |
||||||
|
ssTx << tx; |
||||||
|
return HexStr(ssTx.begin(), ssTx.end()); |
||||||
|
} |
||||||
|
|
||||||
|
void ScriptPubKeyToUniv(const CScript& scriptPubKey, |
||||||
|
UniValue& out, bool fIncludeHex) |
||||||
|
{ |
||||||
|
txnouttype type; |
||||||
|
vector<CTxDestination> addresses; |
||||||
|
int nRequired; |
||||||
|
|
||||||
|
out.pushKV("asm", scriptPubKey.ToString()); |
||||||
|
if (fIncludeHex) |
||||||
|
out.pushKV("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end())); |
||||||
|
|
||||||
|
if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { |
||||||
|
out.pushKV("type", GetTxnOutputType(type)); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
out.pushKV("reqSigs", nRequired); |
||||||
|
out.pushKV("type", GetTxnOutputType(type)); |
||||||
|
|
||||||
|
UniValue a(UniValue::VARR); |
||||||
|
BOOST_FOREACH(const CTxDestination& addr, addresses) |
||||||
|
a.push_back(CBitcoinAddress(addr).ToString()); |
||||||
|
out.pushKV("addresses", a); |
||||||
|
} |
||||||
|
|
||||||
|
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry) |
||||||
|
{ |
||||||
|
entry.pushKV("txid", tx.GetHash().GetHex()); |
||||||
|
entry.pushKV("version", tx.nVersion); |
||||||
|
entry.pushKV("locktime", (int64_t)tx.nLockTime); |
||||||
|
|
||||||
|
UniValue vin(UniValue::VARR); |
||||||
|
BOOST_FOREACH(const CTxIn& txin, tx.vin) { |
||||||
|
UniValue in(UniValue::VOBJ); |
||||||
|
if (tx.IsCoinBase()) |
||||||
|
in.pushKV("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); |
||||||
|
else { |
||||||
|
in.pushKV("txid", txin.prevout.hash.GetHex()); |
||||||
|
in.pushKV("vout", (int64_t)txin.prevout.n); |
||||||
|
UniValue o(UniValue::VOBJ); |
||||||
|
o.pushKV("asm", txin.scriptSig.ToString()); |
||||||
|
o.pushKV("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); |
||||||
|
in.pushKV("scriptSig", o); |
||||||
|
} |
||||||
|
in.pushKV("sequence", (int64_t)txin.nSequence); |
||||||
|
vin.push_back(in); |
||||||
|
} |
||||||
|
entry.pushKV("vin", vin); |
||||||
|
|
||||||
|
UniValue vout(UniValue::VARR); |
||||||
|
for (unsigned int i = 0; i < tx.vout.size(); i++) { |
||||||
|
const CTxOut& txout = tx.vout[i]; |
||||||
|
|
||||||
|
UniValue out(UniValue::VOBJ); |
||||||
|
|
||||||
|
UniValue outValue(UniValue::VNUM, FormatMoney(txout.nValue)); |
||||||
|
out.pushKV("value", outValue); |
||||||
|
out.pushKV("n", (int64_t)i); |
||||||
|
|
||||||
|
UniValue o(UniValue::VOBJ); |
||||||
|
ScriptPubKeyToUniv(txout.scriptPubKey, o, true); |
||||||
|
out.pushKV("scriptPubKey", o); |
||||||
|
vout.push_back(out); |
||||||
|
} |
||||||
|
entry.pushKV("vout", vout); |
||||||
|
|
||||||
|
if (hashBlock != 0) |
||||||
|
entry.pushKV("blockhash", hashBlock.GetHex()); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,233 @@ |
|||||||
|
// 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 <stdint.h> |
||||||
|
#include <ctype.h> |
||||||
|
#include <sstream> |
||||||
|
#include "univalue.h" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
static const UniValue nullValue; |
||||||
|
|
||||||
|
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 << val; |
||||||
|
|
||||||
|
return setNumStr(oss.str()); |
||||||
|
} |
||||||
|
|
||||||
|
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<UniValue>& 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; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::getArray(std::vector<UniValue>& arr) |
||||||
|
{ |
||||||
|
if (typ != VARR) |
||||||
|
return false; |
||||||
|
|
||||||
|
arr = values; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::getObject(std::map<std::string,UniValue>& obj) |
||||||
|
{ |
||||||
|
if (typ != VOBJ) |
||||||
|
return false; |
||||||
|
|
||||||
|
obj.clear(); |
||||||
|
for (unsigned int i = 0; i < keys.size(); i++) { |
||||||
|
obj[keys[i]] = 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<std::string,UniValue::VType>& t) |
||||||
|
{ |
||||||
|
for (std::map<std::string,UniValue::VType>::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 nullValue; |
||||||
|
|
||||||
|
int index = findKey(key); |
||||||
|
if (index < 0) |
||||||
|
return nullValue; |
||||||
|
|
||||||
|
return values[index]; |
||||||
|
} |
||||||
|
|
||||||
|
const UniValue& UniValue::operator[](unsigned int index) const |
||||||
|
{ |
||||||
|
if (typ != VOBJ && typ != VARR) |
||||||
|
return nullValue; |
||||||
|
if (index >= values.size()) |
||||||
|
return nullValue; |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,157 @@ |
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef __UNIVALUE_H__ |
||||||
|
#define __UNIVALUE_H__ |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
#include <map> |
||||||
|
#include <cassert> |
||||||
|
|
||||||
|
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(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; } |
||||||
|
std::string getValStr() const { return val; } |
||||||
|
bool empty() const { return (values.size() == 0); } |
||||||
|
|
||||||
|
size_t count() const { return values.size(); } |
||||||
|
|
||||||
|
bool getBool() const { return isTrue(); } |
||||||
|
bool getArray(std::vector<UniValue>& arr); |
||||||
|
bool getObject(std::map<std::string,UniValue>& obj); |
||||||
|
bool checkObject(const std::map<std::string,UniValue::VType>& 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 (!isTrue()); } |
||||||
|
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<UniValue>& 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<std::string> keys; |
||||||
|
std::vector<UniValue> 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; |
||||||
|
}; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
#endif // __UNIVALUE_H__
|
@ -0,0 +1,390 @@ |
|||||||
|
// 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 <string.h> |
||||||
|
#include <vector> |
||||||
|
#include <stdio.h> |
||||||
|
#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': { |
||||||
|
char buf[4] = {0,0,0,0}; |
||||||
|
char *last = &buf[0]; |
||||||
|
unsigned int codepoint; |
||||||
|
if (hatoui(raw + 1, raw + 1 + 4, codepoint) != |
||||||
|
raw + 1 + 4) |
||||||
|
return JTOK_ERR; |
||||||
|
|
||||||
|
if (codepoint <= 0x7f) |
||||||
|
*last = (char)codepoint; |
||||||
|
else if (codepoint <= 0x7FF) { |
||||||
|
*last++ = (char)(0xC0 | (codepoint >> 6)); |
||||||
|
*last = (char)(0x80 | (codepoint & 0x3F)); |
||||||
|
} else if (codepoint <= 0xFFFF) { |
||||||
|
*last++ = (char)(0xE0 | (codepoint >> 12)); |
||||||
|
*last++ = (char)(0x80 | ((codepoint >> 6) & 0x3F)); |
||||||
|
*last = (char)(0x80 | (codepoint & 0x3F)); |
||||||
|
} |
||||||
|
|
||||||
|
valStr += buf; |
||||||
|
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<UniValue*> stack; |
||||||
|
|
||||||
|
enum jtokentype tok = JTOK_NONE; |
||||||
|
enum jtokentype last_tok = JTOK_NONE; |
||||||
|
while (1) { |
||||||
|
last_tok = tok; |
||||||
|
|
||||||
|
string tokenVal; |
||||||
|
unsigned int consumed; |
||||||
|
tok = getJsonToken(tokenVal, consumed, raw); |
||||||
|
if (tok == JTOK_NONE || tok == JTOK_ERR) |
||||||
|
break; |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (stack.size() != 0) |
||||||
|
return false; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,145 @@ |
|||||||
|
// 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 <ctype.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include "univalue.h" |
||||||
|
|
||||||
|
// TODO: Using UTF8
|
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
static bool initEscapes; |
||||||
|
static const char *escapes[256]; |
||||||
|
|
||||||
|
static void initJsonEscape() |
||||||
|
{ |
||||||
|
escapes['"'] = "\\\""; |
||||||
|
escapes['\\'] = "\\\\"; |
||||||
|
escapes['/'] = "\\/"; |
||||||
|
escapes['\b'] = "\\b"; |
||||||
|
escapes['\f'] = "\\f"; |
||||||
|
escapes['\n'] = "\\n"; |
||||||
|
escapes['\r'] = "\\r"; |
||||||
|
escapes['\t'] = "\\t"; |
||||||
|
|
||||||
|
initEscapes = true; |
||||||
|
} |
||||||
|
|
||||||
|
static string json_escape(const string& inS) |
||||||
|
{ |
||||||
|
if (!initEscapes) |
||||||
|
initJsonEscape(); |
||||||
|
|
||||||
|
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 string spaceStr; |
||||||
|
|
||||||
|
static string indentStr(unsigned int prettyIndent, unsigned int indentLevel) |
||||||
|
{ |
||||||
|
unsigned int spaces = prettyIndent * indentLevel; |
||||||
|
while (spaceStr.size() < spaces) |
||||||
|
spaceStr += " "; |
||||||
|
|
||||||
|
return spaceStr.substr(0, spaces); |
||||||
|
} |
||||||
|
|
||||||
|
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) |
||||||
|
s += indentStr(prettyIndent, indentLevel); |
||||||
|
s += values[i].write(prettyIndent, indentLevel + 1); |
||||||
|
if (i != (values.size() - 1)) |
||||||
|
s += ", "; |
||||||
|
if (prettyIndent) |
||||||
|
s += "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
if (prettyIndent) |
||||||
|
s += indentStr(prettyIndent, indentLevel - 1); |
||||||
|
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) |
||||||
|
s += indentStr(prettyIndent, indentLevel); |
||||||
|
s += "\"" + json_escape(keys[i]) + "\": "; |
||||||
|
s += values[i].write(prettyIndent, indentLevel + 1); |
||||||
|
if (i != (values.size() - 1)) |
||||||
|
s += ","; |
||||||
|
if (prettyIndent) |
||||||
|
s += "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
if (prettyIndent) |
||||||
|
s += indentStr(prettyIndent, indentLevel - 1); |
||||||
|
s += "}"; |
||||||
|
} |
||||||
|
|
Loading…
Reference in new issue