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