text: // Copyright (c) 2009-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.
  
  #if defined(HAVE_CONFIG_H)
  #include "config/bitcoin-config.h"
  #endif
  
  #include "chainparamsbase.h"
  #include "clientversion.h"
  #include "rpc/client.h"
  #include "rpc/protocol.h"
  #include "support/events.h"
  #include "util.h"
  #include "utilstrencodings.h"
  
  #include <boost/filesystem/operations.hpp>
  #include <cstdio>
  
  #include <event2/buffer.h>
  #include <event2/keyvalq_struct.h>
  
  #include <univalue.h>
  
  static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
  static const int DEFAULT_HTTP_CLIENT_TIMEOUT = 900;
  static const bool DEFAULT_NAMED = false;
  static const int CONTINUE_EXECUTION = -1;
  
  std::string HelpMessageCli() {
      std::string strUsage;
      strUsage += HelpMessageGroup(_("Options:"));
      strUsage += HelpMessageOpt("-?", _("This help message"));
      strUsage += HelpMessageOpt(
          "-conf=<file>", strprintf(_("Specify configuration file (default: %s)"),
                                    BITCOIN_CONF_FILENAME));
      strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
      AppendParamsHelpMessages(strUsage);
      strUsage += HelpMessageOpt(
          "-named",
          strprintf(_("Pass named instead of positional arguments (default: %s)"),
                    DEFAULT_NAMED));
      strUsage += HelpMessageOpt(
          "-rpcconnect=<ip>",
          strprintf(_("Send commands to node running on <ip> (default: %s)"),
                    DEFAULT_RPCCONNECT));
      strUsage += HelpMessageOpt(
          "-rpcport=<port>",
          strprintf(
              _("Connect to JSON-RPC on <port> (default: %u or testnet: %u)"),
              BaseParams(CBaseChainParams::MAIN).RPCPort(),
              BaseParams(CBaseChainParams::TESTNET).RPCPort()));
      strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start"));
      strUsage += HelpMessageOpt("-rpcuser=<user>",
                                 _("Username for JSON-RPC connections"));
      strUsage += HelpMessageOpt("-rpcpassword=<pw>",
                                 _("Password for JSON-RPC connections"));
      strUsage += HelpMessageOpt(
          "-rpcclienttimeout=<n>",
          strprintf(_("Timeout during HTTP requests (default: %d)"),
                    DEFAULT_HTTP_CLIENT_TIMEOUT));
      strUsage += HelpMessageOpt(
          "-stdin", _("Read extra arguments from standard input, one per line "
                      "until EOF/Ctrl-D (recommended for sensitive information "
                      "such as passphrases)"));
  
      return strUsage;
  }
  
  //////////////////////////////////////////////////////////////////////////////
  //
  // Start
  //
  
  //
  // Exception thrown on connection error.  This error is used to determine when
  // to wait if -rpcwait is given.
  //
  class CConnectionFailed : public std::runtime_error {
  public:
      explicit inline CConnectionFailed(const std::string &msg)
          : std::runtime_error(msg) {}
  };
  
  //
  // This function returns either one of EXIT_ codes when it's expected to stop
  // the process or CONTINUE_EXECUTION when it's expected to continue further.
  //
  static int AppInitRPC(int argc, char *argv[]) {
      //
      // Parameters
      //
      ParseParameters(argc, argv);
      if (argc < 2 || IsArgSet("-?") || IsArgSet("-h") || IsArgSet("-help") ||
          IsArgSet("-version")) {
          std::string strUsage =
              strprintf(_("%s RPC client version"), _(PACKAGE_NAME)) + " " +
              FormatFullVersion() + "\n";
          if (!IsArgSet("-version")) {
              strUsage +=
                  "\n" + _("Usage:") + "\n" +
                  "  bitcoin-cli [options] <command> [params]  " +
                  strprintf(_("Send command to %s"), _(PACKAGE_NAME)) + "\n" +
                  "  bitcoin-cli [options] -named <command> [name=value] ... " +
                  strprintf(_("Send command to %s (with named arguments)"),
                            _(PACKAGE_NAME)) +
                  "\n" + "  bitcoin-cli [options] help                " +
                  _("List commands") + "\n" +
                  "  bitcoin-cli [options] help <command>      " +
                  _("Get help for a command") + "\n";
  
              strUsage += "\n" + HelpMessageCli();
          }
  
          fprintf(stdout, "%s", strUsage.c_str());
          if (argc < 2) {
              fprintf(stderr, "Error: too few parameters\n");
              return EXIT_FAILURE;
          }
          return EXIT_SUCCESS;
      }
      if (!boost::filesystem::is_directory(GetDataDir(false))) {
          fprintf(stderr,
                  "Error: Specified data directory \"%s\" does not exist.\n",
                  GetArg("-datadir", "").c_str());
          return EXIT_FAILURE;
      }
      try {
          ReadConfigFile(GetArg("-conf", BITCOIN_CONF_FILENAME));
      } catch (const std::exception &e) {
          fprintf(stderr, "Error reading configuration file: %s\n", e.what());
          return EXIT_FAILURE;
      }
      // Check for -testnet or -regtest parameter (BaseParams() calls are only
      // valid after this clause)
      try {
          SelectBaseParams(ChainNameFromCommandLine());
      } catch (const std::exception &e) {
          fprintf(stderr, "Error: %s\n", e.what());
          return EXIT_FAILURE;
      }
      if (GetBoolArg("-rpcssl", false)) {
          fprintf(stderr,
                  "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n");
          return EXIT_FAILURE;
      }
      return CONTINUE_EXECUTION;
  }
  
  /** Reply structure for request_done to fill in */
  struct HTTPReply {
      HTTPReply() : status(0), error(-1) {}
  
      int status;
      int error;
      std::string body;
  };
  
  const char *http_errorstring(int code) {
      switch (code) {
  #if LIBEVENT_VERSION_NUMBER >= 0x02010300
          case EVREQ_HTTP_TIMEOUT:
              return "timeout reached";
          case EVREQ_HTTP_EOF:
              return "EOF reached";
          case EVREQ_HTTP_INVALID_HEADER:
              return "error while reading header, or invalid header";
          case EVREQ_HTTP_BUFFER_ERROR:
              return "error encountered while reading or writing";
          case EVREQ_HTTP_REQUEST_CANCEL:
              return "request was canceled";
          case EVREQ_HTTP_DATA_TOO_LONG:
              return "response body is larger than allowed";
  #endif
          default:
              return "unknown";
      }
  }
  
  static void http_request_done(struct evhttp_request *req, void *ctx) {
      HTTPReply *reply = static_cast<HTTPReply *>(ctx);
  
      if (req == nullptr) {
          /**
           * If req is nullptr, it means an error occurred while connecting: the
           * error code will have been passed to http_error_cb.
           */
          reply->status = 0;
          return;
      }
  
      reply->status = evhttp_request_get_response_code(req);
  
      struct evbuffer *buf = evhttp_request_get_input_buffer(req);
      if (buf) {
          size_t size = evbuffer_get_length(buf);
          const char *data = (const char *)evbuffer_pullup(buf, size);
          if (data) reply->body = std::string(data, size);
          evbuffer_drain(buf, size);
      }
  }
  
  #if LIBEVENT_VERSION_NUMBER >= 0x02010300
  static void http_error_cb(enum evhttp_request_error err, void *ctx) {
      HTTPReply *reply = static_cast<HTTPReply *>(ctx);
      reply->error = err;
  }
  #endif
  
  UniValue CallRPC(const std::string &strMethod, const UniValue &params) {
      std::string host = GetArg("-rpcconnect", DEFAULT_RPCCONNECT);
      int port = GetArg("-rpcport", BaseParams().RPCPort());
  
      // Obtain event base
      raii_event_base base = obtain_event_base();
  
      // Synchronously look up hostname
      raii_evhttp_connection evcon =
          obtain_evhttp_connection_base(base.get(), host, port);
      evhttp_connection_set_timeout(
          evcon.get(), GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT));
  
      HTTPReply response;
      raii_evhttp_request req =
          obtain_evhttp_request(http_request_done, (void *)&response);
      if (req == nullptr) throw std::runtime_error("create http request failed");
  #if LIBEVENT_VERSION_NUMBER >= 0x02010300
      evhttp_request_set_error_cb(req.get(), http_error_cb);
  #endif
  
      // Get credentials
      std::string strRPCUserColonPass;
      if (GetArg("-rpcpassword", "") == "") {
          // Try fall back to cookie-based authentication if no password is
          // provided
          if (!GetAuthCookie(&strRPCUserColonPass)) {
              throw std::runtime_error(strprintf(
                  _("Could not locate RPC credentials. No authentication cookie "
                    "could be found, and no rpcpassword is set in the "
                    "configuration file (%s)"),
                  GetConfigFile(GetArg("-conf", BITCOIN_CONF_FILENAME))
                      .string()
                      .c_str()));
          }
      } else {
          strRPCUserColonPass =
              GetArg("-rpcuser", "") + ":" + GetArg("-rpcpassword", "");
      }
  
      struct evkeyvalq *output_headers =
          evhttp_request_get_output_headers(req.get());
      assert(output_headers);
      evhttp_add_header(output_headers, "Host", host.c_str());
      evhttp_add_header(output_headers, "Connection", "close");
      evhttp_add_header(
          output_headers, "Authorization",
          (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
  
      // Attach request data
      std::string strRequest =
          JSONRPCRequestObj(strMethod, params, 1).write() + "\n";
      struct evbuffer *output_buffer =
          evhttp_request_get_output_buffer(req.get());
      assert(output_buffer);
      evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
  
      int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/");
      // ownership moved to evcon in above call
      req.release();
      if (r != 0) {
          throw CConnectionFailed("send http request failed");
      }
  
      event_base_dispatch(base.get());
  
      if (response.status == 0) {
          throw CConnectionFailed(strprintf(
              "couldn't connect to server: %s (code %d)\n(make sure server is "
              "running and you are connecting to the correct RPC port)",
              http_errorstring(response.error), response.error));
      } else if (response.status == HTTP_UNAUTHORIZED) {
          throw std::runtime_error(
              "incorrect rpcuser or rpcpassword (authorization failed)");
      } else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST &&
                 response.status != HTTP_NOT_FOUND &&
                 response.status != HTTP_INTERNAL_SERVER_ERROR) {
          throw std::runtime_error(
              strprintf("server returned HTTP error %d", response.status));
      } else if (response.body.empty()) {
          throw std::runtime_error("no response from server");
      }
  
      // Parse reply
      UniValue valReply(UniValue::VSTR);
      if (!valReply.read(response.body)) {
          throw std::runtime_error("couldn't parse reply from server");
      }
      const UniValue &reply = valReply.get_obj();
      if (reply.empty()) {
          throw std::runtime_error(
              "expected reply to have result, error and id properties");
      }
  
      return reply;
  }
  
  int CommandLineRPC(int argc, char *argv[]) {
      std::string strPrint;
      int nRet = 0;
      try {
          // Skip switches
          while (argc > 1 && IsSwitchChar(argv[1][0])) {
              argc--;
              argv++;
          }
          std::vector<std::string> args =
              std::vector<std::string>(&argv[1], &argv[argc]);
          if (GetBoolArg("-stdin", false)) {
              // Read one arg per line from stdin and append
              std::string line;
              while (std::getline(std::cin, line)) {
                  args.push_back(line);
              }
          }
          if (args.size() < 1) {
              throw std::runtime_error(
                  "too few parameters (need at least command)");
          }
          std::string strMethod = args[0];
          // Remove trailing method name from arguments vector
          args.erase(args.begin());
  
          UniValue params;
          if (GetBoolArg("-named", DEFAULT_NAMED)) {
              params = RPCConvertNamedValues(strMethod, args);
          } else {
              params = RPCConvertValues(strMethod, args);
          }
  
          // Execute and handle connection failures with -rpcwait
          const bool fWait = GetBoolArg("-rpcwait", false);
          do {
              try {
                  const UniValue reply = CallRPC(strMethod, params);
  
                  // Parse reply
                  const UniValue &result = find_value(reply, "result");
                  const UniValue &error = find_value(reply, "error");
  
                  if (!error.isNull()) {
                      // Error
                      int code = error["code"].get_int();
                      if (fWait && code == RPC_IN_WARMUP)
                          throw CConnectionFailed("server in warmup");
                      strPrint = "error: " + error.write();
                      nRet = abs(code);
                      if (error.isObject()) {
                          UniValue errCode = find_value(error, "code");
                          UniValue errMsg = find_value(error, "message");
                          strPrint =
                              errCode.isNull()
                                  ? ""
                                  : "error code: " + errCode.getValStr() + "\n";
  
                          if (errMsg.isStr()) {
                              strPrint += "error message:\n" + errMsg.get_str();
                          }
                      }
                  } else {
                      // Result
                      if (result.isNull()) {
                          strPrint = "";
                      } else if (result.isStr()) {
                          strPrint = result.get_str();
                      } else {
                          strPrint = result.write(2);
                      }
                  }
                  // Connection succeeded, no need to retry.
                  break;
              } catch (const CConnectionFailed &) {
                  if (fWait) {
                      MilliSleep(1000);
                  } else {
                      throw;
                  }
              }
          } while (fWait);
      } catch (const boost::thread_interrupted &) {
          throw;
      } catch (const std::exception &e) {
          strPrint = std::string("error: ") + e.what();
          nRet = EXIT_FAILURE;
      } catch (...) {
          PrintExceptionContinue(nullptr, "CommandLineRPC()");
          throw;
      }
  
      if (strPrint != "") {
          fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str());
      }
      return nRet;
  }
  
  int main(int argc, char *argv[]) {
      SetupEnvironment();
      if (!SetupNetworking()) {
          fprintf(stderr, "Error: Initializing networking failed\n");
          return EXIT_FAILURE;
      }
  
      try {
          int ret = AppInitRPC(argc, argv);
          if (ret != CONTINUE_EXECUTION) {
              return ret;
          }
      } catch (const std::exception &e) {
          PrintExceptionContinue(&e, "AppInitRPC()");
          return EXIT_FAILURE;
      } catch (...) {
          PrintExceptionContinue(nullptr, "AppInitRPC()");
          return EXIT_FAILURE;
      }
  
      int ret = EXIT_FAILURE;
      try {
          ret = CommandLineRPC(argc, argv);
      } catch (const std::exception &e) {
          PrintExceptionContinue(&e, "CommandLineRPC()");
      } catch (...) {
          PrintExceptionContinue(nullptr, "CommandLineRPC()");
      }
      return ret;
  }
  