http_quik/client/
proxy.rs1use tokio::io::{AsyncReadExt, AsyncWriteExt};
2use tokio::net::TcpStream;
3use url::Url;
4
5use crate::error::{Error, Result};
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9pub enum Proxy {
10 Http(String),
12 Socks5(String),
14}
15
16impl Proxy {
17 pub fn parse(url: &str) -> Result<Self> {
22 let parsed = Url::parse(url).map_err(|e| Error::InvalidUrl(e.to_string()))?;
23 let addr = format!(
24 "{}:{}",
25 parsed.host_str().unwrap_or(""),
26 parsed.port().unwrap_or(1080)
27 );
28
29 match parsed.scheme() {
30 "http" => Ok(Proxy::Http(addr)),
31 "socks5" => Ok(Proxy::Socks5(addr)),
32 _ => Err(Error::InvalidUrl("Unsupported proxy scheme".to_string())),
33 }
34 }
35}
36
37pub async fn dial_proxy(proxy: &Proxy, target_host: &str, target_port: u16) -> Result<TcpStream> {
42 match proxy {
43 Proxy::Http(addr) => dial_http_proxy(addr, target_host, target_port).await,
44 Proxy::Socks5(addr) => dial_socks5_proxy(addr, target_host, target_port).await,
45 }
46}
47
48async fn dial_http_proxy(
50 proxy_addr: &str,
51 target_host: &str,
52 target_port: u16,
53) -> Result<TcpStream> {
54 let mut stream = TcpStream::connect(proxy_addr).await?;
55
56 let connect_req = format!(
57 "CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n",
58 target_host, target_port, target_host, target_port
59 );
60
61 stream.write_all(connect_req.as_bytes()).await?;
62
63 let mut buf = [0; 4096];
64 let mut read_bytes = 0;
65
66 loop {
68 let n = stream.read(&mut buf[read_bytes..]).await?;
69 if n == 0 {
70 return Err(Error::Connect(std::io::Error::new(
71 std::io::ErrorKind::ConnectionAborted,
72 "Proxy closed connection during CONNECT handshake",
73 )));
74 }
75 read_bytes += n;
76
77 let response = String::from_utf8_lossy(&buf[..read_bytes]);
78 if response.contains("\r\n\r\n") {
79 if response.starts_with("HTTP/1.1 200") || response.starts_with("HTTP/1.0 200") {
80 return Ok(stream);
81 } else {
82 return Err(Error::Connect(std::io::Error::other(format!(
83 "HTTP proxy returned error status: {}",
84 response.lines().next().unwrap_or("Unknown")
85 ))));
86 }
87 }
88 }
89}
90
91async fn dial_socks5_proxy(
93 proxy_addr: &str,
94 target_host: &str,
95 target_port: u16,
96) -> Result<TcpStream> {
97 let mut stream = TcpStream::connect(proxy_addr).await?;
98
99 stream.write_all(&[0x05, 0x01, 0x00]).await?;
101
102 let mut response = [0; 2];
103 stream.read_exact(&mut response).await?;
104
105 if response[0] != 0x05 || response[1] != 0x00 {
106 return Err(Error::Connect(std::io::Error::other(
107 "SOCKS5 server rejected 'No Authentication' method",
108 )));
109 }
110
111 let host_bytes = target_host.as_bytes();
113 let mut req = vec![0x05, 0x01, 0x00, 0x03, host_bytes.len() as u8];
114 req.extend_from_slice(host_bytes);
115 req.extend_from_slice(&target_port.to_be_bytes());
116
117 stream.write_all(&req).await?;
118
119 let mut resp_header = [0; 4];
121 stream.read_exact(&mut resp_header).await?;
122
123 if resp_header[0] != 0x05 || resp_header[1] != 0x00 {
124 return Err(Error::Connect(std::io::Error::other(format!(
125 "SOCKS5 connection request failed with status code: {}",
126 resp_header[1]
127 ))));
128 }
129
130 let addr_type = resp_header[3];
132 match addr_type {
133 0x01 => {
134 let mut addr = [0; 4];
135 stream.read_exact(&mut addr).await?;
136 }
137 0x03 => {
138 let mut len = [0; 1];
139 stream.read_exact(&mut len).await?;
140 let mut domain = vec![0; len[0] as usize];
141 stream.read_exact(&mut domain).await?;
142 }
143 0x04 => {
144 let mut addr = [0; 16];
145 stream.read_exact(&mut addr).await?;
146 }
147 _ => {
148 return Err(Error::Connect(std::io::Error::other(
149 "SOCKS5 server returned unsupported address type",
150 )))
151 }
152 }
153
154 let mut port = [0; 2];
155 stream.read_exact(&mut port).await?;
156
157 Ok(stream)
158}