1use std::fmt;
2#[cfg(feature = "socks")]
3use std::net::SocketAddr;
4use std::sync::Arc;
5
6use crate::into_url::{IntoUrl, IntoUrlSealed};
7use crate::Url;
8use http::{header::HeaderValue, Uri};
9use ipnet::IpNet;
10use percent_encoding::percent_decode;
11use std::collections::HashMap;
12use std::env;
13use std::error::Error;
14use std::net::IpAddr;
15#[cfg(all(target_os = "macos", feature = "macos-system-configuration"))]
16use system_configuration::{
17 core_foundation::{
18 base::CFType,
19 dictionary::CFDictionary,
20 number::CFNumber,
21 string::{CFString, CFStringRef},
22 },
23 dynamic_store::SCDynamicStoreBuilder,
24 sys::schema_definitions::kSCPropNetProxiesHTTPEnable,
25 sys::schema_definitions::kSCPropNetProxiesHTTPPort,
26 sys::schema_definitions::kSCPropNetProxiesHTTPProxy,
27 sys::schema_definitions::kSCPropNetProxiesHTTPSEnable,
28 sys::schema_definitions::kSCPropNetProxiesHTTPSPort,
29 sys::schema_definitions::kSCPropNetProxiesHTTPSProxy,
30};
31
32#[derive(Clone)]
65pub struct Proxy {
66 intercept: Intercept,
67 no_proxy: Option<NoProxy>,
68}
69
70#[derive(Clone, Debug)]
72enum Ip {
73 Address(IpAddr),
74 Network(IpNet),
75}
76
77#[derive(Clone, Debug, Default)]
80struct IpMatcher(Vec<Ip>);
81
82#[derive(Clone, Debug, Default)]
85struct DomainMatcher(Vec<String>);
86
87#[derive(Clone, Debug, Default)]
89pub struct NoProxy {
90 ips: IpMatcher,
91 domains: DomainMatcher,
92}
93
94#[derive(Clone)]
98pub enum ProxyScheme {
99 Http {
100 auth: Option<HeaderValue>,
101 host: http::uri::Authority,
102 },
103 Https {
104 auth: Option<HeaderValue>,
105 host: http::uri::Authority,
106 },
107 #[cfg(feature = "socks")]
108 Socks4 { addr: SocketAddr },
109 #[cfg(feature = "socks")]
110 Socks5 {
111 addr: SocketAddr,
112 auth: Option<(String, String)>,
113 remote_dns: bool,
114 },
115}
116
117impl ProxyScheme {
118 fn maybe_http_auth(&self) -> Option<&HeaderValue> {
119 match self {
120 ProxyScheme::Http { auth, .. } | ProxyScheme::Https { auth, .. } => auth.as_ref(),
121 #[cfg(feature = "socks")]
122 _ => None,
123 }
124 }
125}
126
127pub trait IntoProxyScheme {
131 fn into_proxy_scheme(self) -> crate::Result<ProxyScheme>;
132}
133
134impl<S: IntoUrl> IntoProxyScheme for S {
135 fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> {
136 let url = match self.as_str().into_url() {
138 Ok(ok) => ok,
139 Err(e) => {
140 let mut presumed_to_have_scheme = true;
141 let mut source = e.source();
142 while let Some(err) = source {
143 if let Some(parse_error) = err.downcast_ref::<url::ParseError>() {
144 if *parse_error == url::ParseError::RelativeUrlWithoutBase {
145 presumed_to_have_scheme = false;
146 break;
147 }
148 } else if err.downcast_ref::<crate::error::BadScheme>().is_some() {
149 presumed_to_have_scheme = false;
150 break;
151 }
152 source = err.source();
153 }
154 if presumed_to_have_scheme {
155 return Err(crate::error::builder(e));
156 }
157 let try_this = format!("http://{}", self.as_str());
159 try_this.into_url().map_err(|_| {
160 crate::error::builder(e)
162 })?
163 }
164 };
165 ProxyScheme::parse(url)
166 }
167}
168
169fn _implied_bounds() {
173 fn prox<T: IntoProxyScheme>(_t: T) {}
174
175 fn url<T: IntoUrl>(t: T) {
176 prox(t);
177 }
178}
179
180impl IntoProxyScheme for ProxyScheme {
181 fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> {
182 Ok(self)
183 }
184}
185
186impl Proxy {
187 pub fn http<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
202 Ok(Proxy::new(Intercept::Http(
203 proxy_scheme.into_proxy_scheme()?,
204 )))
205 }
206
207 pub fn https<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
222 Ok(Proxy::new(Intercept::Https(
223 proxy_scheme.into_proxy_scheme()?,
224 )))
225 }
226
227 pub fn all<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
242 Ok(Proxy::new(Intercept::All(
243 proxy_scheme.into_proxy_scheme()?,
244 )))
245 }
246
247 pub fn custom<F, U: IntoProxyScheme>(fun: F) -> Proxy
269 where
270 F: Fn(&Url) -> Option<U> + Send + Sync + 'static,
271 {
272 Proxy::new(Intercept::Custom(Custom {
273 auth: None,
274 func: Arc::new(move |url| fun(url).map(IntoProxyScheme::into_proxy_scheme)),
275 }))
276 }
277
278 pub(crate) fn system() -> Proxy {
279 let mut proxy = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(
280 get_from_platform(),
281 ))));
282 proxy.no_proxy = NoProxy::from_env();
283 proxy
284 }
285
286 fn new(intercept: Intercept) -> Proxy {
287 Proxy {
288 intercept,
289 no_proxy: None,
290 }
291 }
292
293 pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy {
307 self.intercept.set_basic_auth(username, password);
308 self
309 }
310
311 pub fn custom_http_auth(mut self, header_value: HeaderValue) -> Proxy {
326 self.intercept.set_custom_http_auth(header_value);
327 self
328 }
329
330 pub fn no_proxy(mut self, no_proxy: Option<NoProxy>) -> Proxy {
344 self.no_proxy = no_proxy;
345 self
346 }
347
348 pub(crate) fn maybe_has_http_auth(&self) -> bool {
349 match &self.intercept {
350 Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().is_some(),
351 Intercept::Custom(_) => true,
353 Intercept::System(system) => system
354 .get("http")
355 .and_then(|s| s.maybe_http_auth())
356 .is_some(),
357 Intercept::Https(_) => false,
358 }
359 }
360
361 pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
362 match &self.intercept {
363 Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().cloned(),
364 Intercept::System(system) => system
365 .get("http")
366 .and_then(|s| s.maybe_http_auth().cloned()),
367 Intercept::Custom(custom) => {
368 custom.call(uri).and_then(|s| s.maybe_http_auth().cloned())
369 }
370 Intercept::Https(_) => None,
371 }
372 }
373
374 pub(crate) fn intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
375 let in_no_proxy = self
376 .no_proxy
377 .as_ref()
378 .map_or(false, |np| np.contains(uri.host()));
379 match self.intercept {
380 Intercept::All(ref u) => {
381 if !in_no_proxy {
382 Some(u.clone())
383 } else {
384 None
385 }
386 }
387 Intercept::Http(ref u) => {
388 if !in_no_proxy && uri.scheme() == "http" {
389 Some(u.clone())
390 } else {
391 None
392 }
393 }
394 Intercept::Https(ref u) => {
395 if !in_no_proxy && uri.scheme() == "https" {
396 Some(u.clone())
397 } else {
398 None
399 }
400 }
401 Intercept::System(ref map) => {
402 if in_no_proxy {
403 None
404 } else {
405 map.get(uri.scheme()).cloned()
406 }
407 }
408 Intercept::Custom(ref custom) => {
409 if !in_no_proxy {
410 custom.call(uri)
411 } else {
412 None
413 }
414 }
415 }
416 }
417
418 pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool {
419 match self.intercept {
420 Intercept::All(_) => true,
421 Intercept::Http(_) => uri.scheme() == "http",
422 Intercept::Https(_) => uri.scheme() == "https",
423 Intercept::System(ref map) => map.contains_key(uri.scheme()),
424 Intercept::Custom(ref custom) => custom.call(uri).is_some(),
425 }
426 }
427}
428
429impl fmt::Debug for Proxy {
430 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
431 f.debug_tuple("Proxy")
432 .field(&self.intercept)
433 .field(&self.no_proxy)
434 .finish()
435 }
436}
437
438impl NoProxy {
439 pub fn from_env() -> Option<NoProxy> {
442 let raw = env::var("NO_PROXY")
443 .or_else(|_| env::var("no_proxy"))
444 .unwrap_or_default();
445
446 Self::from_string(&raw)
447 }
448
449 pub fn from_string(no_proxy_list: &str) -> Option<Self> {
469 if no_proxy_list.is_empty() {
470 return None;
471 }
472 let mut ips = Vec::new();
473 let mut domains = Vec::new();
474 let parts = no_proxy_list.split(',').map(str::trim);
475 for part in parts {
476 match part.parse::<IpNet>() {
477 Ok(ip) => ips.push(Ip::Network(ip)),
479 Err(_) => match part.parse::<IpAddr>() {
480 Ok(addr) => ips.push(Ip::Address(addr)),
481 Err(_) => domains.push(part.to_owned()),
482 },
483 }
484 }
485 Some(NoProxy {
486 ips: IpMatcher(ips),
487 domains: DomainMatcher(domains),
488 })
489 }
490
491 fn contains(&self, host: &str) -> bool {
492 let host = if host.starts_with('[') {
495 let x: &[_] = &['[', ']'];
496 host.trim_matches(x)
497 } else {
498 host
499 };
500 match host.parse::<IpAddr>() {
501 Ok(ip) => self.ips.contains(ip),
503 Err(_) => self.domains.contains(host),
504 }
505 }
506}
507
508impl IpMatcher {
509 fn contains(&self, addr: IpAddr) -> bool {
510 for ip in &self.0 {
511 match ip {
512 Ip::Address(address) => {
513 if &addr == address {
514 return true;
515 }
516 }
517 Ip::Network(net) => {
518 if net.contains(&addr) {
519 return true;
520 }
521 }
522 }
523 }
524 false
525 }
526}
527
528impl DomainMatcher {
529 fn contains(&self, domain: &str) -> bool {
533 let domain_len = domain.len();
534 for d in &self.0 {
535 if d == domain || d.strip_prefix('.') == Some(domain) {
536 return true;
537 } else if domain.ends_with(d) {
538 if d.starts_with('.') {
539 return true;
542 } else if domain.as_bytes().get(domain_len - d.len() - 1) == Some(&b'.') {
543 return true;
546 }
547 } else if d == "*" {
548 return true;
549 }
550 }
551 false
552 }
553}
554
555impl ProxyScheme {
556 fn http(host: &str) -> crate::Result<Self> {
560 Ok(ProxyScheme::Http {
561 auth: None,
562 host: host.parse().map_err(crate::error::builder)?,
563 })
564 }
565
566 fn https(host: &str) -> crate::Result<Self> {
568 Ok(ProxyScheme::Https {
569 auth: None,
570 host: host.parse().map_err(crate::error::builder)?,
571 })
572 }
573
574 #[cfg(feature = "socks")]
580 fn socks4(addr: SocketAddr) -> crate::Result<Self> {
581 Ok(ProxyScheme::Socks4 { addr })
582 }
583
584 #[cfg(feature = "socks")]
590 fn socks5(addr: SocketAddr) -> crate::Result<Self> {
591 Ok(ProxyScheme::Socks5 {
592 addr,
593 auth: None,
594 remote_dns: false,
595 })
596 }
597
598 #[cfg(feature = "socks")]
606 fn socks5h(addr: SocketAddr) -> crate::Result<Self> {
607 Ok(ProxyScheme::Socks5 {
608 addr,
609 auth: None,
610 remote_dns: true,
611 })
612 }
613
614 fn with_basic_auth<T: Into<String>, U: Into<String>>(
616 mut self,
617 username: T,
618 password: U,
619 ) -> Self {
620 self.set_basic_auth(username, password);
621 self
622 }
623
624 fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
625 match *self {
626 ProxyScheme::Http { ref mut auth, .. } => {
627 let header = encode_basic_auth(&username.into(), &password.into());
628 *auth = Some(header);
629 }
630 ProxyScheme::Https { ref mut auth, .. } => {
631 let header = encode_basic_auth(&username.into(), &password.into());
632 *auth = Some(header);
633 }
634 #[cfg(feature = "socks")]
635 ProxyScheme::Socks4 { .. } => {
636 panic!("Socks4 is not supported for this method")
637 }
638 #[cfg(feature = "socks")]
639 ProxyScheme::Socks5 { ref mut auth, .. } => {
640 *auth = Some((username.into(), password.into()));
641 }
642 }
643 }
644
645 fn set_custom_http_auth(&mut self, header_value: HeaderValue) {
646 match *self {
647 ProxyScheme::Http { ref mut auth, .. } => {
648 *auth = Some(header_value);
649 }
650 ProxyScheme::Https { ref mut auth, .. } => {
651 *auth = Some(header_value);
652 }
653 #[cfg(feature = "socks")]
654 ProxyScheme::Socks4 { .. } => {
655 panic!("Socks4 is not supported for this method")
656 }
657 #[cfg(feature = "socks")]
658 ProxyScheme::Socks5 { .. } => {
659 panic!("Socks5 is not supported for this method")
660 }
661 }
662 }
663
664 fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
665 match self {
666 ProxyScheme::Http { ref mut auth, .. } => {
667 if auth.is_none() {
668 *auth = update.clone();
669 }
670 }
671 ProxyScheme::Https { ref mut auth, .. } => {
672 if auth.is_none() {
673 *auth = update.clone();
674 }
675 }
676 #[cfg(feature = "socks")]
677 ProxyScheme::Socks4 { .. } => {}
678 #[cfg(feature = "socks")]
679 ProxyScheme::Socks5 { .. } => {}
680 }
681
682 self
683 }
684
685 fn parse(url: Url) -> crate::Result<Self> {
690 use url::Position;
691
692 #[cfg(feature = "socks")]
694 let to_addr = || {
695 let addrs = url
696 .socket_addrs(|| match url.scheme() {
697 "socks4" | "socks5" | "socks5h" => Some(1080),
698 _ => None,
699 })
700 .map_err(crate::error::builder)?;
701 addrs
702 .into_iter()
703 .next()
704 .ok_or_else(|| crate::error::builder("unknown proxy scheme"))
705 };
706
707 let mut scheme = match url.scheme() {
708 "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
709 "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
710 #[cfg(feature = "socks")]
711 "socks4" => Self::socks4(to_addr()?)?,
712 #[cfg(feature = "socks")]
713 "socks5" => Self::socks5(to_addr()?)?,
714 #[cfg(feature = "socks")]
715 "socks5h" => Self::socks5h(to_addr()?)?,
716 _ => return Err(crate::error::builder("unknown proxy scheme")),
717 };
718
719 if let Some(pwd) = url.password() {
720 let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
721 let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
722 scheme = scheme.with_basic_auth(decoded_username, decoded_password);
723 }
724
725 Ok(scheme)
726 }
727
728 #[cfg(test)]
729 fn scheme(&self) -> &str {
730 match self {
731 ProxyScheme::Http { .. } => "http",
732 ProxyScheme::Https { .. } => "https",
733 #[cfg(feature = "socks")]
734 ProxyScheme::Socks4 { .. } => "socks4",
735 #[cfg(feature = "socks")]
736 ProxyScheme::Socks5 { .. } => "socks5",
737 }
738 }
739
740 #[cfg(test)]
741 fn host(&self) -> &str {
742 match self {
743 ProxyScheme::Http { host, .. } => host.as_str(),
744 ProxyScheme::Https { host, .. } => host.as_str(),
745 #[cfg(feature = "socks")]
746 ProxyScheme::Socks4 { .. } => panic!("socks4"),
747 #[cfg(feature = "socks")]
748 ProxyScheme::Socks5 { .. } => panic!("socks5"),
749 }
750 }
751}
752
753impl fmt::Debug for ProxyScheme {
754 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
755 match self {
756 ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{host}"),
757 ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{host}"),
758 #[cfg(feature = "socks")]
759 ProxyScheme::Socks4 { addr } => {
760 write!(f, "socks4://{addr}")
761 }
762 #[cfg(feature = "socks")]
763 ProxyScheme::Socks5 {
764 addr,
765 auth: _auth,
766 remote_dns,
767 } => {
768 let h = if *remote_dns { "h" } else { "" };
769 write!(f, "socks5{h}://{addr}")
770 }
771 }
772 }
773}
774
775type SystemProxyMap = HashMap<String, ProxyScheme>;
776
777#[derive(Clone, Debug)]
778enum Intercept {
779 All(ProxyScheme),
780 Http(ProxyScheme),
781 Https(ProxyScheme),
782 System(Arc<SystemProxyMap>),
783 Custom(Custom),
784}
785
786impl Intercept {
787 fn set_basic_auth(&mut self, username: &str, password: &str) {
788 match self {
789 Intercept::All(ref mut s)
790 | Intercept::Http(ref mut s)
791 | Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
792 Intercept::System(_) => unimplemented!(),
793 Intercept::Custom(ref mut custom) => {
794 let header = encode_basic_auth(username, password);
795 custom.auth = Some(header);
796 }
797 }
798 }
799
800 fn set_custom_http_auth(&mut self, header_value: HeaderValue) {
801 match self {
802 Intercept::All(ref mut s)
803 | Intercept::Http(ref mut s)
804 | Intercept::Https(ref mut s) => s.set_custom_http_auth(header_value),
805 Intercept::System(_) => unimplemented!(),
806 Intercept::Custom(ref mut custom) => {
807 custom.auth = Some(header_value);
808 }
809 }
810 }
811}
812
813#[derive(Clone)]
814struct Custom {
815 auth: Option<HeaderValue>,
817 func: Arc<dyn Fn(&Url) -> Option<crate::Result<ProxyScheme>> + Send + Sync + 'static>,
818}
819
820impl Custom {
821 fn call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
822 let url = format!(
823 "{}://{}{}{}",
824 uri.scheme(),
825 uri.host(),
826 uri.port().map_or("", |_| ":"),
827 uri.port().map_or(String::new(), |p| p.to_string())
828 )
829 .parse()
830 .expect("should be valid Url");
831
832 (self.func)(&url)
833 .and_then(|result| result.ok())
834 .map(|scheme| scheme.if_no_auth(&self.auth))
835 }
836}
837
838impl fmt::Debug for Custom {
839 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
840 f.write_str("_")
841 }
842}
843
844pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
845 crate::util::basic_auth(username, Some(password))
846}
847
848pub(crate) trait Dst {
851 fn scheme(&self) -> &str;
852 fn host(&self) -> &str;
853 fn port(&self) -> Option<u16>;
854}
855
856#[doc(hidden)]
857impl Dst for Uri {
858 fn scheme(&self) -> &str {
859 self.scheme().expect("Uri should have a scheme").as_str()
860 }
861
862 fn host(&self) -> &str {
863 Uri::host(self).expect("<Uri as Dst>::host should have a str")
864 }
865
866 fn port(&self) -> Option<u16> {
867 self.port().map(|p| p.as_u16())
868 }
869}
870
871fn get_sys_proxies(
882 #[cfg_attr(
883 not(any(target_os = "windows", target_os = "macos")),
884 allow(unused_variables)
885 )]
886 platform_proxies: Option<String>,
887) -> SystemProxyMap {
888 let proxies = get_from_environment();
889
890 #[cfg(any(target_os = "windows", target_os = "macos"))]
891 if proxies.is_empty() {
892 if let Some(platform_proxies) = platform_proxies {
895 return parse_platform_values(platform_proxies);
896 }
897 }
898
899 proxies
900}
901
902fn insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool {
903 if addr.trim().is_empty() {
904 false
906 } else if let Ok(valid_addr) = addr.into_proxy_scheme() {
907 proxies.insert(scheme.into(), valid_addr);
908 true
909 } else {
910 false
911 }
912}
913
914fn get_from_environment() -> SystemProxyMap {
915 let mut proxies = HashMap::new();
916
917 if !(insert_from_env(&mut proxies, "http", "ALL_PROXY")
918 && insert_from_env(&mut proxies, "https", "ALL_PROXY"))
919 {
920 insert_from_env(&mut proxies, "http", "all_proxy");
921 insert_from_env(&mut proxies, "https", "all_proxy");
922 }
923
924 if is_cgi() {
925 if log::log_enabled!(log::Level::Warn) && env::var_os("HTTP_PROXY").is_some() {
926 log::warn!("HTTP_PROXY environment variable ignored in CGI");
927 }
928 } else if !insert_from_env(&mut proxies, "http", "HTTP_PROXY") {
929 insert_from_env(&mut proxies, "http", "http_proxy");
930 }
931
932 if !insert_from_env(&mut proxies, "https", "HTTPS_PROXY") {
933 insert_from_env(&mut proxies, "https", "https_proxy");
934 }
935
936 proxies
937}
938
939fn insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool {
940 if let Ok(val) = env::var(var) {
941 insert_proxy(proxies, scheme, val)
942 } else {
943 false
944 }
945}
946
947fn is_cgi() -> bool {
952 env::var_os("REQUEST_METHOD").is_some()
953}
954
955#[cfg(target_os = "windows")]
956fn get_from_platform_impl() -> Result<Option<String>, Box<dyn Error>> {
957 let internet_setting = windows_registry::CURRENT_USER
958 .open("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
959 let proxy_enable = internet_setting.get_u32("ProxyEnable")?;
961 let proxy_server = internet_setting.get_string("ProxyServer")?;
962
963 Ok((proxy_enable == 1).then_some(proxy_server))
964}
965
966#[cfg(all(target_os = "macos", feature = "macos-system-configuration"))]
967fn parse_setting_from_dynamic_store(
968 proxies_map: &CFDictionary<CFString, CFType>,
969 enabled_key: CFStringRef,
970 host_key: CFStringRef,
971 port_key: CFStringRef,
972 scheme: &str,
973) -> Option<String> {
974 let proxy_enabled = proxies_map
975 .find(enabled_key)
976 .and_then(|flag| flag.downcast::<CFNumber>())
977 .and_then(|flag| flag.to_i32())
978 .unwrap_or(0)
979 == 1;
980
981 if proxy_enabled {
982 let proxy_host = proxies_map
983 .find(host_key)
984 .and_then(|host| host.downcast::<CFString>())
985 .map(|host| host.to_string());
986 let proxy_port = proxies_map
987 .find(port_key)
988 .and_then(|port| port.downcast::<CFNumber>())
989 .and_then(|port| port.to_i32());
990
991 return match (proxy_host, proxy_port) {
992 (Some(proxy_host), Some(proxy_port)) => {
993 Some(format!("{scheme}={proxy_host}:{proxy_port}"))
994 }
995 (Some(proxy_host), None) => Some(format!("{scheme}={proxy_host}")),
996 (None, Some(_)) => None,
997 (None, None) => None,
998 };
999 }
1000
1001 None
1002}
1003
1004#[cfg(all(target_os = "macos", feature = "macos-system-configuration"))]
1005fn get_from_platform_impl() -> Result<Option<String>, Box<dyn Error>> {
1006 let store = SCDynamicStoreBuilder::new("reqwest").build();
1007
1008 let proxies_map = if let Some(proxies_map) = store.get_proxies() {
1009 proxies_map
1010 } else {
1011 return Ok(None);
1012 };
1013
1014 let http_proxy_config = parse_setting_from_dynamic_store(
1015 &proxies_map,
1016 unsafe { kSCPropNetProxiesHTTPEnable },
1017 unsafe { kSCPropNetProxiesHTTPProxy },
1018 unsafe { kSCPropNetProxiesHTTPPort },
1019 "http",
1020 );
1021 let https_proxy_config = parse_setting_from_dynamic_store(
1022 &proxies_map,
1023 unsafe { kSCPropNetProxiesHTTPSEnable },
1024 unsafe { kSCPropNetProxiesHTTPSProxy },
1025 unsafe { kSCPropNetProxiesHTTPSPort },
1026 "https",
1027 );
1028
1029 match http_proxy_config.as_ref().zip(https_proxy_config.as_ref()) {
1030 Some((http_config, https_config)) => Ok(Some(format!("{http_config};{https_config}"))),
1031 None => Ok(http_proxy_config.or(https_proxy_config)),
1032 }
1033}
1034
1035#[cfg(any(
1036 target_os = "windows",
1037 all(target_os = "macos", feature = "macos-system-configuration")
1038))]
1039fn get_from_platform() -> Option<String> {
1040 get_from_platform_impl().ok().flatten()
1041}
1042
1043#[cfg(not(any(
1044 target_os = "windows",
1045 all(target_os = "macos", feature = "macos-system-configuration")
1046)))]
1047fn get_from_platform() -> Option<String> {
1048 None
1049}
1050
1051#[cfg(any(target_os = "windows", target_os = "macos"))]
1052fn parse_platform_values_impl(platform_values: String) -> SystemProxyMap {
1053 let mut proxies = HashMap::new();
1054 if platform_values.contains("=") {
1055 for p in platform_values.split(";") {
1057 let protocol_parts: Vec<&str> = p.split("=").collect();
1058 match protocol_parts.as_slice() {
1059 [protocol, address] => {
1060 let address = if extract_type_prefix(*address).is_some() {
1063 String::from(*address)
1064 } else {
1065 format!("http://{address}")
1066 };
1067
1068 insert_proxy(&mut proxies, *protocol, address);
1069 }
1070 _ => {
1071 proxies.clear();
1074 break;
1075 }
1076 }
1077 }
1078 } else {
1079 if let Some(scheme) = extract_type_prefix(&platform_values) {
1080 insert_proxy(&mut proxies, scheme, platform_values.to_owned());
1082 } else {
1083 insert_proxy(&mut proxies, "http", format!("http://{platform_values}"));
1085 insert_proxy(&mut proxies, "https", format!("http://{platform_values}"));
1086 }
1087 }
1088 proxies
1089}
1090
1091#[cfg(any(target_os = "windows", target_os = "macos"))]
1094fn extract_type_prefix(address: &str) -> Option<&str> {
1095 if let Some(indice) = address.find("://") {
1096 if indice == 0 {
1097 None
1098 } else {
1099 let prefix = &address[..indice];
1100 let contains_banned = prefix.contains(|c| c == ':' || c == '/');
1101
1102 if !contains_banned {
1103 Some(prefix)
1104 } else {
1105 None
1106 }
1107 }
1108 } else {
1109 None
1110 }
1111}
1112
1113#[cfg(any(target_os = "windows", target_os = "macos"))]
1114fn parse_platform_values(platform_values: String) -> SystemProxyMap {
1115 parse_platform_values_impl(platform_values)
1116}
1117
1118#[cfg(test)]
1119mod tests {
1120 use super::*;
1121 use once_cell::sync::Lazy;
1122 use std::sync::Mutex;
1123
1124 impl Dst for Url {
1125 fn scheme(&self) -> &str {
1126 Url::scheme(self)
1127 }
1128
1129 fn host(&self) -> &str {
1130 Url::host_str(self).expect("<Url as Dst>::host should have a str")
1131 }
1132
1133 fn port(&self) -> Option<u16> {
1134 Url::port(self)
1135 }
1136 }
1137
1138 fn url(s: &str) -> Url {
1139 s.parse().unwrap()
1140 }
1141
1142 fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
1143 let (scheme, host) = match p.intercept(&url(s)).unwrap() {
1144 ProxyScheme::Http { host, .. } => ("http", host),
1145 ProxyScheme::Https { host, .. } => ("https", host),
1146 #[cfg(feature = "socks")]
1147 _ => panic!("intercepted as socks"),
1148 };
1149 http::Uri::builder()
1150 .scheme(scheme)
1151 .authority(host)
1152 .path_and_query("/")
1153 .build()
1154 .expect("intercepted_uri")
1155 }
1156
1157 #[test]
1158 fn test_http() {
1159 let target = "http://example.domain/";
1160 let p = Proxy::http(target).unwrap();
1161
1162 let http = "http://hyper.rs";
1163 let other = "https://hyper.rs";
1164
1165 assert_eq!(intercepted_uri(&p, http), target);
1166 assert!(p.intercept(&url(other)).is_none());
1167 }
1168
1169 #[test]
1170 fn test_https() {
1171 let target = "http://example.domain/";
1172 let p = Proxy::https(target).unwrap();
1173
1174 let http = "http://hyper.rs";
1175 let other = "https://hyper.rs";
1176
1177 assert!(p.intercept(&url(http)).is_none());
1178 assert_eq!(intercepted_uri(&p, other), target);
1179 }
1180
1181 #[test]
1182 fn test_all() {
1183 let target = "http://example.domain/";
1184 let p = Proxy::all(target).unwrap();
1185
1186 let http = "http://hyper.rs";
1187 let https = "https://hyper.rs";
1188 let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
1189
1190 assert_eq!(intercepted_uri(&p, http), target);
1191 assert_eq!(intercepted_uri(&p, https), target);
1192 assert_eq!(intercepted_uri(&p, other), target);
1193 }
1194
1195 #[test]
1196 fn test_custom() {
1197 let target1 = "http://example.domain/";
1198 let target2 = "https://example.domain/";
1199 let p = Proxy::custom(move |url| {
1200 if url.host_str() == Some("hyper.rs") {
1201 target1.parse().ok()
1202 } else if url.scheme() == "http" {
1203 target2.parse().ok()
1204 } else {
1205 None::<Url>
1206 }
1207 });
1208
1209 let http = "http://seanmonstar.com";
1210 let https = "https://hyper.rs";
1211 let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
1212
1213 assert_eq!(intercepted_uri(&p, http), target2);
1214 assert_eq!(intercepted_uri(&p, https), target1);
1215 assert!(p.intercept(&url(other)).is_none());
1216 }
1217
1218 #[test]
1219 fn test_proxy_scheme_parse() {
1220 let ps = "http://foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1221
1222 match ps {
1223 ProxyScheme::Http { auth, host } => {
1224 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1225 assert_eq!(host, "localhost:1239");
1226 }
1227 other => panic!("unexpected: {other:?}"),
1228 }
1229 }
1230
1231 #[test]
1232 fn test_proxy_scheme_ip_address_default_http() {
1233 let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap();
1234
1235 match ps {
1236 ProxyScheme::Http { auth, host } => {
1237 assert!(auth.is_none());
1238 assert_eq!(host, "192.168.1.1:8888");
1239 }
1240 other => panic!("unexpected: {other:?}"),
1241 }
1242 }
1243
1244 #[test]
1245 fn test_proxy_scheme_parse_default_http_with_auth() {
1246 let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1248
1249 match ps {
1250 ProxyScheme::Http { auth, host } => {
1251 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1252 assert_eq!(host, "localhost:1239");
1253 }
1254 other => panic!("unexpected: {other:?}"),
1255 }
1256 }
1257
1258 #[test]
1259 fn test_domain_matcher() {
1260 let domains = vec![".foo.bar".into(), "bar.foo".into()];
1261 let matcher = DomainMatcher(domains);
1262
1263 assert!(matcher.contains("foo.bar"));
1265 assert!(matcher.contains("www.foo.bar"));
1267
1268 assert!(matcher.contains("bar.foo"));
1270 assert!(matcher.contains("www.bar.foo"));
1272
1273 assert!(!matcher.contains("notfoo.bar"));
1275 assert!(!matcher.contains("notbar.foo"));
1276 }
1277
1278 struct MutexInner;
1280
1281 static ENVLOCK: Lazy<Mutex<MutexInner>> = Lazy::new(|| Mutex::new(MutexInner));
1282
1283 #[test]
1284 fn test_get_sys_proxies_parsing() {
1285 let _lock = ENVLOCK.lock();
1287 let _g1 = env_guard("HTTP_PROXY");
1289 let _g2 = env_guard("http_proxy");
1290 let _g3 = env_guard("ALL_PROXY");
1291
1292 let baseline_proxies = get_sys_proxies(None);
1295 env::set_var("http_proxy", "file://123465");
1297 let invalid_proxies = get_sys_proxies(None);
1298 env::set_var("http_proxy", "127.0.0.1/");
1300 let valid_proxies = get_sys_proxies(None);
1301 env::set_var("ALL_PROXY", "127.0.0.2/");
1303 let all_proxies = get_sys_proxies(None);
1304
1305 drop(_g1);
1307 drop(_g2);
1308 drop(_lock);
1310
1311 assert!(!baseline_proxies.contains_key("http"));
1312 assert!(!invalid_proxies.contains_key("http"));
1313
1314 let p = &valid_proxies["http"];
1315 assert_eq!(p.scheme(), "http");
1316 assert_eq!(p.host(), "127.0.0.1");
1317
1318 assert_eq!(all_proxies.len(), 2);
1319 assert_eq!(all_proxies["https"].host(), "127.0.0.2");
1321 assert_eq!(all_proxies["http"].host(), "127.0.0.1");
1323 }
1324
1325 #[cfg(any(target_os = "windows", target_os = "macos"))]
1326 #[test]
1327 fn test_get_sys_proxies_registry_parsing() {
1328 let _lock = ENVLOCK.lock();
1330 let _g1 = env_guard("HTTP_PROXY");
1332 let _g2 = env_guard("http_proxy");
1333
1334 let baseline_proxies = get_sys_proxies(None);
1337 let valid_proxies = get_sys_proxies(Some(String::from("http://127.0.0.1/")));
1339 let valid_proxies_no_scheme = get_sys_proxies(Some(String::from("127.0.0.1")));
1340 let valid_proxies_explicit_https =
1341 get_sys_proxies(Some(String::from("https://127.0.0.1/")));
1342 let multiple_proxies = get_sys_proxies(Some(String::from(
1343 "http=127.0.0.1:8888;https=127.0.0.2:8888",
1344 )));
1345 let multiple_proxies_explicit_scheme = get_sys_proxies(Some(String::from(
1346 "http=http://127.0.0.1:8888;https=https://127.0.0.2:8888",
1347 )));
1348
1349 drop(_g1);
1351 drop(_g2);
1352 drop(_lock);
1354
1355 assert_eq!(baseline_proxies.contains_key("http"), false);
1356
1357 let p = &valid_proxies["http"];
1358 assert_eq!(p.scheme(), "http");
1359 assert_eq!(p.host(), "127.0.0.1");
1360
1361 let p = &valid_proxies_no_scheme["http"];
1362 assert_eq!(p.scheme(), "http");
1363 assert_eq!(p.host(), "127.0.0.1");
1364
1365 let p = &valid_proxies_no_scheme["https"];
1366 assert_eq!(p.scheme(), "http");
1367 assert_eq!(p.host(), "127.0.0.1");
1368
1369 let p = &valid_proxies_explicit_https["https"];
1370 assert_eq!(p.scheme(), "https");
1371 assert_eq!(p.host(), "127.0.0.1");
1372
1373 let p = &multiple_proxies["http"];
1374 assert_eq!(p.scheme(), "http");
1375 assert_eq!(p.host(), "127.0.0.1:8888");
1376
1377 let p = &multiple_proxies["https"];
1378 assert_eq!(p.scheme(), "http");
1379 assert_eq!(p.host(), "127.0.0.2:8888");
1380
1381 let p = &multiple_proxies_explicit_scheme["http"];
1382 assert_eq!(p.scheme(), "http");
1383 assert_eq!(p.host(), "127.0.0.1:8888");
1384
1385 let p = &multiple_proxies_explicit_scheme["https"];
1386 assert_eq!(p.scheme(), "https");
1387 assert_eq!(p.host(), "127.0.0.2:8888");
1388 }
1389
1390 #[test]
1391 fn test_get_sys_proxies_in_cgi() {
1392 let _lock = ENVLOCK.lock();
1394 let _g1 = env_guard("REQUEST_METHOD");
1396 let _g2 = env_guard("HTTP_PROXY");
1397
1398 env::set_var("HTTP_PROXY", "http://evil/");
1401
1402 let baseline_proxies = get_sys_proxies(None);
1403 env::set_var("REQUEST_METHOD", "GET");
1405
1406 let cgi_proxies = get_sys_proxies(None);
1407
1408 drop(_g1);
1410 drop(_g2);
1411 drop(_lock);
1413
1414 assert_eq!(baseline_proxies["http"].host(), "evil");
1416 assert!(!cgi_proxies.contains_key("http"));
1418 }
1419
1420 #[test]
1421 fn test_sys_no_proxy() {
1422 let _lock = ENVLOCK.lock();
1424 let _g1 = env_guard("HTTP_PROXY");
1426 let _g2 = env_guard("NO_PROXY");
1427
1428 let target = "http://example.domain/";
1429 env::set_var("HTTP_PROXY", target);
1430
1431 env::set_var(
1432 "NO_PROXY",
1433 ".foo.bar, bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17",
1434 );
1435
1436 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1438 p.no_proxy = NoProxy::from_env();
1439
1440 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1442 assert_eq!(intercepted_uri(&p, "http://notfoo.bar"), target);
1444 assert_eq!(intercepted_uri(&p, "http://notbar.baz"), target);
1446 assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target);
1448 assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target);
1450 assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target);
1452 assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target);
1454
1455 assert!(p.intercept(&url("http://hello.foo.bar")).is_none());
1457 assert!(p.intercept(&url("http://bar.baz")).is_none());
1459 assert!(p.intercept(&url("http://BAR.baz")).is_none());
1461 assert!(p.intercept(&url("http://foo.bar.baz")).is_none());
1463 assert!(p.intercept(&url("http://foo.bar")).is_none());
1465 assert!(p.intercept(&url("http://10.42.1.100")).is_none());
1467 assert!(p.intercept(&url("http://[::1]")).is_none());
1469 assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none());
1471 assert!(p.intercept(&url("http://10.124.7.8")).is_none());
1473
1474 drop(_g1);
1476 drop(_g2);
1477 drop(_lock);
1479 }
1480
1481 #[test]
1482 fn test_proxy_no_proxy_interception_for_proxy_types() {
1483 let proxy_url = "http://example.domain/";
1484 let no_proxy = ".no.proxy.tld";
1485
1486 let p = Proxy::all(proxy_url)
1488 .unwrap()
1489 .no_proxy(NoProxy::from_string(no_proxy));
1490
1491 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
1493
1494 assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1496
1497 let p = Proxy::http(proxy_url)
1499 .unwrap()
1500 .no_proxy(NoProxy::from_string(no_proxy));
1501
1502 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
1504
1505 assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
1507
1508 assert!(p.intercept(&url("https://hyper.rs")).is_none());
1510
1511 let p = Proxy::https(proxy_url)
1513 .unwrap()
1514 .no_proxy(NoProxy::from_string(no_proxy));
1515
1516 assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
1518
1519 assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1521
1522 assert!(p.intercept(&url("http://hyper.rs")).is_none());
1524
1525 let p = Proxy::custom(move |_url| Some(proxy_url)).no_proxy(NoProxy::from_string(no_proxy));
1527
1528 assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
1530
1531 assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1533 assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
1534 }
1535
1536 #[test]
1537 fn test_wildcard_sys_no_proxy() {
1538 let _lock = ENVLOCK.lock();
1540 let _g1 = env_guard("HTTP_PROXY");
1542 let _g2 = env_guard("NO_PROXY");
1543
1544 let target = "http://example.domain/";
1545 env::set_var("HTTP_PROXY", target);
1546
1547 env::set_var("NO_PROXY", "*");
1548
1549 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1551 p.no_proxy = NoProxy::from_env();
1552
1553 assert!(p.intercept(&url("http://foo.bar")).is_none());
1554
1555 drop(_g1);
1557 drop(_g2);
1558 drop(_lock);
1560 }
1561
1562 #[test]
1563 fn test_empty_sys_no_proxy() {
1564 let _lock = ENVLOCK.lock();
1566 let _g1 = env_guard("HTTP_PROXY");
1568 let _g2 = env_guard("NO_PROXY");
1569
1570 let target = "http://example.domain/";
1571 env::set_var("HTTP_PROXY", target);
1572
1573 env::set_var("NO_PROXY", ",");
1574
1575 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1577 p.no_proxy = NoProxy::from_env();
1578
1579 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1581
1582 drop(_g1);
1584 drop(_g2);
1585 drop(_lock);
1587 }
1588
1589 #[test]
1590 fn test_no_proxy_load() {
1591 let _lock = ENVLOCK.lock();
1593
1594 let _g1 = env_guard("no_proxy");
1595 let domain = "lower.case";
1596 env::set_var("no_proxy", domain);
1597 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1599 p.no_proxy = NoProxy::from_env();
1600 assert_eq!(
1601 p.no_proxy.expect("should have a no proxy set").domains.0[0],
1602 domain
1603 );
1604
1605 env::remove_var("no_proxy");
1606 let _g2 = env_guard("NO_PROXY");
1607 let domain = "upper.case";
1608 env::set_var("NO_PROXY", domain);
1609 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1611 p.no_proxy = NoProxy::from_env();
1612 assert_eq!(
1613 p.no_proxy.expect("should have a no proxy set").domains.0[0],
1614 domain
1615 );
1616
1617 let _g3 = env_guard("HTTP_PROXY");
1618 env::remove_var("NO_PROXY");
1619 env::remove_var("no_proxy");
1620 let target = "http://example.domain/";
1621 env::set_var("HTTP_PROXY", target);
1622
1623 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1625 p.no_proxy = NoProxy::from_env();
1626 assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
1627
1628 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1629
1630 drop(_g1);
1632 drop(_g2);
1633 drop(_g3);
1634 drop(_lock);
1636 }
1637
1638 #[cfg(any(target_os = "windows", target_os = "macos"))]
1639 #[test]
1640 fn test_type_prefix_extraction() {
1641 assert!(extract_type_prefix("test").is_none());
1642 assert!(extract_type_prefix("://test").is_none());
1643 assert!(extract_type_prefix("some:prefix://test").is_none());
1644 assert!(extract_type_prefix("some/prefix://test").is_none());
1645
1646 assert_eq!(extract_type_prefix("http://test").unwrap(), "http");
1647 assert_eq!(extract_type_prefix("a://test").unwrap(), "a");
1648 }
1649
1650 fn env_guard(name: impl Into<String>) -> EnvGuard {
1653 let name = name.into();
1654 let orig_val = env::var(&name).ok();
1655 env::remove_var(&name);
1656 EnvGuard { name, orig_val }
1657 }
1658
1659 struct EnvGuard {
1660 name: String,
1661 orig_val: Option<String>,
1662 }
1663
1664 impl Drop for EnvGuard {
1665 fn drop(&mut self) {
1666 if let Some(val) = self.orig_val.take() {
1667 env::set_var(&self.name, val);
1668 } else {
1669 env::remove_var(&self.name);
1670 }
1671 }
1672 }
1673
1674 #[test]
1675 fn test_has_http_auth() {
1676 let http_proxy_with_auth = Proxy {
1677 intercept: Intercept::Http(ProxyScheme::Http {
1678 auth: Some(HeaderValue::from_static("auth1")),
1679 host: http::uri::Authority::from_static("authority"),
1680 }),
1681 no_proxy: None,
1682 };
1683 assert!(http_proxy_with_auth.maybe_has_http_auth());
1684 assert_eq!(
1685 http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1686 Some(HeaderValue::from_static("auth1"))
1687 );
1688
1689 let http_proxy_without_auth = Proxy {
1690 intercept: Intercept::Http(ProxyScheme::Http {
1691 auth: None,
1692 host: http::uri::Authority::from_static("authority"),
1693 }),
1694 no_proxy: None,
1695 };
1696 assert!(!http_proxy_without_auth.maybe_has_http_auth());
1697 assert_eq!(
1698 http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1699 None
1700 );
1701
1702 let https_proxy_with_auth = Proxy {
1703 intercept: Intercept::Http(ProxyScheme::Https {
1704 auth: Some(HeaderValue::from_static("auth2")),
1705 host: http::uri::Authority::from_static("authority"),
1706 }),
1707 no_proxy: None,
1708 };
1709 assert!(https_proxy_with_auth.maybe_has_http_auth());
1710 assert_eq!(
1711 https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1712 Some(HeaderValue::from_static("auth2"))
1713 );
1714
1715 let all_http_proxy_with_auth = Proxy {
1716 intercept: Intercept::All(ProxyScheme::Http {
1717 auth: Some(HeaderValue::from_static("auth3")),
1718 host: http::uri::Authority::from_static("authority"),
1719 }),
1720 no_proxy: None,
1721 };
1722 assert!(all_http_proxy_with_auth.maybe_has_http_auth());
1723 assert_eq!(
1724 all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1725 Some(HeaderValue::from_static("auth3"))
1726 );
1727
1728 let all_https_proxy_with_auth = Proxy {
1729 intercept: Intercept::All(ProxyScheme::Https {
1730 auth: Some(HeaderValue::from_static("auth4")),
1731 host: http::uri::Authority::from_static("authority"),
1732 }),
1733 no_proxy: None,
1734 };
1735 assert!(all_https_proxy_with_auth.maybe_has_http_auth());
1736 assert_eq!(
1737 all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1738 Some(HeaderValue::from_static("auth4"))
1739 );
1740
1741 let all_https_proxy_without_auth = Proxy {
1742 intercept: Intercept::All(ProxyScheme::Https {
1743 auth: None,
1744 host: http::uri::Authority::from_static("authority"),
1745 }),
1746 no_proxy: None,
1747 };
1748 assert!(!all_https_proxy_without_auth.maybe_has_http_auth());
1749 assert_eq!(
1750 all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1751 None
1752 );
1753
1754 let system_http_proxy_with_auth = Proxy {
1755 intercept: Intercept::System(Arc::new({
1756 let mut m = HashMap::new();
1757 m.insert(
1758 "http".into(),
1759 ProxyScheme::Http {
1760 auth: Some(HeaderValue::from_static("auth5")),
1761 host: http::uri::Authority::from_static("authority"),
1762 },
1763 );
1764 m
1765 })),
1766 no_proxy: None,
1767 };
1768 assert!(system_http_proxy_with_auth.maybe_has_http_auth());
1769 assert_eq!(
1770 system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1771 Some(HeaderValue::from_static("auth5"))
1772 );
1773
1774 let system_https_proxy_with_auth = Proxy {
1775 intercept: Intercept::System(Arc::new({
1776 let mut m = HashMap::new();
1777 m.insert(
1778 "https".into(),
1779 ProxyScheme::Https {
1780 auth: Some(HeaderValue::from_static("auth6")),
1781 host: http::uri::Authority::from_static("authority"),
1782 },
1783 );
1784 m
1785 })),
1786 no_proxy: None,
1787 };
1788 assert!(!system_https_proxy_with_auth.maybe_has_http_auth());
1789 assert_eq!(
1790 system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1791 None
1792 );
1793 }
1794}
1795
1796#[cfg(test)]
1797mod test {
1798 mod into_proxy_scheme {
1799 use crate::Proxy;
1800 use std::error::Error;
1801 use std::mem::discriminant;
1802
1803 fn includes(haystack: &crate::error::Error, needle: url::ParseError) -> bool {
1804 let mut source = haystack.source();
1805 while let Some(error) = source {
1806 if let Some(parse_error) = error.downcast_ref::<url::ParseError>() {
1807 if discriminant(parse_error) == discriminant(&needle) {
1808 return true;
1809 }
1810 }
1811 source = error.source();
1812 }
1813 false
1814 }
1815
1816 fn check_parse_error(url: &str, needle: url::ParseError) {
1817 let error = Proxy::http(url).unwrap_err();
1818 if !includes(&error, needle) {
1819 panic!("{needle:?} expected; {error:?}, {error} found");
1820 }
1821 }
1822
1823 mod when_scheme_missing {
1824 mod and_url_is_valid {
1825 use crate::Proxy;
1826
1827 #[test]
1828 fn lookback_works() {
1829 let _ = Proxy::http("127.0.0.1").unwrap();
1830 }
1831
1832 #[test]
1833 fn loopback_port_works() {
1834 let _ = Proxy::http("127.0.0.1:8080").unwrap();
1835 }
1836
1837 #[test]
1838 fn loopback_username_works() {
1839 let _ = Proxy::http("username@127.0.0.1").unwrap();
1840 }
1841
1842 #[test]
1843 fn loopback_username_password_works() {
1844 let _ = Proxy::http("username:password@127.0.0.1").unwrap();
1845 }
1846
1847 #[test]
1848 fn loopback_username_password_port_works() {
1849 let _ = Proxy::http("ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
1850 }
1851
1852 #[test]
1853 fn domain_works() {
1854 let _ = Proxy::http("proxy.example.com").unwrap();
1855 }
1856
1857 #[test]
1858 fn domain_port_works() {
1859 let _ = Proxy::http("proxy.example.com:8080").unwrap();
1860 }
1861
1862 #[test]
1863 fn domain_username_works() {
1864 let _ = Proxy::http("username@proxy.example.com").unwrap();
1865 }
1866
1867 #[test]
1868 fn domain_username_password_works() {
1869 let _ = Proxy::http("username:password@proxy.example.com").unwrap();
1870 }
1871
1872 #[test]
1873 fn domain_username_password_port_works() {
1874 let _ =
1875 Proxy::http("ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080").unwrap();
1876 }
1877 }
1878 mod and_url_has_bad {
1879 use super::super::check_parse_error;
1880
1881 #[test]
1882 fn host() {
1883 check_parse_error("username@", url::ParseError::RelativeUrlWithoutBase);
1884 }
1885
1886 #[test]
1887 fn idna_encoding() {
1888 check_parse_error("xn---", url::ParseError::RelativeUrlWithoutBase);
1889 }
1890
1891 #[test]
1892 fn port() {
1893 check_parse_error("127.0.0.1:808080", url::ParseError::RelativeUrlWithoutBase);
1894 }
1895
1896 #[test]
1897 fn ip_v4_address() {
1898 check_parse_error("421.627.718.469", url::ParseError::RelativeUrlWithoutBase);
1899 }
1900
1901 #[test]
1902 fn ip_v6_address() {
1903 check_parse_error(
1904 "[56FE::2159:5BBC::6594]",
1905 url::ParseError::RelativeUrlWithoutBase,
1906 );
1907 }
1908
1909 #[test]
1910 fn invalid_domain_character() {
1911 check_parse_error("abc 123", url::ParseError::RelativeUrlWithoutBase);
1912 }
1913 }
1914 }
1915
1916 mod when_scheme_present {
1917 mod and_url_is_valid {
1918 use crate::Proxy;
1919
1920 #[test]
1921 fn loopback_works() {
1922 let _ = Proxy::http("http://127.0.0.1").unwrap();
1923 }
1924
1925 #[test]
1926 fn loopback_port_works() {
1927 let _ = Proxy::http("https://127.0.0.1:8080").unwrap();
1928 }
1929
1930 #[test]
1931 fn loopback_username_works() {
1932 let _ = Proxy::http("http://username@127.0.0.1").unwrap();
1933 }
1934
1935 #[test]
1936 fn loopback_username_password_works() {
1937 let _ = Proxy::http("https://username:password@127.0.0.1").unwrap();
1938 }
1939
1940 #[test]
1941 fn loopback_username_password_port_works() {
1942 let _ =
1943 Proxy::http("http://ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
1944 }
1945
1946 #[test]
1947 fn domain_works() {
1948 let _ = Proxy::http("https://proxy.example.com").unwrap();
1949 }
1950
1951 #[test]
1952 fn domain_port_works() {
1953 let _ = Proxy::http("http://proxy.example.com:8080").unwrap();
1954 }
1955
1956 #[test]
1957 fn domain_username_works() {
1958 let _ = Proxy::http("https://username@proxy.example.com").unwrap();
1959 }
1960
1961 #[test]
1962 fn domain_username_password_works() {
1963 let _ = Proxy::http("http://username:password@proxy.example.com").unwrap();
1964 }
1965
1966 #[test]
1967 fn domain_username_password_port_works() {
1968 let _ =
1969 Proxy::http("https://ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080")
1970 .unwrap();
1971 }
1972 }
1973 mod and_url_has_bad {
1974 use super::super::check_parse_error;
1975
1976 #[test]
1977 fn host() {
1978 check_parse_error("http://username@", url::ParseError::EmptyHost);
1979 }
1980
1981 #[test]
1982 fn idna_encoding() {
1983 check_parse_error("http://xn---", url::ParseError::IdnaError);
1984 }
1985
1986 #[test]
1987 fn port() {
1988 check_parse_error("http://127.0.0.1:808080", url::ParseError::InvalidPort);
1989 }
1990
1991 #[test]
1992 fn ip_v4_address() {
1993 check_parse_error(
1994 "http://421.627.718.469",
1995 url::ParseError::InvalidIpv4Address,
1996 );
1997 }
1998
1999 #[test]
2000 fn ip_v6_address() {
2001 check_parse_error(
2002 "http://[56FE::2159:5BBC::6594]",
2003 url::ParseError::InvalidIpv6Address,
2004 );
2005 }
2006 }
2007 }
2008 }
2009}