2013-11-20 14:18:57 +01:00
|
|
|
// Copyright (c) 2010 Satoshi Nakamoto
|
2014-12-17 02:47:57 +01:00
|
|
|
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
2014-11-20 03:19:29 +01:00
|
|
|
// Distributed under the MIT software license, see the accompanying
|
2013-11-20 14:18:57 +01:00
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
#include "rpcprotocol.h"
|
|
|
|
|
2014-10-29 02:33:23 +01:00
|
|
|
#include "clientversion.h"
|
Split up util.cpp/h
Split up util.cpp/h into:
- string utilities (hex, base32, base64): no internal dependencies, no dependency on boost (apart from foreach)
- money utilities (parsesmoney, formatmoney)
- time utilities (gettime*, sleep, format date):
- and the rest (logging, argument parsing, config file parsing)
The latter is basically the environment and OS handling,
and is stripped of all utility functions, so we may want to
rename it to something else than util.cpp/h for clarity (Matt suggested
osinterface).
Breaks dependency of sha256.cpp on all the things pulled in by util.
2014-08-21 16:11:09 +02:00
|
|
|
#include "tinyformat.h"
|
2014-09-14 12:43:56 +02:00
|
|
|
#include "util.h"
|
Split up util.cpp/h
Split up util.cpp/h into:
- string utilities (hex, base32, base64): no internal dependencies, no dependency on boost (apart from foreach)
- money utilities (parsesmoney, formatmoney)
- time utilities (gettime*, sleep, format date):
- and the rest (logging, argument parsing, config file parsing)
The latter is basically the environment and OS handling,
and is stripped of all utility functions, so we may want to
rename it to something else than util.cpp/h for clarity (Matt suggested
osinterface).
Breaks dependency of sha256.cpp on all the things pulled in by util.
2014-08-21 16:11:09 +02:00
|
|
|
#include "utilstrencodings.h"
|
|
|
|
#include "utiltime.h"
|
2014-08-21 16:11:05 +02:00
|
|
|
#include "version.h"
|
2013-11-20 14:18:57 +01:00
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
#include <boost/asio.hpp>
|
|
|
|
#include <boost/asio/ssl.hpp>
|
|
|
|
#include <boost/bind.hpp>
|
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
#include <boost/foreach.hpp>
|
|
|
|
#include <boost/iostreams/concepts.hpp>
|
|
|
|
#include <boost/iostreams/stream.hpp>
|
|
|
|
#include <boost/shared_ptr.hpp>
|
|
|
|
#include "json/json_spirit_writer_template.h"
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace boost;
|
|
|
|
using namespace boost::asio;
|
|
|
|
using namespace json_spirit;
|
|
|
|
|
2014-11-20 03:19:29 +01:00
|
|
|
//! Number of bytes to allocate and read at most at once in post data
|
2014-06-20 15:21:30 +02:00
|
|
|
const size_t POST_READ_SIZE = 256 * 1024;
|
|
|
|
|
2014-11-20 03:19:29 +01:00
|
|
|
/**
|
|
|
|
* HTTP protocol
|
|
|
|
*
|
|
|
|
* This ain't Apache. We're just using HTTP header for the length field
|
|
|
|
* and to be compatible with other JSON-RPC implementations.
|
|
|
|
*/
|
2013-11-20 14:18:57 +01:00
|
|
|
|
|
|
|
string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders)
|
|
|
|
{
|
|
|
|
ostringstream s;
|
|
|
|
s << "POST / HTTP/1.1\r\n"
|
|
|
|
<< "User-Agent: bitcoin-json-rpc/" << FormatFullVersion() << "\r\n"
|
|
|
|
<< "Host: 127.0.0.1\r\n"
|
|
|
|
<< "Content-Type: application/json\r\n"
|
|
|
|
<< "Content-Length: " << strMsg.size() << "\r\n"
|
|
|
|
<< "Connection: close\r\n"
|
|
|
|
<< "Accept: application/json\r\n";
|
|
|
|
BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders)
|
|
|
|
s << item.first << ": " << item.second << "\r\n";
|
|
|
|
s << "\r\n" << strMsg;
|
|
|
|
|
|
|
|
return s.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
static string rfc1123Time()
|
|
|
|
{
|
2014-05-08 18:01:10 +02:00
|
|
|
return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime());
|
2013-11-20 14:18:57 +01:00
|
|
|
}
|
|
|
|
|
2014-06-29 03:14:36 +02:00
|
|
|
static const char *httpStatusDescription(int nStatus)
|
|
|
|
{
|
|
|
|
switch (nStatus) {
|
|
|
|
case HTTP_OK: return "OK";
|
|
|
|
case HTTP_BAD_REQUEST: return "Bad Request";
|
|
|
|
case HTTP_FORBIDDEN: return "Forbidden";
|
|
|
|
case HTTP_NOT_FOUND: return "Not Found";
|
|
|
|
case HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error";
|
|
|
|
default: return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string HTTPError(int nStatus, bool keepalive, bool headersOnly)
|
2013-11-20 14:18:57 +01:00
|
|
|
{
|
|
|
|
if (nStatus == HTTP_UNAUTHORIZED)
|
|
|
|
return strprintf("HTTP/1.0 401 Authorization Required\r\n"
|
|
|
|
"Date: %s\r\n"
|
|
|
|
"Server: bitcoin-json-rpc/%s\r\n"
|
|
|
|
"WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n"
|
|
|
|
"Content-Type: text/html\r\n"
|
|
|
|
"Content-Length: 296\r\n"
|
|
|
|
"\r\n"
|
|
|
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n"
|
|
|
|
"\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n"
|
|
|
|
"<HTML>\r\n"
|
|
|
|
"<HEAD>\r\n"
|
|
|
|
"<TITLE>Error</TITLE>\r\n"
|
|
|
|
"<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n"
|
|
|
|
"</HEAD>\r\n"
|
|
|
|
"<BODY><H1>401 Unauthorized.</H1></BODY>\r\n"
|
2014-01-16 16:15:27 +01:00
|
|
|
"</HTML>\r\n", rfc1123Time(), FormatFullVersion());
|
2014-06-04 17:24:43 +02:00
|
|
|
|
2014-06-29 03:14:36 +02:00
|
|
|
return HTTPReply(nStatus, httpStatusDescription(nStatus), keepalive,
|
|
|
|
headersOnly, "text/plain");
|
|
|
|
}
|
2014-06-04 17:24:43 +02:00
|
|
|
|
2014-08-06 13:01:49 +02:00
|
|
|
string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, const char *contentType)
|
2014-06-29 03:14:36 +02:00
|
|
|
{
|
2013-11-20 14:18:57 +01:00
|
|
|
return strprintf(
|
|
|
|
"HTTP/1.1 %d %s\r\n"
|
|
|
|
"Date: %s\r\n"
|
|
|
|
"Connection: %s\r\n"
|
2014-05-06 15:25:01 +02:00
|
|
|
"Content-Length: %u\r\n"
|
2014-06-04 17:24:43 +02:00
|
|
|
"Content-Type: %s\r\n"
|
2013-11-20 14:18:57 +01:00
|
|
|
"Server: bitcoin-json-rpc/%s\r\n"
|
2014-08-06 13:01:49 +02:00
|
|
|
"\r\n",
|
2013-11-20 14:18:57 +01:00
|
|
|
nStatus,
|
2014-06-29 03:14:36 +02:00
|
|
|
httpStatusDescription(nStatus),
|
2014-01-16 16:15:27 +01:00
|
|
|
rfc1123Time(),
|
2013-11-20 14:18:57 +01:00
|
|
|
keepalive ? "keep-alive" : "close",
|
2014-08-06 13:01:49 +02:00
|
|
|
contentLength,
|
2014-06-04 17:24:43 +02:00
|
|
|
contentType,
|
2014-08-06 13:01:49 +02:00
|
|
|
FormatFullVersion());
|
|
|
|
}
|
|
|
|
|
|
|
|
string HTTPReply(int nStatus, const string& strMsg, bool keepalive,
|
|
|
|
bool headersOnly, const char *contentType)
|
|
|
|
{
|
|
|
|
if (headersOnly)
|
|
|
|
{
|
|
|
|
return HTTPReplyHeader(nStatus, keepalive, 0, contentType);
|
|
|
|
} else {
|
|
|
|
return HTTPReplyHeader(nStatus, keepalive, strMsg.size(), contentType) + strMsg;
|
|
|
|
}
|
2013-11-20 14:18:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto,
|
|
|
|
string& http_method, string& http_uri)
|
|
|
|
{
|
|
|
|
string str;
|
|
|
|
getline(stream, str);
|
|
|
|
|
|
|
|
// HTTP request line is space-delimited
|
|
|
|
vector<string> vWords;
|
|
|
|
boost::split(vWords, str, boost::is_any_of(" "));
|
|
|
|
if (vWords.size() < 2)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// HTTP methods permitted: GET, POST
|
|
|
|
http_method = vWords[0];
|
|
|
|
if (http_method != "GET" && http_method != "POST")
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// HTTP URI must be an absolute path, relative to current host
|
|
|
|
http_uri = vWords[1];
|
|
|
|
if (http_uri.size() == 0 || http_uri[0] != '/')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// parse proto, if present
|
|
|
|
string strProto = "";
|
|
|
|
if (vWords.size() > 2)
|
|
|
|
strProto = vWords[2];
|
|
|
|
|
|
|
|
proto = 0;
|
|
|
|
const char *ver = strstr(strProto.c_str(), "HTTP/1.");
|
|
|
|
if (ver != NULL)
|
|
|
|
proto = atoi(ver+7);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto)
|
|
|
|
{
|
|
|
|
string str;
|
|
|
|
getline(stream, str);
|
|
|
|
vector<string> vWords;
|
|
|
|
boost::split(vWords, str, boost::is_any_of(" "));
|
|
|
|
if (vWords.size() < 2)
|
|
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
|
proto = 0;
|
|
|
|
const char *ver = strstr(str.c_str(), "HTTP/1.");
|
|
|
|
if (ver != NULL)
|
|
|
|
proto = atoi(ver+7);
|
|
|
|
return atoi(vWords[1].c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet)
|
|
|
|
{
|
|
|
|
int nLen = 0;
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
string str;
|
|
|
|
std::getline(stream, str);
|
|
|
|
if (str.empty() || str == "\r")
|
|
|
|
break;
|
|
|
|
string::size_type nColon = str.find(":");
|
|
|
|
if (nColon != string::npos)
|
|
|
|
{
|
|
|
|
string strHeader = str.substr(0, nColon);
|
|
|
|
boost::trim(strHeader);
|
|
|
|
boost::to_lower(strHeader);
|
|
|
|
string strValue = str.substr(nColon+1);
|
|
|
|
boost::trim(strValue);
|
|
|
|
mapHeadersRet[strHeader] = strValue;
|
|
|
|
if (strHeader == "content-length")
|
|
|
|
nLen = atoi(strValue.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nLen;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int ReadHTTPMessage(std::basic_istream<char>& stream, map<string,
|
|
|
|
string>& mapHeadersRet, string& strMessageRet,
|
2014-08-06 13:03:58 +02:00
|
|
|
int nProto, size_t max_size)
|
2013-11-20 14:18:57 +01:00
|
|
|
{
|
|
|
|
mapHeadersRet.clear();
|
|
|
|
strMessageRet = "";
|
|
|
|
|
|
|
|
// Read header
|
|
|
|
int nLen = ReadHTTPHeaders(stream, mapHeadersRet);
|
2014-08-06 13:03:58 +02:00
|
|
|
if (nLen < 0 || (size_t)nLen > max_size)
|
2013-11-20 14:18:57 +01:00
|
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
|
|
|
|
|
// Read message
|
|
|
|
if (nLen > 0)
|
|
|
|
{
|
2014-06-20 15:21:30 +02:00
|
|
|
vector<char> vch;
|
|
|
|
size_t ptr = 0;
|
|
|
|
while (ptr < (size_t)nLen)
|
|
|
|
{
|
|
|
|
size_t bytes_to_read = std::min((size_t)nLen - ptr, POST_READ_SIZE);
|
|
|
|
vch.resize(ptr + bytes_to_read);
|
|
|
|
stream.read(&vch[ptr], bytes_to_read);
|
|
|
|
if (!stream) // Connection lost while reading
|
|
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
|
ptr += bytes_to_read;
|
|
|
|
}
|
2013-11-20 14:18:57 +01:00
|
|
|
strMessageRet = string(vch.begin(), vch.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
string sConHdr = mapHeadersRet["connection"];
|
|
|
|
|
|
|
|
if ((sConHdr != "close") && (sConHdr != "keep-alive"))
|
|
|
|
{
|
|
|
|
if (nProto >= 1)
|
|
|
|
mapHeadersRet["connection"] = "keep-alive";
|
|
|
|
else
|
|
|
|
mapHeadersRet["connection"] = "close";
|
|
|
|
}
|
|
|
|
|
|
|
|
return HTTP_OK;
|
|
|
|
}
|
|
|
|
|
2014-11-20 03:19:29 +01:00
|
|
|
/**
|
|
|
|
* JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
|
|
|
|
* but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
|
|
|
|
* unspecified (HTTP errors and contents of 'error').
|
|
|
|
*
|
|
|
|
* 1.0 spec: http://json-rpc.org/wiki/specification
|
|
|
|
* 1.2 spec: http://jsonrpc.org/historical/json-rpc-over-http.html
|
|
|
|
* http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx
|
|
|
|
*/
|
2013-11-20 14:18:57 +01:00
|
|
|
|
|
|
|
string JSONRPCRequest(const string& strMethod, const Array& params, const Value& id)
|
|
|
|
{
|
|
|
|
Object request;
|
|
|
|
request.push_back(Pair("method", strMethod));
|
|
|
|
request.push_back(Pair("params", params));
|
|
|
|
request.push_back(Pair("id", id));
|
|
|
|
return write_string(Value(request), false) + "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
Object JSONRPCReplyObj(const Value& result, const Value& error, const Value& id)
|
|
|
|
{
|
|
|
|
Object reply;
|
|
|
|
if (error.type() != null_type)
|
|
|
|
reply.push_back(Pair("result", Value::null));
|
|
|
|
else
|
|
|
|
reply.push_back(Pair("result", result));
|
|
|
|
reply.push_back(Pair("error", error));
|
|
|
|
reply.push_back(Pair("id", id));
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
|
|
|
string JSONRPCReply(const Value& result, const Value& error, const Value& id)
|
|
|
|
{
|
|
|
|
Object reply = JSONRPCReplyObj(result, error, id);
|
|
|
|
return write_string(Value(reply), false) + "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
Object JSONRPCError(int code, const string& message)
|
|
|
|
{
|
|
|
|
Object error;
|
|
|
|
error.push_back(Pair("code", code));
|
|
|
|
error.push_back(Pair("message", message));
|
|
|
|
return error;
|
|
|
|
}
|