Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(rpc): ignore non-standard "jsonrpc = 1.0" in lightwalletd requests #173

Merged
merged 3 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions zebra-rpc/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ pub trait Rpc {
/// getinfo
#[rpc(name = "getinfo")]
fn getinfo(&self) -> Result<GetInfo>;

/// getblockchaininfo
#[rpc(name = "getblockchaininfo")]
fn getblockchaininfo(&self) -> Result<GetBlockChainInfo>;
}

/// RPC method implementations.
Expand All @@ -25,6 +29,15 @@ impl Rpc for RpcImpl {

Ok(info)
}

fn getblockchaininfo(&self) -> Result<GetBlockChainInfo> {
// TODO: dummy output data, fix in the context of #3143
let response = GetBlockChainInfo {
chain: "main".to_string(),
};

Ok(response)
}
}

#[derive(serde::Serialize, serde::Deserialize)]
Expand All @@ -33,3 +46,10 @@ pub struct GetInfo {
build: String,
subversion: String,
}

#[derive(serde::Serialize, serde::Deserialize)]
/// Response to a `getblockchaininfo` RPC request.
pub struct GetBlockChainInfo {
chain: String,
// TODO: add other fields used by lightwalletd (#3143)
}
79 changes: 77 additions & 2 deletions zebrad/src/components/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! An RPC endpoint.

use futures::TryStreamExt;
use hyper::{body::Bytes, Body};
use jsonrpc_core;

use jsonrpc_http_server::ServerBuilder;
use jsonrpc_http_server::{RequestMiddleware, ServerBuilder};

use zebra_rpc::rpc::{Rpc, RpcImpl};

Expand All @@ -24,7 +25,12 @@ impl RpcServer {
io.extend_with(RpcImpl.to_delegate());

let server = ServerBuilder::new(io)
// TODO: use the same tokio executor as the rest of Zebra
//.event_loop_executor(tokio::runtime::Handle::current())
.threads(1)
// TODO: disable this security check if we see errors from lightwalletd.
//.allowed_hosts(DomainsValidation::Disabled)
.request_middleware(FixHttpRequestMiddleware)
.start_http(&config.listen_addr)
.expect("Unable to start RPC server");

Expand All @@ -35,3 +41,72 @@ impl RpcServer {
RpcServer {}
}
}

/// HTTP [`RequestMiddleware`] with compatibility wrokarounds.
///
/// This middleware makes the following changes to requests:
///
/// ## JSON RPC 1.0 `jsonrpc` field
///
/// Removes "jsonrpc: 1.0" fields from requests,
/// because the "jsonrpc" field was only added in JSON-RPC 2.0.
///
/// <http://www.simple-is-better.org/rpc/#differences-between-1-0-and-2-0>
//
// TODO: put this HTTP middleware in a separate module
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct FixHttpRequestMiddleware;

impl RequestMiddleware for FixHttpRequestMiddleware {
fn on_request(
&self,
request: hyper::Request<hyper::Body>,
) -> jsonrpc_http_server::RequestMiddlewareAction {
let request = request.map(|body| {
let body = body.map_ok(|data| {
// To simplify data handling, we assume that any search strings won't be split
// across multiple `Bytes` data buffers.
//
// To simplify error handling, Zebra only supports valid UTF-8 requests,
// and uses lossy UTF-8 conversion.
//
// JSON-RPC requires all requests to be valid UTF-8.
// The lower layers should reject invalid requests with lossy changes.
// But if they accept some lossy changes, that's ok,
// because the request was non-standard anyway.
//
// We're not concerned about performance here, so we just clone the Cow<str>
let data = String::from_utf8_lossy(data.as_ref()).to_string();

// Fix up the request.
let data = Self::remove_json_1_fields(data);

Bytes::from(data)
});

Body::wrap_stream(body)
});

jsonrpc_http_server::RequestMiddlewareAction::Proceed {
// TODO: disable this security check if we see errors from lightwalletd.
should_continue_on_invalid_cors: false,
request,
}
}
}

impl FixHttpRequestMiddleware {
/// Remove any "jsonrpc: 1.0" fields in `data`, and return the resulting string.
pub fn remove_json_1_fields(data: String) -> String {
// Replace "jsonrpc = 1.0":
// - at the start or middle of a list, and
// - at the end of a list;
// with no spaces (lightwalletd format), and spaces after separators (example format).
//
// TODO: replace this with a regular expression if we see errors from lightwalletd.
data.replace("\"jsonrpc\":\"1.0\",", "")
.replace("\"jsonrpc\": \"1.0\",", "")
.replace(",\"jsonrpc\":\"1.0\"", "")
.replace(", \"jsonrpc\": \"1.0\"", "")
}
}