refactor: Add ExecuteHTTPRPC function
What changed, and why it matters
This commit is a code cleanup that splits the existing JSON-RPC request handling into a new helper function called ExecuteHTTPRPC. The commit message explicitly says behavior is not changing, and the diff shows the same logic being moved around rather than added or removed. There is no indication of a security fix or vulnerability.
No security action required. Treat as ordinary refactoring; review for correctness during normal code review.
Security signals we found
No security-relevant behavior change
Refactoring only: code moved, not added or removed
No new trust boundaries or input handling introduced
No vendor security disclosure or CVE references present
Evidence from the diff
The change refactors HTTPReq_JSONRPC in src/httprpc.cpp by extracting request parsing, authorization whitelist checks, batch execution, and error handling into a new ExecuteHTTPRPC function. The new function returns a UniValue reply and sets an HTTPStatusCode out-parameter, while the caller remains responsible for writing the HTTP response. The HTTPRequestHandler typedef is changed from returning bool to returning void, and HTTPStatusCode is made explicitly int-backed. These are structural changes to support future work (likely JSON-RPC v2) but do not alter current runtime behavior.
Changed components
src/httprpc.cppsrc/httprpc.hsrc/httpserver.hsrc/rpc/protocol.hInspect captured patch +87 / −70
diff --git a/src/httprpc.cpp b/src/httprpc.cpp
index 56a24308..2a6751df 100644
--- a/src/httprpc.cpp
+++ b/src/httprpc.cpp
@@ -38,13 +38,15 @@ static std::vector<std::vector<std::string>> g_rpcauth;
static std::map<std::string, std::set<std::string>> g_rpc_whitelist;
static bool g_rpc_whitelist_default = false;
-static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCRequest& jreq)
+static UniValue JSONErrorReply(UniValue objError, const JSONRPCRequest& jreq, HTTPStatusCode& nStatus)
{
- // Sending HTTP errors is a legacy JSON-RPC behavior.
+ // HTTP errors should never be returned if JSON-RPC v2 was requested. This
+ // function should only be called when a v1 request fails or when a request
+ // cannot be parsed, so the version is unknown.
Assume(jreq.m_json_version != JSONRPCVersion::V2);
// Send error reply from json-rpc error object
- int nStatus = HTTP_INTERNAL_SERVER_ERROR;
+ nStatus = HTTP_INTERNAL_SERVER_ERROR;
int code = objError.find_value("code").getInt<int>();
if (code == RPC_INVALID_REQUEST)
@@ -52,10 +54,7 @@ static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCReq
else if (code == RPC_METHOD_NOT_FOUND)
nStatus = HTTP_NOT_FOUND;
- std::string strReply = JSONRPCReplyObj(NullUniValue, std::move(objError), jreq.id, jreq.m_json_version).write() + "\n";
-
- req->WriteHeader("Content-Type", "application/json");
- req->WriteReply(nStatus, strReply);
+ return JSONRPCReplyObj(NullUniValue, std::move(objError), jreq.id, jreq.m_json_version);
}
//This function checks username and password against -rpcauth
@@ -101,60 +100,23 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna
return CheckUserAuthorized(user, pass);
}
-static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
+UniValue ExecuteHTTPRPC(const UniValue& valRequest, JSONRPCRequest& jreq, HTTPStatusCode& status)
{
- // JSONRPC handles only POST
- if (req->GetRequestMethod() != HTTPRequest::POST) {
- req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests");
- return false;
- }
- // Check authorization
- std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
- if (!authHeader.first) {
- req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
- req->WriteReply(HTTP_UNAUTHORIZED);
- return false;
- }
-
- JSONRPCRequest jreq;
- jreq.context = context;
- jreq.peerAddr = req->GetPeer().ToStringAddrPort();
- if (!RPCAuthorized(authHeader.second, jreq.authUser)) {
- LogWarning("ThreadRPCServer incorrect password attempt from %s", jreq.peerAddr);
-
- /* Deter brute-forcing
- If this results in a DoS the user really
- shouldn't have their RPC port exposed. */
- UninterruptibleSleep(std::chrono::milliseconds{250});
-
- req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
- req->WriteReply(HTTP_UNAUTHORIZED);
- return false;
- }
-
+ status = HTTP_OK;
try {
- // Parse request
- UniValue valRequest;
- if (!valRequest.read(req->ReadBody()))
- throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
-
- // Set the URI
- jreq.URI = req->GetURI();
-
- UniValue reply;
bool user_has_whitelist = g_rpc_whitelist.contains(jreq.authUser);
if (!user_has_whitelist && g_rpc_whitelist_default) {
LogWarning("RPC User %s not allowed to call any methods", jreq.authUser);
- req->WriteReply(HTTP_FORBIDDEN);
- return false;
+ status = HTTP_FORBIDDEN;
+ return {};
// singleton request
} else if (valRequest.isObject()) {
jreq.parse(valRequest);
if (user_has_whitelist && !g_rpc_whitelist[jreq.authUser].contains(jreq.strMethod)) {
LogWarning("RPC User %s not allowed to call method %s", jreq.authUser, jreq.strMethod);
- req->WriteReply(HTTP_FORBIDDEN);
- return false;
+ status = HTTP_FORBIDDEN;
+ return {};
}
// Legacy 1.0/1.1 behavior is for failed requests to throw
@@ -162,14 +124,13 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
// 2.0 behavior is to catch exceptions and return HTTP success with
// RPC errors, as long as there is not an actual HTTP server error.
const bool catch_errors{jreq.m_json_version == JSONRPCVersion::V2};
- reply = JSONRPCExec(jreq, catch_errors);
-
+ UniValue reply{JSONRPCExec(jreq, catch_errors)};
if (jreq.IsNotification()) {
// Even though we do execute notifications, we do not respond to them
- req->WriteReply(HTTP_NO_CONTENT);
- return true;
+ status = HTTP_NO_CONTENT;
+ return {};
}
-
+ return reply;
// array of requests
} else if (valRequest.isArray()) {
// Check authorization for each request's method
@@ -183,15 +144,15 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
std::string strMethod = request.find_value("method").get_str();
if (!g_rpc_whitelist[jreq.authUser].contains(strMethod)) {
LogWarning("RPC User %s not allowed to call method %s", jreq.authUser, strMethod);
- req->WriteReply(HTTP_FORBIDDEN);
- return false;
+ status = HTTP_FORBIDDEN;
+ return {};
}
}
}
}
// Execute each request
- reply = UniValue::VARR;
+ UniValue reply = UniValue::VARR;
for (size_t i{0}; i < valRequest.size(); ++i) {
// Batches never throw HTTP errors, they are always just included
// in "HTTP OK" responses. Notifications never get any response.
@@ -218,23 +179,70 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
// empty response in this case to favor being backwards compatible
// over complying with the JSON-RPC 2.0 spec in this case.
if (reply.size() == 0 && valRequest.size() > 0) {
- req->WriteReply(HTTP_NO_CONTENT);
- return true;
+ status = HTTP_NO_CONTENT;
+ return {};
}
+ return reply;
}
else
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
-
- req->WriteHeader("Content-Type", "application/json");
- req->WriteReply(HTTP_OK, reply.write() + "\n");
} catch (UniValue& e) {
- JSONErrorReply(req, std::move(e), jreq);
- return false;
+ return JSONErrorReply(std::move(e), jreq, status);
} catch (const std::exception& e) {
- JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq);
- return false;
+ return JSONErrorReply(JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq, status);
+ }
+}
+
+static void HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
+{
+ // JSONRPC handles only POST
+ if (req->GetRequestMethod() != HTTPRequest::POST) {
+ req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests");
+ return;
+ }
+ // Check authorization
+ std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
+ if (!authHeader.first) {
+ req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
+ req->WriteReply(HTTP_UNAUTHORIZED);
+ return;
+ }
+
+ JSONRPCRequest jreq;
+ jreq.context = context;
+ jreq.peerAddr = req->GetPeer().ToStringAddrPort();
+ jreq.URI = req->GetURI();
+ if (!RPCAuthorized(authHeader.second, jreq.authUser)) {
+ LogWarning("ThreadRPCServer incorrect password attempt from %s", jreq.peerAddr);
+
+ /* Deter brute-forcing
+ If this results in a DoS the user really
+ shouldn't have their RPC port exposed. */
+ UninterruptibleSleep(std::chrono::milliseconds{250});
+
+ req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
+ req->WriteReply(HTTP_UNAUTHORIZED);
+ return;
+ }
+
+ // Generate reply
+ HTTPStatusCode status;
+ UniValue reply;
+ UniValue request;
+ if (request.read(req->ReadBody())) {
+ reply = ExecuteHTTPRPC(request, jreq, status);
+ } else {
+ reply = JSONErrorReply(JSONRPCError(RPC_PARSE_ERROR, "Parse error"), jreq, status);
+ }
+
+ // Write reply
+ if (reply.isNull()) {
+ // Error case or no-content notification reply.
+ req->WriteReply(status);
+ } else {
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(status, reply.write() + "\n");
}
- return true;
}
static bool InitRPCAuthentication()
diff --git a/src/httprpc.h b/src/httprpc.h
index 1c1a6241..511c82a4 100644
--- a/src/httprpc.h
+++ b/src/httprpc.h
@@ -7,6 +7,10 @@
#include <any>
+class JSONRPCRequest;
+class UniValue;
+enum HTTPStatusCode : int;
+
/** Start HTTP RPC subsystem.
* Precondition; HTTP and RPC has been started.
*/
@@ -19,6 +23,11 @@ void InterruptHTTPRPC();
*/
void StopHTTPRPC();
+/** Execute a single HTTP request containing one or more JSONRPC requests.
+ * Specified `jreq` will be modified and `status` will be returned.
+ */
+UniValue ExecuteHTTPRPC(const UniValue& valRequest, JSONRPCRequest& jreq, HTTPStatusCode& status);
+
/** Start HTTP REST subsystem.
* Precondition; HTTP and RPC has been started.
*/
diff --git a/src/httpserver.h b/src/httpserver.h
index 5461480d..76381a38 100644
--- a/src/httpserver.h
+++ b/src/httpserver.h
@@ -50,7 +50,7 @@ void StopHTTPServer();
void UpdateHTTPServerLogging(bool enable);
/** Handler for requests to a certain HTTP path */
-typedef std::function<bool(HTTPRequest* req, const std::string &)> HTTPRequestHandler;
+typedef std::function<void(HTTPRequest* req, const std::string &)> HTTPRequestHandler;
/** Register handler for prefix.
* If multiple handlers match a prefix, the first-registered one will
* be invoked.
diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h
index 3f18365c..40e685d5 100644
--- a/src/rpc/protocol.h
+++ b/src/rpc/protocol.h
@@ -7,7 +7,7 @@
#define BITCOIN_RPC_PROTOCOL_H
//! HTTP status codes
-enum HTTPStatusCode
+enum HTTPStatusCode : int
{
HTTP_OK = 200,
HTTP_NO_CONTENT = 204,
Why this scored 13/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.