1use std::io::{Read, Write};
9
10use thiserror::Error;
11
12use crate::coroutine::{ProxyCoroutine, ProxyCoroutineState, ProxyYield};
13#[cfg(feature = "http")]
14use crate::http::connect::{HttpConnect, HttpCredentials};
15#[cfg(feature = "socks5")]
16use crate::socks::v5::{
17 address::Socks5Address,
18 auth::Socks5Credentials,
19 connect::{Socks5Connect, Socks5ConnectError},
20};
21
22const READ_BUFFER_SIZE: usize = 258;
25
26#[derive(Debug, Error)]
28pub enum ProxyClientError {
29 #[cfg(feature = "socks5")]
31 #[error(transparent)]
32 Socks5(#[from] Socks5ConnectError),
33 #[cfg(feature = "http")]
35 #[error(transparent)]
36 Http(#[from] crate::http::connect::HttpConnectError),
37 #[error(transparent)]
39 Io(#[from] std::io::Error),
40}
41
42fn run<S, C, E>(stream: &mut S, mut coroutine: C) -> Result<(), ProxyClientError>
47where
48 S: Read + Write,
49 C: ProxyCoroutine<Yield = ProxyYield, Return = Result<(), E>>,
50 ProxyClientError: From<E>,
51{
52 let mut buf = [0u8; READ_BUFFER_SIZE];
53 let mut arg: Option<&[u8]> = None;
54
55 loop {
56 match coroutine.resume(arg.take()) {
57 ProxyCoroutineState::Complete(Ok(())) => return Ok(()),
58 ProxyCoroutineState::Complete(Err(err)) => return Err(err.into()),
59 ProxyCoroutineState::Yielded(ProxyYield::WantsWrite(bytes)) => {
60 stream.write_all(&bytes)?;
61 arg = None;
62 }
63 ProxyCoroutineState::Yielded(ProxyYield::WantsRead(n)) => {
64 stream.read_exact(&mut buf[..n])?;
65 arg = Some(&buf[..n]);
66 }
67 }
68 }
69}
70
71#[cfg(feature = "socks5")]
75#[cfg_attr(docsrs, doc(cfg(feature = "socks5")))]
76pub fn connect_socks5<S: Read + Write>(
77 stream: &mut S,
78 target: Socks5Address,
79 credentials: Option<Socks5Credentials>,
80) -> Result<(), ProxyClientError> {
81 run(stream, Socks5Connect::new(target, credentials))
82}
83
84#[cfg(feature = "http")]
88#[cfg_attr(docsrs, doc(cfg(feature = "http")))]
89pub fn connect_http<S: Read + Write>(
90 stream: &mut S,
91 host: &str,
92 port: u16,
93 credentials: Option<HttpCredentials>,
94) -> Result<(), ProxyClientError> {
95 run(stream, HttpConnect::new(host, port, credentials))
96}
97
98#[cfg(all(test, feature = "socks5"))]
99mod socks5_tests {
100 use std::{
101 io::{self, Cursor, Read, Write},
102 vec::Vec,
103 };
104
105 use super::*;
106
107 struct Fake {
109 to_read: Cursor<Vec<u8>>,
110 written: Vec<u8>,
111 }
112
113 impl Read for Fake {
114 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
115 self.to_read.read(buf)
116 }
117 }
118
119 impl Write for Fake {
120 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
121 self.written.extend_from_slice(buf);
122 Ok(buf.len())
123 }
124 fn flush(&mut self) -> io::Result<()> {
125 Ok(())
126 }
127 }
128
129 #[test]
130 fn drives_handshake_and_leaves_tunnel_bytes() {
131 let mut server = vec![0x05, 0x00];
134 server.extend_from_slice(&[0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]);
135 server.push(0xAB); let mut fake = Fake {
138 to_read: Cursor::new(server),
139 written: Vec::new(),
140 };
141
142 connect_socks5(
143 &mut fake,
144 Socks5Address::Domain("example.com".into(), 993),
145 None,
146 )
147 .unwrap();
148
149 assert_eq!(&fake.written[..3], [0x05, 0x01, 0x00]);
151
152 let mut rest = Vec::new();
154 fake.read_to_end(&mut rest).unwrap();
155 assert_eq!(rest, [0xAB]);
156 }
157}
158
159#[cfg(all(test, feature = "http"))]
160mod http_tests {
161 use std::{
162 io::{self, Cursor, Read, Write},
163 vec::Vec,
164 };
165
166 use super::*;
167
168 struct Fake {
169 to_read: Cursor<Vec<u8>>,
170 written: Vec<u8>,
171 }
172
173 impl Read for Fake {
174 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
175 self.to_read.read(buf)
176 }
177 }
178
179 impl Write for Fake {
180 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
181 self.written.extend_from_slice(buf);
182 Ok(buf.len())
183 }
184 fn flush(&mut self) -> io::Result<()> {
185 Ok(())
186 }
187 }
188
189 #[test]
190 fn drives_connect_and_leaves_tunnel_bytes() {
191 let mut server = b"HTTP/1.1 200 Connection established\r\n\r\n".to_vec();
194 server.push(0xAB);
195
196 let mut fake = Fake {
197 to_read: Cursor::new(server),
198 written: Vec::new(),
199 };
200
201 connect_http(&mut fake, "imap.example.com", 993, None).unwrap();
202
203 assert!(fake.written.starts_with(b"CONNECT imap.example.com:993"));
204
205 let mut rest = Vec::new();
206 fake.read_to_end(&mut rest).unwrap();
207 assert_eq!(rest, [0xAB]);
208 }
209}