arcbox_proxy/
http_connect.rs1use std::io;
9
10use tokio::io::{AsyncReadExt, AsyncWriteExt};
11use tokio::net::TcpStream;
12
13pub async fn connect_via_http_proxy(proxy: &str, host: &str, port: u16) -> io::Result<TcpStream> {
18 let mut stream = TcpStream::connect(proxy).await?;
19
20 let request = format!("CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n\r\n");
21 stream.write_all(request.as_bytes()).await?;
22
23 let mut buf = Vec::with_capacity(256);
26 let mut tmp = [0u8; 1];
27 loop {
28 let n = stream.read(&mut tmp).await?;
29 if n == 0 {
30 return Err(io::Error::new(
31 io::ErrorKind::UnexpectedEof,
32 "proxy closed connection before completing status line",
33 ));
34 }
35 buf.push(tmp[0]);
36 if buf.ends_with(b"\r\n") {
37 break;
38 }
39 if buf.len() > 1024 {
40 return Err(io::Error::other(
41 "HTTP CONNECT status line exceeds 1024 bytes",
42 ));
43 }
44 }
45
46 let status_line = String::from_utf8_lossy(&buf);
47 let status_ok = status_line.starts_with("HTTP/1.1 2") || status_line.starts_with("HTTP/1.0 2");
49
50 if !status_ok {
51 let line = status_line.trim_end();
52 return Err(io::Error::other(format!(
53 "HTTP CONNECT proxy rejected: {line}"
54 )));
55 }
56
57 let mut header_buf = Vec::with_capacity(512);
61 loop {
62 let n = stream.read(&mut tmp).await?;
63 if n == 0 {
64 break;
65 }
66 header_buf.push(tmp[0]);
67 if header_buf.len() >= 2
71 && header_buf.ends_with(b"\r\n")
72 && (header_buf.len() == 2 || header_buf[..header_buf.len() - 2].ends_with(b"\r\n"))
73 {
74 break;
75 }
76 if header_buf.len() > 8192 {
77 return Err(io::Error::other(
78 "HTTP CONNECT response headers exceed 8192 bytes",
79 ));
80 }
81 }
82
83 tracing::debug!(
84 proxy = proxy,
85 target = %format!("{host}:{port}"),
86 "HTTP CONNECT tunnel established"
87 );
88 Ok(stream)
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use tokio::net::TcpListener;
95
96 #[tokio::test]
98 async fn http_connect_establishes_tunnel() {
99 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
100 let addr = listener.local_addr().unwrap();
101 let server = tokio::spawn(async move {
102 let (mut s, _) = listener.accept().await.unwrap();
103 let mut buf = Vec::new();
104 let mut byte = [0u8; 1];
105 while !buf.ends_with(b"\r\n\r\n") {
106 s.read_exact(&mut byte).await.unwrap();
107 buf.push(byte[0]);
108 }
109 let req = String::from_utf8_lossy(&buf);
110 assert!(req.starts_with("CONNECT example.com:443 HTTP/1.1"), "{req}");
111 s.write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n")
112 .await
113 .unwrap();
114 let mut echo = [0u8; 3];
115 s.read_exact(&mut echo).await.unwrap();
116 s.write_all(&echo).await.unwrap();
117 });
118
119 let mut stream = connect_via_http_proxy(&addr.to_string(), "example.com", 443)
120 .await
121 .expect("HTTP CONNECT should establish");
122 stream.write_all(b"abc").await.unwrap();
123 let mut got = [0u8; 3];
124 stream.read_exact(&mut got).await.unwrap();
125 assert_eq!(&got, b"abc");
126 server.await.unwrap();
127 }
128}