// 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 #include #include #include #include #include #include #include #include #include #include "json/json_spirit_writer_template.h" using namespace std; using namespace boost; using namespace boost::asio; using namespace json_spirit; // Number of bytes to allocate and read at most at once in post data const size_t POST_READ_SIZE = 256 * 1024; // // 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& 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() { return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime()); } 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) { 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" "\r\n" "\r\n" "\r\n" "Error\r\n" "\r\n" "\r\n" "

401 Unauthorized.

\r\n" "\r\n", rfc1123Time(), FormatFullVersion()); return HTTPReply(nStatus, httpStatusDescription(nStatus), keepalive, headersOnly, "text/plain"); } string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, const char *contentType) { return strprintf( "HTTP/1.1 %d %s\r\n" "Date: %s\r\n" "Connection: %s\r\n" "Content-Length: %u\r\n" "Content-Type: %s\r\n" "Server: bitcoin-json-rpc/%s\r\n" "\r\n", nStatus, httpStatusDescription(nStatus), rfc1123Time(), keepalive ? "keep-alive" : "close", contentLength, contentType, 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; } } bool ReadHTTPRequestLine(std::basic_istream& stream, int &proto, string& http_method, string& http_uri) { string str; getline(stream, str); // HTTP request line is space-delimited vector 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& stream, int &proto) { string str; getline(stream, str); vector 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& stream, map& 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& stream, map& mapHeadersRet, string& strMessageRet, int nProto, size_t max_size) { mapHeadersRet.clear(); strMessageRet = ""; // Read header int nLen = ReadHTTPHeaders(stream, mapHeadersRet); if (nLen < 0 || (size_t)nLen > max_size) return HTTP_INTERNAL_SERVER_ERROR; // Read message if (nLen > 0) { vector 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; } 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://jsonrpc.org/historical/json-rpc-over-http.html // 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; }