1//! client proxy
2
3mod http_proxy;
4mod socks_proxy;
5
6use anyhow::{Context as _, Result};
7use http_client::Url;
8use http_proxy::{HttpProxyType, connect_http_proxy_stream, parse_http_proxy};
9use socks_proxy::{SocksVersion, connect_socks_proxy_stream, parse_socks_proxy};
10
11pub(crate) async fn connect_proxy_stream(
12 proxy: &Url,
13 rpc_host: (&str, u16),
14) -> Result<Box<dyn AsyncReadWrite>> {
15 let Some(((proxy_domain, proxy_port), proxy_type)) = parse_proxy_type(proxy) else {
16 // If parsing the proxy URL fails, we must avoid falling back to an insecure connection.
17 // SOCKS proxies are often used in contexts where security and privacy are critical,
18 // so any fallback could expose users to significant risks.
19 anyhow::bail!("Parsing proxy url failed");
20 };
21
22 // Connect to proxy and wrap protocol later
23 let stream = tokio::net::TcpStream::connect((proxy_domain.as_str(), proxy_port))
24 .await
25 .context("Failed to connect to proxy")?;
26
27 let proxy_stream = match proxy_type {
28 ProxyType::SocksProxy(proxy) => connect_socks_proxy_stream(stream, proxy, rpc_host).await?,
29 ProxyType::HttpProxy(proxy) => {
30 connect_http_proxy_stream(stream, proxy, rpc_host, &proxy_domain).await?
31 }
32 };
33
34 Ok(proxy_stream)
35}
36
37enum ProxyType<'t> {
38 SocksProxy(SocksVersion<'t>),
39 HttpProxy(HttpProxyType<'t>),
40}
41
42fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType<'_>)> {
43 let scheme = proxy.scheme();
44 let host = proxy.host()?.to_string();
45 let port = proxy.port_or_known_default()?;
46 let proxy_type = match scheme {
47 scheme if scheme.starts_with("socks") => {
48 Some(ProxyType::SocksProxy(parse_socks_proxy(scheme, proxy)))
49 }
50 scheme if scheme.starts_with("http") => {
51 Some(ProxyType::HttpProxy(parse_http_proxy(scheme, proxy)))
52 }
53 _ => None,
54 }?;
55
56 Some(((host, port), proxy_type))
57}
58
59pub(crate) trait AsyncReadWrite:
60 tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static
61{
62}
63impl<T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static> AsyncReadWrite
64 for T
65{
66}