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