1use base64::prelude::BASE64_STANDARD;
2use base64::Engine;
3use std::convert::{TryFrom, TryInto};
4use std::fmt;
5use std::io::Write;
6use std::sync::Arc;
7use ureq_proto::http::uri::{PathAndQuery, Scheme};
8use ureq_proto::parser::try_parse_response;
9
10use http::{StatusCode, Uri};
11
12use crate::config::DEFAULT_USER_AGENT;
13use crate::http;
14use crate::transport::{ConnectionDetails, Connector, Transport, TransportAdapter};
15use crate::util::{AuthorityExt, DebugUri, SchemeExt, UriExt};
16use crate::Error;
17
18#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
20#[non_exhaustive]
21pub(crate) enum Proto {
22 Http,
23 Https,
24 Socks4,
25 Socks4A,
26 Socks5,
27}
28
29impl Proto {
30 pub fn default_port(&self) -> u16 {
31 match self {
32 Proto::Http => 80,
33 Proto::Https => 443,
34 Proto::Socks4 | Proto::Socks4A | Proto::Socks5 => 1080,
35 }
36 }
37
38 pub fn is_socks(&self) -> bool {
39 matches!(self, Self::Socks4 | Self::Socks4A | Self::Socks5)
40 }
41
42 pub(crate) fn is_connect(&self) -> bool {
43 matches!(self, Self::Http | Self::Https)
44 }
45}
46
47#[derive(Clone, Eq, Hash, PartialEq)]
49pub struct Proxy {
50 inner: Arc<ProxyInner>,
51}
52
53#[derive(Eq, Hash, PartialEq)]
54struct ProxyInner {
55 proto: Proto,
56 uri: Uri,
57 from_env: bool,
58}
59
60impl Proxy {
61 pub fn new(proxy: &str) -> Result<Self, Error> {
83 Self::new_with_flag(proxy, false)
84 }
85
86 fn new_with_flag(proxy: &str, from_env: bool) -> Result<Self, Error> {
87 let mut uri = proxy.parse::<Uri>().or(Err(Error::InvalidProxyUrl))?;
88
89 let _ = uri.authority().ok_or(Error::InvalidProxyUrl)?;
92
93 let scheme = match uri.scheme_str() {
94 Some(v) => v,
95 None => {
96 uri = insert_default_scheme(uri);
99 "http"
100 }
101 };
102 let proto = scheme.try_into()?;
103
104 let inner = ProxyInner {
105 proto,
106 uri,
107 from_env,
108 };
109
110 Ok(Self {
111 inner: Arc::new(inner),
112 })
113 }
114
115 pub fn try_from_env() -> Option<Self> {
126 if let Ok(env) = std::env::var("ALL_PROXY") {
127 if let Ok(proxy) = Self::new_with_flag(&env, true) {
128 return Some(proxy);
129 }
130 }
131
132 if let Ok(env) = std::env::var("all_proxy") {
133 if let Ok(proxy) = Self::new_with_flag(&env, true) {
134 return Some(proxy);
135 }
136 }
137
138 if let Ok(env) = std::env::var("HTTPS_PROXY") {
139 if let Ok(proxy) = Self::new_with_flag(&env, true) {
140 return Some(proxy);
141 }
142 }
143
144 if let Ok(env) = std::env::var("https_proxy") {
145 if let Ok(proxy) = Self::new_with_flag(&env, true) {
146 return Some(proxy);
147 }
148 }
149
150 if let Ok(env) = std::env::var("HTTP_PROXY") {
151 if let Ok(proxy) = Self::new_with_flag(&env, true) {
152 return Some(proxy);
153 }
154 }
155
156 if let Ok(env) = std::env::var("http_proxy") {
157 if let Ok(proxy) = Self::new_with_flag(&env, true) {
158 return Some(proxy);
159 }
160 }
161 None
162 }
163
164 pub(crate) fn proto(&self) -> Proto {
165 self.inner.proto
166 }
167
168 pub fn uri(&self) -> &Uri {
170 &self.inner.uri
171 }
172
173 pub fn host(&self) -> &str {
175 self.inner
176 .uri
177 .authority()
178 .map(|a| a.host())
179 .expect("constructor to ensure there is an authority")
180 }
181
182 pub fn port(&self) -> u16 {
184 self.inner
185 .uri
186 .authority()
187 .and_then(|a| a.port_u16())
188 .unwrap_or_else(|| self.inner.proto.default_port())
189 }
190
191 pub fn username(&self) -> Option<&str> {
193 self.inner.uri.authority().and_then(|a| a.username())
194 }
195
196 pub fn password(&self) -> Option<&str> {
198 self.inner.uri.authority().and_then(|a| a.password())
199 }
200
201 pub fn is_from_env(&self) -> bool {
204 self.inner.from_env
205 }
206}
207
208fn insert_default_scheme(uri: Uri) -> Uri {
209 let mut parts = uri.into_parts();
210
211 parts.scheme = Some(Scheme::HTTP);
212
213 parts.path_and_query = parts
216 .path_and_query
217 .or_else(|| Some(PathAndQuery::from_static("/")));
218
219 Uri::from_parts(parts).unwrap()
220}
221
222#[derive(Default)]
227pub struct ConnectProxyConnector(());
228
229impl<In: Transport> Connector<In> for ConnectProxyConnector {
230 type Out = In;
231
232 fn connect(
233 &self,
234 details: &ConnectionDetails,
235 chained: Option<In>,
236 ) -> Result<Option<Self::Out>, Error> {
237 let Some(transport) = chained else {
238 return Ok(None);
239 };
240
241 let is_connect_proxy = details.config.connect_proxy_uri().is_some();
242 if !is_connect_proxy {
243 return Ok(Some(transport));
244 }
245
246 let proxy = details.config.proxy().unwrap();
248
249 let mut w = TransportAdapter::new(transport);
250
251 let proxied = details.proxied.unwrap();
254 proxied.ensure_valid_url()?;
255
256 let proxied_host = proxied.host().unwrap();
257 let proxied_port = proxied
258 .port_u16()
259 .unwrap_or(proxied.scheme().unwrap().default_port().unwrap());
260
261 write!(w, "CONNECT {}:{} HTTP/1.1\r\n", proxied_host, proxied_port)?;
262 write!(w, "Host: {}:{}\r\n", proxied_host, proxied_port)?;
263 if let Some(v) = details.config.user_agent().as_str(DEFAULT_USER_AGENT) {
264 write!(w, "User-Agent: {}\r\n", v)?;
265 }
266 write!(w, "Proxy-Connection: Keep-Alive\r\n")?;
267
268 let use_creds = proxy.username().is_some() || proxy.password().is_some();
269
270 if use_creds {
271 let user = proxy.username().unwrap_or_default();
272 let pass = proxy.password().unwrap_or_default();
273 let creds = BASE64_STANDARD.encode(format!("{}:{}", user, pass));
274 write!(w, "Proxy-Authorization: basic {}\r\n", creds)?;
275 }
276
277 write!(w, "\r\n")?;
278 w.flush()?;
279
280 let mut transport = w.into_inner();
281
282 let response = loop {
283 let made_progress = transport.await_input(details.timeout)?;
284 let buffers = transport.buffers();
285 let input = buffers.input();
286 let Some((used_input, response)) = try_parse_response::<20>(input)? else {
287 if !made_progress {
288 let reason = "proxy server did not respond".to_string();
289 return Err(Error::ConnectProxyFailed(reason));
290 }
291 continue;
292 };
293 buffers.input_consume(used_input);
294 break response;
295 };
296
297 match response.status() {
298 StatusCode::OK => {
299 trace!("CONNECT proxy connected");
300 }
301 x => {
302 let reason = format!("proxy server responded {}/{}", x.as_u16(), x.as_str());
303 return Err(Error::ConnectProxyFailed(reason));
304 }
305 }
306
307 Ok(Some(transport))
308 }
309}
310
311impl TryFrom<&str> for Proto {
312 type Error = Error;
313
314 fn try_from(scheme: &str) -> Result<Self, Self::Error> {
315 match scheme.to_ascii_lowercase().as_str() {
316 "http" => Ok(Proto::Http),
317 "https" => Ok(Proto::Https),
318 "socks4" => Ok(Proto::Socks4),
319 "socks4a" => Ok(Proto::Socks4A),
320 "socks" => Ok(Proto::Socks5),
321 "socks5" => Ok(Proto::Socks5),
322 _ => Err(Error::InvalidProxyUrl),
323 }
324 }
325}
326
327impl fmt::Debug for Proxy {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 f.debug_struct("Proxy")
330 .field("proto", &self.inner.proto)
331 .field("uri", &DebugUri(&self.inner.uri))
332 .field("from_env", &self.inner.from_env)
333 .finish()
334 }
335}
336
337impl fmt::Display for Proto {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 match self {
340 Proto::Http => write!(f, "HTTP"),
341 Proto::Https => write!(f, "HTTPS"),
342 Proto::Socks4 => write!(f, "SOCKS4"),
343 Proto::Socks4A => write!(f, "SOCKS4a"),
344 Proto::Socks5 => write!(f, "SOCKS5"),
345 }
346 }
347}
348
349impl fmt::Debug for ConnectProxyConnector {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 f.debug_struct("ProxyConnector").finish()
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn parse_proxy_fakeproto() {
361 assert!(Proxy::new("fakeproto://localhost").is_err());
362 }
363
364 #[test]
365 fn parse_proxy_http_user_pass_server_port() {
366 let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999").unwrap();
367 assert_eq!(proxy.username(), Some("user"));
368 assert_eq!(proxy.password(), Some("p@ssw0rd"));
369 assert_eq!(proxy.host(), "localhost");
370 assert_eq!(proxy.port(), 9999);
371 assert_eq!(proxy.inner.proto, Proto::Http);
372 }
373
374 #[test]
375 fn parse_proxy_http_user_pass_server_port_trailing_slash() {
376 let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999/").unwrap();
377 assert_eq!(proxy.username(), Some("user"));
378 assert_eq!(proxy.password(), Some("p@ssw0rd"));
379 assert_eq!(proxy.host(), "localhost");
380 assert_eq!(proxy.port(), 9999);
381 assert_eq!(proxy.inner.proto, Proto::Http);
382 }
383
384 #[test]
385 fn parse_proxy_socks4_user_pass_server_port() {
386 let proxy = Proxy::new("socks4://user:p@ssw0rd@localhost:9999").unwrap();
387 assert_eq!(proxy.username(), Some("user"));
388 assert_eq!(proxy.password(), Some("p@ssw0rd"));
389 assert_eq!(proxy.host(), "localhost");
390 assert_eq!(proxy.port(), 9999);
391 assert_eq!(proxy.inner.proto, Proto::Socks4);
392 }
393
394 #[test]
395 fn parse_proxy_socks4a_user_pass_server_port() {
396 let proxy = Proxy::new("socks4a://user:p@ssw0rd@localhost:9999").unwrap();
397 assert_eq!(proxy.username(), Some("user"));
398 assert_eq!(proxy.password(), Some("p@ssw0rd"));
399 assert_eq!(proxy.host(), "localhost");
400 assert_eq!(proxy.port(), 9999);
401 assert_eq!(proxy.inner.proto, Proto::Socks4A);
402 }
403
404 #[test]
405 fn parse_proxy_socks_user_pass_server_port() {
406 let proxy = Proxy::new("socks://user:p@ssw0rd@localhost:9999").unwrap();
407 assert_eq!(proxy.username(), Some("user"));
408 assert_eq!(proxy.password(), Some("p@ssw0rd"));
409 assert_eq!(proxy.host(), "localhost");
410 assert_eq!(proxy.port(), 9999);
411 assert_eq!(proxy.inner.proto, Proto::Socks5);
412 }
413
414 #[test]
415 fn parse_proxy_socks5_user_pass_server_port() {
416 let proxy = Proxy::new("socks5://user:p@ssw0rd@localhost:9999").unwrap();
417 assert_eq!(proxy.username(), Some("user"));
418 assert_eq!(proxy.password(), Some("p@ssw0rd"));
419 assert_eq!(proxy.host(), "localhost");
420 assert_eq!(proxy.port(), 9999);
421 assert_eq!(proxy.inner.proto, Proto::Socks5);
422 }
423
424 #[test]
425 fn parse_proxy_user_pass_server_port() {
426 let proxy = Proxy::new("user:p@ssw0rd@localhost:9999").unwrap();
427 assert_eq!(proxy.username(), Some("user"));
428 assert_eq!(proxy.password(), Some("p@ssw0rd"));
429 assert_eq!(proxy.host(), "localhost");
430 assert_eq!(proxy.port(), 9999);
431 assert_eq!(proxy.inner.proto, Proto::Http);
432 }
433
434 #[test]
435 fn parse_proxy_server_port() {
436 let proxy = Proxy::new("localhost:9999").unwrap();
437 assert_eq!(proxy.username(), None);
438 assert_eq!(proxy.password(), None);
439 assert_eq!(proxy.host(), "localhost");
440 assert_eq!(proxy.port(), 9999);
441 assert_eq!(proxy.inner.proto, Proto::Http);
442 }
443
444 #[test]
445 fn parse_proxy_server() {
446 let proxy = Proxy::new("localhost").unwrap();
447 assert_eq!(proxy.username(), None);
448 assert_eq!(proxy.password(), None);
449 assert_eq!(proxy.host(), "localhost");
450 assert_eq!(proxy.port(), 80);
451 assert_eq!(proxy.inner.proto, Proto::Http);
452 }
453}
454
455#[cfg(test)]
456mod test {
457 use super::*;
458 use assert_no_alloc::*;
459
460 #[test]
461 fn proxy_clone_does_not_allocate() {
462 let c = Proxy::new("socks://1.2.3.4").unwrap();
463 assert_no_alloc(|| c.clone());
464 }
465
466 #[test]
467 fn proxy_new_default_scheme() {
468 let c = Proxy::new("localhost:1234").unwrap();
469 assert_eq!(c.proto(), Proto::Http);
470 assert_eq!(c.uri(), "http://localhost:1234");
471 }
472
473 #[test]
474 fn proxy_empty_env_url() {
475 let result = Proxy::new_with_flag("", false);
476 assert!(result.is_err());
477 }
478
479 #[test]
480 fn proxy_invalid_env_url() {
481 let result = Proxy::new_with_flag("r32/?//52:**", false);
482 assert!(result.is_err());
483 }
484}