diff --git a/src/rpcprotocol.cpp b/src/rpcprotocol.cpp index 2718f8178..2cb4a35c4 100644 --- a/src/rpcprotocol.cpp +++ b/src/rpcprotocol.cpp @@ -54,7 +54,8 @@ static string rfc1123Time() return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime()); } -string HTTPReply(int nStatus, const string& strMsg, bool keepalive) +string HTTPReply(int nStatus, const string& strMsg, bool keepalive, + bool headersOnly, const char *contentType) { if (nStatus == HTTP_UNAUTHORIZED) return strprintf("HTTP/1.0 401 Authorization Required\r\n" @@ -73,6 +74,7 @@ string HTTPReply(int nStatus, const string& strMsg, bool keepalive) "\r\n" "

401 Unauthorized.

\r\n" "\r\n", rfc1123Time(), FormatFullVersion()); + const char *cStatus; if (nStatus == HTTP_OK) cStatus = "OK"; else if (nStatus == HTTP_BAD_REQUEST) cStatus = "Bad Request"; @@ -80,12 +82,19 @@ string HTTPReply(int nStatus, const string& strMsg, bool keepalive) else if (nStatus == HTTP_NOT_FOUND) cStatus = "Not Found"; else if (nStatus == HTTP_INTERNAL_SERVER_ERROR) cStatus = "Internal Server Error"; else cStatus = ""; + + bool useInternalContent = false; + if (nStatus != HTTP_OK) { + contentType = "text/plain"; + useInternalContent = true; + } + return strprintf( "HTTP/1.1 %d %s\r\n" "Date: %s\r\n" "Connection: %s\r\n" "Content-Length: %u\r\n" - "Content-Type: application/json\r\n" + "Content-Type: %s\r\n" "Server: bitcoin-json-rpc/%s\r\n" "\r\n" "%s", @@ -94,8 +103,10 @@ string HTTPReply(int nStatus, const string& strMsg, bool keepalive) rfc1123Time(), keepalive ? "keep-alive" : "close", strMsg.size(), + contentType, FormatFullVersion(), - strMsg); + (headersOnly ? "" : + (useInternalContent ? cStatus : strMsg.c_str()))); } bool ReadHTTPRequestLine(std::basic_istream& stream, int &proto, diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h index 11bdd171d..f1317e9c2 100644 --- a/src/rpcprotocol.h +++ b/src/rpcprotocol.h @@ -141,7 +141,9 @@ private: }; std::string HTTPPost(const std::string& strMsg, const std::map& mapRequestHeaders); -std::string HTTPReply(int nStatus, const std::string& strMsg, bool keepalive); +std::string HTTPReply(int nStatus, const std::string& strMsg, bool keepalive, + bool headerOnly = false, + const char *contentType = "application/json"); bool ReadHTTPRequestLine(std::basic_istream& stream, int &proto, std::string& http_method, std::string& http_uri); int ReadHTTPStatus(std::basic_istream& stream, int &proto); diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 6552de8c4..49bc05e5d 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -393,16 +393,6 @@ bool ClientAllowed(const boost::asio::ip::address& address) return false; } -class AcceptedConnection -{ -public: - virtual ~AcceptedConnection() {} - - virtual std::iostream& stream() = 0; - virtual std::string peer_address_to_string() const = 0; - virtual void close() = 0; -}; - template class AcceptedConnectionImpl : public AcceptedConnection { @@ -819,6 +809,71 @@ static string JSONRPCExecBatch(const Array& vReq) return write_string(Value(ret), false) + "\n"; } +static bool HTTPReq_JSONRPC(AcceptedConnection *conn, + string& strRequest, + map& mapHeaders, + bool fRun) +{ + // Check authorization + if (mapHeaders.count("authorization") == 0) + { + conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; + return false; + } + + if (!HTTPAuthorized(mapHeaders)) + { + LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string()); + /* Deter brute-forcing short passwords. + If this results in a DoS the user really + shouldn't have their RPC port exposed. */ + if (mapArgs["-rpcpassword"].size() < 20) + MilliSleep(250); + + conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; + return false; + } + + JSONRequest jreq; + try + { + // Parse request + Value valRequest; + if (!read_string(strRequest, valRequest)) + throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); + + string strReply; + + // singleton request + if (valRequest.type() == obj_type) { + jreq.parse(valRequest); + + Value result = tableRPC.execute(jreq.strMethod, jreq.params); + + // Send reply + strReply = JSONRPCReply(result, Value::null, jreq.id); + + // array of requests + } else if (valRequest.type() == array_type) + strReply = JSONRPCExecBatch(valRequest.get_array()); + else + throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); + + conn->stream() << HTTPReply(HTTP_OK, strReply, fRun) << std::flush; + } + catch (Object& objError) + { + ErrorReply(conn->stream(), objError, jreq.id); + return false; + } + catch (std::exception& e) + { + ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); + return false; + } + return true; +} + void ServiceConnection(AcceptedConnection *conn) { bool fRun = true; @@ -835,67 +890,17 @@ void ServiceConnection(AcceptedConnection *conn) // Read HTTP message headers and body ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto); - if (strURI != "/") { - conn->stream() << HTTPReply(HTTP_NOT_FOUND, "", false) << std::flush; - break; - } - - // Check authorization - if (mapHeaders.count("authorization") == 0) - { - conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; - break; - } - if (!HTTPAuthorized(mapHeaders)) - { - LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string()); - /* Deter brute-forcing short passwords. - If this results in a DoS the user really - shouldn't have their RPC port exposed. */ - if (mapArgs["-rpcpassword"].size() < 20) - MilliSleep(250); - - conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; - break; - } + // HTTP Keep-Alive is false; close connection immediately if (mapHeaders["connection"] == "close") fRun = false; - JSONRequest jreq; - try - { - // Parse request - Value valRequest; - if (!read_string(strRequest, valRequest)) - throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); - - string strReply; - - // singleton request - if (valRequest.type() == obj_type) { - jreq.parse(valRequest); - - Value result = tableRPC.execute(jreq.strMethod, jreq.params); - - // Send reply - strReply = JSONRPCReply(result, Value::null, jreq.id); - - // array of requests - } else if (valRequest.type() == array_type) - strReply = JSONRPCExecBatch(valRequest.get_array()); - else - throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); - - conn->stream() << HTTPReply(HTTP_OK, strReply, fRun) << std::flush; - } - catch (Object& objError) - { - ErrorReply(conn->stream(), objError, jreq.id); - break; + if (strURI == "/") { + if (!HTTPReq_JSONRPC(conn, strRequest, mapHeaders, fRun)) + break; } - catch (std::exception& e) - { - ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); + + else { + conn->stream() << HTTPReply(HTTP_NOT_FOUND, "", false) << std::flush; break; } } diff --git a/src/rpcserver.h b/src/rpcserver.h index 1966a65b5..fcd293663 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -21,6 +21,16 @@ class CBlockIndex; class CNetAddr; +class AcceptedConnection +{ +public: + virtual ~AcceptedConnection() {} + + virtual std::iostream& stream() = 0; + virtual std::string peer_address_to_string() const = 0; + virtual void close() = 0; +}; + /* Start RPC threads */ void StartRPCThreads(); /* Alternative to StartRPCThreads for the GUI, when no server is