You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
7.8 KiB
261 lines
7.8 KiB
// Copyright (c) 2010 Satoshi Nakamoto |
|
// 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 "rpcprotocol.h" |
|
|
|
#include "util.h" |
|
|
|
#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; |
|
|
|
// |
|
// 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. |
|
// |
|
|
|
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() |
|
{ |
|
char buffer[64]; |
|
time_t now; |
|
time(&now); |
|
struct tm* now_gmt = gmtime(&now); |
|
string locale(setlocale(LC_TIME, NULL)); |
|
setlocale(LC_TIME, "C"); // we want POSIX (aka "C") weekday/month strings |
|
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S +0000", now_gmt); |
|
setlocale(LC_TIME, locale.c_str()); |
|
return string(buffer); |
|
} |
|
|
|
string HTTPReply(int nStatus, const string& strMsg, bool keepalive) |
|
{ |
|
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" |
|
"</HTML>\r\n", rfc1123Time(), FormatFullVersion()); |
|
const char *cStatus; |
|
if (nStatus == HTTP_OK) cStatus = "OK"; |
|
else if (nStatus == HTTP_BAD_REQUEST) cStatus = "Bad Request"; |
|
else if (nStatus == HTTP_FORBIDDEN) cStatus = "Forbidden"; |
|
else if (nStatus == HTTP_NOT_FOUND) cStatus = "Not Found"; |
|
else if (nStatus == HTTP_INTERNAL_SERVER_ERROR) cStatus = "Internal Server Error"; |
|
else cStatus = ""; |
|
return strprintf( |
|
"HTTP/1.1 %d %s\r\n" |
|
"Date: %s\r\n" |
|
"Connection: %s\r\n" |
|
"Content-Length: %"PRIszu"\r\n" |
|
"Content-Type: application/json\r\n" |
|
"Server: bitcoin-json-rpc/%s\r\n" |
|
"\r\n" |
|
"%s", |
|
nStatus, |
|
cStatus, |
|
rfc1123Time(), |
|
keepalive ? "keep-alive" : "close", |
|
strMsg.size(), |
|
FormatFullVersion(), |
|
strMsg); |
|
} |
|
|
|
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, |
|
int nProto) |
|
{ |
|
mapHeadersRet.clear(); |
|
strMessageRet = ""; |
|
|
|
// Read header |
|
int nLen = ReadHTTPHeaders(stream, mapHeadersRet); |
|
if (nLen < 0 || nLen > (int)MAX_SIZE) |
|
return HTTP_INTERNAL_SERVER_ERROR; |
|
|
|
// Read message |
|
if (nLen > 0) |
|
{ |
|
vector<char> vch(nLen); |
|
stream.read(&vch[0], nLen); |
|
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; |
|
} |
|
|
|
// |
|
// 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://groups.google.com/group/json-rpc/web/json-rpc-over-http |
|
// http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx |
|
// |
|
|
|
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; |
|
}
|
|
|