1use std::convert::{TryFrom, TryInto};
2use std::fmt;
3use std::sync::Arc;
4use ureq_proto::http::uri::{PathAndQuery, Scheme};
5
6use http::Uri;
7
8use crate::http;
9use crate::util::{AuthorityExt, DebugUri};
10use crate::Error;
11
12#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
14#[non_exhaustive]
15pub enum ProxyProtocol {
16 Http,
18 Https,
20 Socks4,
22 Socks4A,
24 Socks5,
26}
27
28impl ProxyProtocol {
29 pub(crate) fn default_port(&self) -> u16 {
30 match self {
31 ProxyProtocol::Http => 80,
32 ProxyProtocol::Https => 443,
33 ProxyProtocol::Socks4 | ProxyProtocol::Socks4A | ProxyProtocol::Socks5 => 1080,
34 }
35 }
36
37 pub(crate) fn is_socks(&self) -> bool {
38 matches!(self, Self::Socks4 | Self::Socks4A | Self::Socks5)
39 }
40
41 pub(crate) fn is_connect(&self) -> bool {
42 matches!(self, Self::Http | Self::Https)
43 }
44
45 fn default_resolve_target(&self) -> bool {
46 match self {
47 ProxyProtocol::Http => false,
48 ProxyProtocol::Https => false,
49 ProxyProtocol::Socks4 => true, ProxyProtocol::Socks4A => false,
51 ProxyProtocol::Socks5 => false,
52 }
53 }
54}
55
56#[derive(Clone, Eq, Hash, PartialEq)]
106pub struct Proxy {
107 inner: Arc<ProxyInner>,
108}
109
110#[derive(Eq, Hash, PartialEq)]
111struct ProxyInner {
112 proto: ProxyProtocol,
113 uri: Uri,
114 from_env: bool,
115 resolve_target: bool,
116}
117
118impl Proxy {
119 pub fn new(proxy: &str) -> Result<Self, Error> {
141 Self::new_with_flag(proxy, false, None)
142 }
143
144 pub fn builder(p: ProxyProtocol) -> ProxyBuilder {
146 ProxyBuilder {
147 protocol: p,
148 host: None,
149 port: None,
150 username: None,
151 password: None,
152 resolve_target: p.default_resolve_target(),
153 }
154 }
155
156 fn new_with_flag(
157 proxy: &str,
158 from_env: bool,
159 resolve_target: Option<bool>,
160 ) -> Result<Self, Error> {
161 let mut uri = proxy.parse::<Uri>().or(Err(Error::InvalidProxyUrl))?;
162
163 let _ = uri.authority().ok_or(Error::InvalidProxyUrl)?;
166
167 let scheme = match uri.scheme_str() {
168 Some(v) => v,
169 None => {
170 uri = insert_default_scheme(uri);
173 "http"
174 }
175 };
176
177 let proto: ProxyProtocol = scheme.try_into()?;
178 let resolve_target = resolve_target.unwrap_or(proto.default_resolve_target());
179
180 let inner = ProxyInner {
181 proto,
182 uri,
183 from_env,
184 resolve_target,
185 };
186
187 Ok(Self {
188 inner: Arc::new(inner),
189 })
190 }
191
192 pub fn try_from_env() -> Option<Self> {
203 const TRY_ENV: &[&str] = &[
204 "ALL_PROXY",
205 "all_proxy",
206 "HTTPS_PROXY",
207 "https_proxy",
208 "HTTP_PROXY",
209 "http_proxy",
210 ];
211
212 for attempt in TRY_ENV {
213 if let Ok(env) = std::env::var(attempt) {
214 if let Ok(proxy) = Self::new_with_flag(&env, true, None) {
215 return Some(proxy);
216 }
217 }
218 }
219
220 None
221 }
222
223 pub fn protocol(&self) -> ProxyProtocol {
225 self.inner.proto
226 }
227
228 pub fn uri(&self) -> &Uri {
230 &self.inner.uri
231 }
232
233 pub fn host(&self) -> &str {
235 self.inner
236 .uri
237 .authority()
238 .map(|a| a.host())
239 .expect("constructor to ensure there is an authority")
240 }
241
242 pub fn port(&self) -> u16 {
244 self.inner
245 .uri
246 .authority()
247 .and_then(|a| a.port_u16())
248 .unwrap_or_else(|| self.inner.proto.default_port())
249 }
250
251 pub fn username(&self) -> Option<&str> {
253 self.inner.uri.authority().and_then(|a| a.username())
254 }
255
256 pub fn password(&self) -> Option<&str> {
258 self.inner.uri.authority().and_then(|a| a.password())
259 }
260
261 pub fn is_from_env(&self) -> bool {
264 self.inner.from_env
265 }
266
267 pub fn resolve_target(&self) -> bool {
275 self.inner.resolve_target
276 }
277}
278
279fn insert_default_scheme(uri: Uri) -> Uri {
280 let mut parts = uri.into_parts();
281
282 parts.scheme = Some(Scheme::HTTP);
283
284 parts.path_and_query = parts
287 .path_and_query
288 .or_else(|| Some(PathAndQuery::from_static("/")));
289
290 Uri::from_parts(parts).unwrap()
291}
292
293pub struct ProxyBuilder {
297 protocol: ProxyProtocol,
298 host: Option<String>,
299 port: Option<u16>,
300 username: Option<String>,
301 password: Option<String>,
302 resolve_target: bool,
303}
304
305impl ProxyBuilder {
306 pub fn host(mut self, host: &str) -> Self {
310 self.host = Some(host.to_string());
311 self
312 }
313
314 pub fn port(mut self, port: u16) -> Self {
318 self.port = Some(port);
319 self
320 }
321
322 pub fn username(mut self, v: &str) -> Self {
326 self.username = Some(v.to_string());
327 self
328 }
329
330 pub fn password(mut self, v: &str) -> Self {
337 self.password = Some(v.to_string());
338 self
339 }
340
341 pub fn resolve_target(mut self, do_resolve: bool) -> Self {
349 self.resolve_target = do_resolve;
350 self
351 }
352
353 pub fn build(self) -> Result<Proxy, Error> {
355 let host = self.host.as_deref().unwrap_or("localhost");
356 let port = self.port.unwrap_or(self.protocol.default_port());
357
358 let mut userpass = String::new();
359 if let Some(username) = self.username {
360 userpass.push_str(&username);
361 if let Some(password) = self.password {
362 userpass.push(':');
363 userpass.push_str(&password);
364 }
365 userpass.push('@');
366 }
367
368 let proxy = format!("{}://{}{}:{}", self.protocol, userpass, host, port);
372 Proxy::new_with_flag(&proxy, false, Some(self.resolve_target))
373 }
374}
375
376impl TryFrom<&str> for ProxyProtocol {
377 type Error = Error;
378
379 fn try_from(scheme: &str) -> Result<Self, Self::Error> {
380 match scheme.to_ascii_lowercase().as_str() {
381 "http" => Ok(ProxyProtocol::Http),
382 "https" => Ok(ProxyProtocol::Https),
383 "socks4" => Ok(ProxyProtocol::Socks4),
384 "socks4a" => Ok(ProxyProtocol::Socks4A),
385 "socks" => Ok(ProxyProtocol::Socks5),
386 "socks5" => Ok(ProxyProtocol::Socks5),
387 _ => Err(Error::InvalidProxyUrl),
388 }
389 }
390}
391
392impl fmt::Debug for Proxy {
393 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394 f.debug_struct("Proxy")
395 .field("proto", &self.inner.proto)
396 .field("uri", &DebugUri(&self.inner.uri))
397 .field("from_env", &self.inner.from_env)
398 .finish()
399 }
400}
401
402impl fmt::Display for ProxyProtocol {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 match self {
405 ProxyProtocol::Http => write!(f, "HTTP"),
406 ProxyProtocol::Https => write!(f, "HTTPS"),
407 ProxyProtocol::Socks4 => write!(f, "SOCKS4"),
408 ProxyProtocol::Socks4A => write!(f, "SOCKS4a"),
409 ProxyProtocol::Socks5 => write!(f, "SOCKS5"),
410 }
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn parse_proxy_fakeproto() {
420 assert!(Proxy::new("fakeproto://localhost").is_err());
421 }
422
423 #[test]
424 fn parse_proxy_http_user_pass_server_port() {
425 let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999").unwrap();
426 assert_eq!(proxy.username(), Some("user"));
427 assert_eq!(proxy.password(), Some("p@ssw0rd"));
428 assert_eq!(proxy.host(), "localhost");
429 assert_eq!(proxy.port(), 9999);
430 assert_eq!(proxy.inner.proto, ProxyProtocol::Http);
431 }
432
433 #[test]
434 fn parse_proxy_http_user_pass_server_port_trailing_slash() {
435 let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999/").unwrap();
436 assert_eq!(proxy.username(), Some("user"));
437 assert_eq!(proxy.password(), Some("p@ssw0rd"));
438 assert_eq!(proxy.host(), "localhost");
439 assert_eq!(proxy.port(), 9999);
440 assert_eq!(proxy.inner.proto, ProxyProtocol::Http);
441 }
442
443 #[test]
444 fn parse_proxy_socks4_user_pass_server_port() {
445 let proxy = Proxy::new("socks4://user:p@ssw0rd@localhost:9999").unwrap();
446 assert_eq!(proxy.username(), Some("user"));
447 assert_eq!(proxy.password(), Some("p@ssw0rd"));
448 assert_eq!(proxy.host(), "localhost");
449 assert_eq!(proxy.port(), 9999);
450 assert_eq!(proxy.inner.proto, ProxyProtocol::Socks4);
451 }
452
453 #[test]
454 fn parse_proxy_socks4a_user_pass_server_port() {
455 let proxy = Proxy::new("socks4a://user:p@ssw0rd@localhost:9999").unwrap();
456 assert_eq!(proxy.username(), Some("user"));
457 assert_eq!(proxy.password(), Some("p@ssw0rd"));
458 assert_eq!(proxy.host(), "localhost");
459 assert_eq!(proxy.port(), 9999);
460 assert_eq!(proxy.inner.proto, ProxyProtocol::Socks4A);
461 }
462
463 #[test]
464 fn parse_proxy_socks_user_pass_server_port() {
465 let proxy = Proxy::new("socks://user:p@ssw0rd@localhost:9999").unwrap();
466 assert_eq!(proxy.username(), Some("user"));
467 assert_eq!(proxy.password(), Some("p@ssw0rd"));
468 assert_eq!(proxy.host(), "localhost");
469 assert_eq!(proxy.port(), 9999);
470 assert_eq!(proxy.inner.proto, ProxyProtocol::Socks5);
471 }
472
473 #[test]
474 fn parse_proxy_socks5_user_pass_server_port() {
475 let proxy = Proxy::new("socks5://user:p@ssw0rd@localhost:9999").unwrap();
476 assert_eq!(proxy.username(), Some("user"));
477 assert_eq!(proxy.password(), Some("p@ssw0rd"));
478 assert_eq!(proxy.host(), "localhost");
479 assert_eq!(proxy.port(), 9999);
480 assert_eq!(proxy.inner.proto, ProxyProtocol::Socks5);
481 }
482
483 #[test]
484 fn parse_proxy_user_pass_server_port() {
485 let proxy = Proxy::new("user:p@ssw0rd@localhost:9999").unwrap();
486 assert_eq!(proxy.username(), Some("user"));
487 assert_eq!(proxy.password(), Some("p@ssw0rd"));
488 assert_eq!(proxy.host(), "localhost");
489 assert_eq!(proxy.port(), 9999);
490 assert_eq!(proxy.inner.proto, ProxyProtocol::Http);
491 }
492
493 #[test]
494 fn parse_proxy_server_port() {
495 let proxy = Proxy::new("localhost:9999").unwrap();
496 assert_eq!(proxy.username(), None);
497 assert_eq!(proxy.password(), None);
498 assert_eq!(proxy.host(), "localhost");
499 assert_eq!(proxy.port(), 9999);
500 assert_eq!(proxy.inner.proto, ProxyProtocol::Http);
501 }
502
503 #[test]
504 fn parse_proxy_server() {
505 let proxy = Proxy::new("localhost").unwrap();
506 assert_eq!(proxy.username(), None);
507 assert_eq!(proxy.password(), None);
508 assert_eq!(proxy.host(), "localhost");
509 assert_eq!(proxy.port(), 80);
510 assert_eq!(proxy.inner.proto, ProxyProtocol::Http);
511 }
512}
513
514#[cfg(test)]
515mod test {
516 use super::*;
517 use assert_no_alloc::*;
518
519 #[test]
520 fn proxy_clone_does_not_allocate() {
521 let c = Proxy::new("socks://1.2.3.4").unwrap();
522 assert_no_alloc(|| c.clone());
523 }
524
525 #[test]
526 fn proxy_new_default_scheme() {
527 let c = Proxy::new("localhost:1234").unwrap();
528 assert_eq!(c.protocol(), ProxyProtocol::Http);
529 assert_eq!(c.uri(), "http://localhost:1234");
530 }
531
532 #[test]
533 fn proxy_empty_env_url() {
534 let result = Proxy::new_with_flag("", false, None);
535 assert!(result.is_err());
536 }
537
538 #[test]
539 fn proxy_invalid_env_url() {
540 let result = Proxy::new_with_flag("r32/?//52:**", false, None);
541 assert!(result.is_err());
542 }
543
544 #[test]
545 fn proxy_builder() {
546 let proxy = Proxy::builder(ProxyProtocol::Socks4)
547 .host("my-proxy.com")
548 .port(5551)
549 .resolve_target(false)
550 .build()
551 .unwrap();
552
553 assert_eq!(proxy.protocol(), ProxyProtocol::Socks4);
554 assert_eq!(proxy.uri(), "SOCKS4://my-proxy.com:5551/");
555 assert_eq!(proxy.host(), "my-proxy.com");
556 assert_eq!(proxy.port(), 5551);
557 assert_eq!(proxy.username(), None);
558 assert_eq!(proxy.password(), None);
559 assert_eq!(proxy.is_from_env(), false);
560 assert_eq!(proxy.resolve_target(), false);
561 }
562
563 #[test]
564 fn proxy_builder_username() {
565 let proxy = Proxy::builder(ProxyProtocol::Https)
566 .username("hemligearne")
567 .build()
568 .unwrap();
569
570 assert_eq!(proxy.protocol(), ProxyProtocol::Https);
571 assert_eq!(proxy.uri(), "https://hemligearne@localhost:443/");
572 assert_eq!(proxy.host(), "localhost");
573 assert_eq!(proxy.port(), 443);
574 assert_eq!(proxy.username(), Some("hemligearne"));
575 assert_eq!(proxy.password(), None);
576 assert_eq!(proxy.is_from_env(), false);
577 assert_eq!(proxy.resolve_target(), false);
578 }
579
580 #[test]
581 fn proxy_builder_username_password() {
582 let proxy = Proxy::builder(ProxyProtocol::Https)
583 .username("hemligearne")
584 .password("kulgrej")
585 .build()
586 .unwrap();
587
588 assert_eq!(proxy.protocol(), ProxyProtocol::Https);
589 assert_eq!(proxy.uri(), "https://hemligearne:kulgrej@localhost:443/");
590 assert_eq!(proxy.host(), "localhost");
591 assert_eq!(proxy.port(), 443);
592 assert_eq!(proxy.username(), Some("hemligearne"));
593 assert_eq!(proxy.password(), Some("kulgrej"));
594 assert_eq!(proxy.is_from_env(), false);
595 assert_eq!(proxy.resolve_target(), false);
596 }
597}