1use base64::{prelude::BASE64_STANDARD, Engine};
2
3use crate::{
4 error::{Error, ErrorKind},
5 Response,
6};
7
8#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
10pub enum Proto {
11 HTTP,
12 SOCKS4,
13 SOCKS4A,
14 SOCKS5,
15}
16
17#[derive(Clone, Debug, Eq, Hash, PartialEq)]
19pub struct Proxy {
20 pub(crate) server: String,
21 pub(crate) port: u32,
22 pub(crate) user: Option<String>,
23 pub(crate) password: Option<String>,
24 pub(crate) proto: Proto,
25}
26
27impl Proxy {
28 fn parse_creds<S: AsRef<str>>(
29 creds: &Option<S>,
30 ) -> Result<(Option<String>, Option<String>), Error> {
31 match creds {
32 Some(creds) => {
33 let mut parts = creds
34 .as_ref()
35 .splitn(2, ':')
36 .collect::<Vec<&str>>()
37 .into_iter();
38
39 if parts.len() != 2 {
40 Err(ErrorKind::InvalidProxyUrl.new())
41 } else {
42 Ok((
43 parts.next().map(String::from),
44 parts.next().map(String::from),
45 ))
46 }
47 }
48 None => Ok((None, None)),
49 }
50 }
51
52 fn parse_address<S: AsRef<str>>(host: &Option<S>) -> Result<(String, Option<u32>), Error> {
53 match host {
54 Some(host) => {
55 let mut parts = host.as_ref().split(':').collect::<Vec<&str>>().into_iter();
56 let host = parts
57 .next()
58 .ok_or_else(|| ErrorKind::InvalidProxyUrl.new())?;
59 let port = parts.next();
60 Ok((
61 String::from(host),
62 port.and_then(|port| port.parse::<u32>().ok()),
63 ))
64 }
65 None => Err(ErrorKind::InvalidProxyUrl.new()),
66 }
67 }
68
69 pub(crate) fn use_authorization(&self) -> bool {
70 self.user.is_some() && self.password.is_some()
71 }
72
73 pub(crate) fn try_from_system() -> Option<Self> {
74 macro_rules! try_env {
75 ($($env:literal),+) => {
76 $(
77 if let Ok(env) = std::env::var($env) {
78 if let Ok(proxy) = Self::new(env) {
79 return Some(proxy);
80 }
81 }
82 )+
83 };
84 }
85
86 try_env!(
87 "ALL_PROXY",
88 "all_proxy",
89 "HTTPS_PROXY",
90 "https_proxy",
91 "HTTP_PROXY",
92 "http_proxy"
93 );
94 None
95 }
96
97 pub fn new<S: AsRef<str>>(proxy: S) -> Result<Self, Error> {
111 let mut proxy = proxy.as_ref();
112
113 while proxy.ends_with('/') {
114 proxy = &proxy[..(proxy.len() - 1)];
115 }
116
117 let mut proxy_parts = proxy.splitn(2, "://").collect::<Vec<&str>>().into_iter();
118
119 let proto = if proxy_parts.len() == 2 {
120 match proxy_parts.next() {
121 Some("http") => Proto::HTTP,
122 Some("socks4") => Proto::SOCKS4,
123 Some("socks4a") => Proto::SOCKS4A,
124 Some("socks") => Proto::SOCKS5,
125 Some("socks5") => Proto::SOCKS5,
126 _ => return Err(ErrorKind::InvalidProxyUrl.new()),
127 }
128 } else {
129 Proto::HTTP
130 };
131
132 let remaining_parts = proxy_parts.next();
133 if remaining_parts.is_none() {
134 return Err(ErrorKind::InvalidProxyUrl.new());
135 }
136
137 let mut creds_server_port_parts = remaining_parts
138 .unwrap()
139 .rsplitn(2, '@')
140 .collect::<Vec<&str>>()
141 .into_iter()
142 .rev();
143
144 let (user, password) = if creds_server_port_parts.len() == 2 {
145 Proxy::parse_creds(&creds_server_port_parts.next())?
146 } else {
147 (None, None)
148 };
149
150 let (server, port) = Proxy::parse_address(&creds_server_port_parts.next())?;
151
152 Ok(Self {
153 server,
154 user,
155 password,
156 port: port.unwrap_or(match proto {
157 Proto::HTTP => 80,
158 Proto::SOCKS4 | Proto::SOCKS4A | Proto::SOCKS5 => 1080,
159 }),
160 proto,
161 })
162 }
163
164 pub(crate) fn connect<S: AsRef<str>>(&self, host: S, port: u16, user_agent: &str) -> String {
165 let authorization = if self.use_authorization() {
166 let creds = BASE64_STANDARD.encode(format!(
167 "{}:{}",
168 self.user.clone().unwrap_or_default(),
169 self.password.clone().unwrap_or_default()
170 ));
171
172 match self.proto {
173 Proto::HTTP => format!("Proxy-Authorization: basic {}\r\n", creds),
174 _ => String::new(),
175 }
176 } else {
177 String::new()
178 };
179
180 format!(
181 "CONNECT {}:{} HTTP/1.1\r\n\
182Host: {}:{}\r\n\
183User-Agent: {}\r\n\
184Proxy-Connection: Keep-Alive\r\n\
185{}\
186\r\n",
187 host.as_ref(),
188 port,
189 host.as_ref(),
190 port,
191 user_agent,
192 authorization
193 )
194 }
195
196 pub(crate) fn verify_response(response: &Response) -> Result<(), Error> {
197 match response.status() {
198 200 => Ok(()),
199 401 | 407 => Err(ErrorKind::ProxyUnauthorized.new()),
200 _ => Err(ErrorKind::ProxyConnect.new()),
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn parse_proxy_fakeproto() {
211 assert!(Proxy::new("fakeproto://localhost").is_err());
212 }
213
214 #[test]
215 fn parse_proxy_http_user_pass_server_port() {
216 let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999").unwrap();
217 assert_eq!(proxy.user, Some(String::from("user")));
218 assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
219 assert_eq!(proxy.server, String::from("localhost"));
220 assert_eq!(proxy.port, 9999);
221 assert_eq!(proxy.proto, Proto::HTTP);
222 }
223
224 #[test]
225 fn parse_proxy_http_user_pass_server_port_trailing_slash() {
226 let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999/").unwrap();
227 assert_eq!(proxy.user, Some(String::from("user")));
228 assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
229 assert_eq!(proxy.server, String::from("localhost"));
230 assert_eq!(proxy.port, 9999);
231 assert_eq!(proxy.proto, Proto::HTTP);
232 }
233
234 #[cfg(feature = "socks-proxy")]
235 #[test]
236 fn parse_proxy_socks4_user_pass_server_port() {
237 let proxy = Proxy::new("socks4://user:p@ssw0rd@localhost:9999").unwrap();
238 assert_eq!(proxy.user, Some(String::from("user")));
239 assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
240 assert_eq!(proxy.server, String::from("localhost"));
241 assert_eq!(proxy.port, 9999);
242 assert_eq!(proxy.proto, Proto::SOCKS4);
243 }
244
245 #[cfg(feature = "socks-proxy")]
246 #[test]
247 fn parse_proxy_socks4a_user_pass_server_port() {
248 let proxy = Proxy::new("socks4a://user:p@ssw0rd@localhost:9999").unwrap();
249 assert_eq!(proxy.user, Some(String::from("user")));
250 assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
251 assert_eq!(proxy.server, String::from("localhost"));
252 assert_eq!(proxy.port, 9999);
253 assert_eq!(proxy.proto, Proto::SOCKS4A);
254 }
255
256 #[cfg(feature = "socks-proxy")]
257 #[test]
258 fn parse_proxy_socks_user_pass_server_port() {
259 let proxy = Proxy::new("socks://user:p@ssw0rd@localhost:9999").unwrap();
260 assert_eq!(proxy.user, Some(String::from("user")));
261 assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
262 assert_eq!(proxy.server, String::from("localhost"));
263 assert_eq!(proxy.port, 9999);
264 assert_eq!(proxy.proto, Proto::SOCKS5);
265 }
266
267 #[cfg(feature = "socks-proxy")]
268 #[test]
269 fn parse_proxy_socks5_user_pass_server_port() {
270 let proxy = Proxy::new("socks5://user:p@ssw0rd@localhost:9999").unwrap();
271 assert_eq!(proxy.user, Some(String::from("user")));
272 assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
273 assert_eq!(proxy.server, String::from("localhost"));
274 assert_eq!(proxy.port, 9999);
275 assert_eq!(proxy.proto, Proto::SOCKS5);
276 }
277
278 #[test]
279 fn parse_proxy_user_pass_server_port() {
280 let proxy = Proxy::new("user:p@ssw0rd@localhost:9999").unwrap();
281 assert_eq!(proxy.user, Some(String::from("user")));
282 assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
283 assert_eq!(proxy.server, String::from("localhost"));
284 assert_eq!(proxy.port, 9999);
285 }
286
287 #[test]
288 fn parse_proxy_server_port() {
289 let proxy = Proxy::new("localhost:9999").unwrap();
290 assert_eq!(proxy.user, None);
291 assert_eq!(proxy.password, None);
292 assert_eq!(proxy.server, String::from("localhost"));
293 assert_eq!(proxy.port, 9999);
294 }
295
296 #[test]
297 fn parse_proxy_server() {
298 let proxy = Proxy::new("localhost").unwrap();
299 assert_eq!(proxy.user, None);
300 assert_eq!(proxy.password, None);
301 assert_eq!(proxy.server, String::from("localhost"));
302 }
303}