use std::fmt;
use std::time::Duration;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64;
use ureq::{Agent, Proxy};
use crate::rpc::jsonrpc::{self, Request, Response};
pub struct Socks5Transport {
url: String,
agent: Agent,
basic_auth: Option<String>,
}
impl fmt::Debug for Socks5Transport {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Socks5Transport")
.field("url", &self.url)
.field("has_auth", &self.basic_auth.is_some())
.finish()
}
}
impl Socks5Transport {
pub fn new(
url: &str,
proxy_url: &str,
auth: Option<(String, Option<String>)>,
) -> Result<Self, Error> {
let proxy = Proxy::new(proxy_url)
.map_err(|e| Error::Proxy(e.to_string()))?;
let agent = Agent::config_builder()
.proxy(Some(proxy))
.timeout_global(Some(Duration::from_secs(60)))
.build()
.new_agent();
let basic_auth = auth.map(|(user, pass)| {
let credentials = format!("{}:{}", user, pass.unwrap_or_default());
format!("Basic {}", BASE64.encode(&credentials))
});
Ok(Socks5Transport { url: url.to_owned(), agent, basic_auth })
}
fn request<R>(&self, req: impl serde::Serialize) -> Result<R, jsonrpc::Error>
where
R: for<'a> serde::de::Deserialize<'a>,
{
let body = serde_json::to_vec(&req)
.map_err(|e| jsonrpc::Error::Transport(e.into()))?;
let mut request = self.agent.post(&self.url)
.header("Content-Type", "application/json");
if let Some(ref auth) = self.basic_auth {
request = request.header("Authorization", auth);
}
let resp = request
.send(&body[..])
.map_err(|e| jsonrpc::Error::Transport(e.into()))?;
let status = resp.status().as_u16();
if status < 200 || status >= 300 {
return Err(jsonrpc::Error::Transport(
Box::new(Error::Http(status)),
));
}
let resp_body = resp.into_body().read_to_string()
.map_err(|e| jsonrpc::Error::Transport(e.into()))?;
serde_json::from_str(&resp_body)
.map_err(|e| jsonrpc::Error::Transport(e.into()))
}
}
impl jsonrpc::client::Transport for Socks5Transport {
fn send_request(&self, req: Request) -> Result<Response, jsonrpc::Error> {
self.request(req)
}
fn send_batch(&self, reqs: &[Request]) -> Result<Vec<Response>, jsonrpc::Error> {
self.request(reqs)
}
fn fmt_target(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} (via socks5 proxy)", self.url)
}
}
#[derive(Debug)]
pub enum Error {
Proxy(String),
Http(u16),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Proxy(e) => write!(f, "invalid proxy URL: {}", e),
Error::Http(code) => write!(f, "HTTP error {}", code),
}
}
}
impl std::error::Error for Error {}