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(target_os = "macos")]
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(target_os = "macos")]
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(target_os = "macos")]
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(target_os = "windows", target_os = "macos"))]
1020fn get_from_platform() -> Option<String> {
1021 get_from_platform_impl().ok().flatten()
1022}
1023
1024#[cfg(not(any(target_os = "windows", target_os = "macos")))]
1025fn get_from_platform() -> Option<String> {
1026 None
1027}
1028
1029#[cfg(any(target_os = "windows", target_os = "macos"))]
1030fn parse_platform_values_impl(platform_values: String) -> SystemProxyMap {
1031 let mut proxies = HashMap::new();
1032 if platform_values.contains("=") {
1033 for p in platform_values.split(";") {
1035 let protocol_parts: Vec<&str> = p.split("=").collect();
1036 match protocol_parts.as_slice() {
1037 [protocol, address] => {
1038 let address = if extract_type_prefix(*address).is_some() {
1041 String::from(*address)
1042 } else {
1043 format!("http://{address}")
1044 };
1045
1046 insert_proxy(&mut proxies, *protocol, address);
1047 }
1048 _ => {
1049 proxies.clear();
1052 break;
1053 }
1054 }
1055 }
1056 } else {
1057 if let Some(scheme) = extract_type_prefix(&platform_values) {
1058 insert_proxy(&mut proxies, scheme, platform_values.to_owned());
1060 } else {
1061 insert_proxy(&mut proxies, "http", format!("http://{platform_values}"));
1063 insert_proxy(&mut proxies, "https", format!("http://{platform_values}"));
1064 }
1065 }
1066 proxies
1067}
1068
1069#[cfg(any(target_os = "windows", target_os = "macos"))]
1072fn extract_type_prefix(address: &str) -> Option<&str> {
1073 if let Some(indice) = address.find("://") {
1074 if indice == 0 {
1075 None
1076 } else {
1077 let prefix = &address[..indice];
1078 let contains_banned = prefix.contains(|c| c == ':' || c == '/');
1079
1080 if !contains_banned {
1081 Some(prefix)
1082 } else {
1083 None
1084 }
1085 }
1086 } else {
1087 None
1088 }
1089}
1090
1091#[cfg(any(target_os = "windows", target_os = "macos"))]
1092fn parse_platform_values(platform_values: String) -> SystemProxyMap {
1093 parse_platform_values_impl(platform_values)
1094}
1095
1096#[cfg(test)]
1097mod tests {
1098 use super::*;
1099 use once_cell::sync::Lazy;
1100 use std::sync::Mutex;
1101
1102 impl Dst for Url {
1103 fn scheme(&self) -> &str {
1104 Url::scheme(self)
1105 }
1106
1107 fn host(&self) -> &str {
1108 Url::host_str(self).expect("<Url as Dst>::host should have a str")
1109 }
1110
1111 fn port(&self) -> Option<u16> {
1112 Url::port(self)
1113 }
1114 }
1115
1116 fn url(s: &str) -> Url {
1117 s.parse().unwrap()
1118 }
1119
1120 fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
1121 let (scheme, host) = match p.intercept(&url(s)).unwrap() {
1122 ProxyScheme::Http { host, .. } => ("http", host),
1123 ProxyScheme::Https { host, .. } => ("https", host),
1124 #[cfg(feature = "socks")]
1125 _ => panic!("intercepted as socks"),
1126 };
1127 http::Uri::builder()
1128 .scheme(scheme)
1129 .authority(host)
1130 .path_and_query("/")
1131 .build()
1132 .expect("intercepted_uri")
1133 }
1134
1135 #[test]
1136 fn test_http() {
1137 let target = "http://example.domain/";
1138 let p = Proxy::http(target).unwrap();
1139
1140 let http = "http://hyper.rs";
1141 let other = "https://hyper.rs";
1142
1143 assert_eq!(intercepted_uri(&p, http), target);
1144 assert!(p.intercept(&url(other)).is_none());
1145 }
1146
1147 #[test]
1148 fn test_https() {
1149 let target = "http://example.domain/";
1150 let p = Proxy::https(target).unwrap();
1151
1152 let http = "http://hyper.rs";
1153 let other = "https://hyper.rs";
1154
1155 assert!(p.intercept(&url(http)).is_none());
1156 assert_eq!(intercepted_uri(&p, other), target);
1157 }
1158
1159 #[test]
1160 fn test_all() {
1161 let target = "http://example.domain/";
1162 let p = Proxy::all(target).unwrap();
1163
1164 let http = "http://hyper.rs";
1165 let https = "https://hyper.rs";
1166 let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
1167
1168 assert_eq!(intercepted_uri(&p, http), target);
1169 assert_eq!(intercepted_uri(&p, https), target);
1170 assert_eq!(intercepted_uri(&p, other), target);
1171 }
1172
1173 #[test]
1174 fn test_custom() {
1175 let target1 = "http://example.domain/";
1176 let target2 = "https://example.domain/";
1177 let p = Proxy::custom(move |url| {
1178 if url.host_str() == Some("hyper.rs") {
1179 target1.parse().ok()
1180 } else if url.scheme() == "http" {
1181 target2.parse().ok()
1182 } else {
1183 None::<Url>
1184 }
1185 });
1186
1187 let http = "http://seanmonstar.com";
1188 let https = "https://hyper.rs";
1189 let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
1190
1191 assert_eq!(intercepted_uri(&p, http), target2);
1192 assert_eq!(intercepted_uri(&p, https), target1);
1193 assert!(p.intercept(&url(other)).is_none());
1194 }
1195
1196 #[test]
1197 fn test_proxy_scheme_parse() {
1198 let ps = "http://foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1199
1200 match ps {
1201 ProxyScheme::Http { auth, host } => {
1202 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1203 assert_eq!(host, "localhost:1239");
1204 }
1205 other => panic!("unexpected: {other:?}"),
1206 }
1207 }
1208
1209 #[test]
1210 fn test_proxy_scheme_ip_address_default_http() {
1211 let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap();
1212
1213 match ps {
1214 ProxyScheme::Http { auth, host } => {
1215 assert!(auth.is_none());
1216 assert_eq!(host, "192.168.1.1:8888");
1217 }
1218 other => panic!("unexpected: {other:?}"),
1219 }
1220 }
1221
1222 #[test]
1223 fn test_proxy_scheme_parse_default_http_with_auth() {
1224 let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1226
1227 match ps {
1228 ProxyScheme::Http { auth, host } => {
1229 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1230 assert_eq!(host, "localhost:1239");
1231 }
1232 other => panic!("unexpected: {other:?}"),
1233 }
1234 }
1235
1236 #[test]
1237 fn test_domain_matcher() {
1238 let domains = vec![".foo.bar".into(), "bar.foo".into()];
1239 let matcher = DomainMatcher(domains);
1240
1241 assert!(matcher.contains("foo.bar"));
1243 assert!(matcher.contains("www.foo.bar"));
1245
1246 assert!(matcher.contains("bar.foo"));
1248 assert!(matcher.contains("www.bar.foo"));
1250
1251 assert!(!matcher.contains("notfoo.bar"));
1253 assert!(!matcher.contains("notbar.foo"));
1254 }
1255
1256 struct MutexInner;
1258
1259 static ENVLOCK: Lazy<Mutex<MutexInner>> = Lazy::new(|| Mutex::new(MutexInner));
1260
1261 #[test]
1262 fn test_get_sys_proxies_parsing() {
1263 let _lock = ENVLOCK.lock();
1265 let _g1 = env_guard("HTTP_PROXY");
1267 let _g2 = env_guard("http_proxy");
1268 let _g3 = env_guard("ALL_PROXY");
1269
1270 let baseline_proxies = get_sys_proxies(None);
1273 env::set_var("http_proxy", "file://123465");
1275 let invalid_proxies = get_sys_proxies(None);
1276 env::set_var("http_proxy", "127.0.0.1/");
1278 let valid_proxies = get_sys_proxies(None);
1279 env::set_var("ALL_PROXY", "127.0.0.2/");
1281 let all_proxies = get_sys_proxies(None);
1282
1283 drop(_g1);
1285 drop(_g2);
1286 drop(_lock);
1288
1289 assert!(!baseline_proxies.contains_key("http"));
1290 assert!(!invalid_proxies.contains_key("http"));
1291
1292 let p = &valid_proxies["http"];
1293 assert_eq!(p.scheme(), "http");
1294 assert_eq!(p.host(), "127.0.0.1");
1295
1296 assert_eq!(all_proxies.len(), 2);
1297 assert!(all_proxies.values().all(|p| p.host() == "127.0.0.2"));
1298 }
1299
1300 #[cfg(any(target_os = "windows", target_os = "macos"))]
1301 #[test]
1302 fn test_get_sys_proxies_registry_parsing() {
1303 let _lock = ENVLOCK.lock();
1305 let _g1 = env_guard("HTTP_PROXY");
1307 let _g2 = env_guard("http_proxy");
1308
1309 let baseline_proxies = get_sys_proxies(None);
1312 let valid_proxies = get_sys_proxies(Some(String::from("http://127.0.0.1/")));
1314 let valid_proxies_no_scheme = get_sys_proxies(Some(String::from("127.0.0.1")));
1315 let valid_proxies_explicit_https =
1316 get_sys_proxies(Some(String::from("https://127.0.0.1/")));
1317 let multiple_proxies = get_sys_proxies(Some(String::from(
1318 "http=127.0.0.1:8888;https=127.0.0.2:8888",
1319 )));
1320 let multiple_proxies_explicit_scheme = get_sys_proxies(Some(String::from(
1321 "http=http://127.0.0.1:8888;https=https://127.0.0.2:8888",
1322 )));
1323
1324 drop(_g1);
1326 drop(_g2);
1327 drop(_lock);
1329
1330 assert_eq!(baseline_proxies.contains_key("http"), false);
1331
1332 let p = &valid_proxies["http"];
1333 assert_eq!(p.scheme(), "http");
1334 assert_eq!(p.host(), "127.0.0.1");
1335
1336 let p = &valid_proxies_no_scheme["http"];
1337 assert_eq!(p.scheme(), "http");
1338 assert_eq!(p.host(), "127.0.0.1");
1339
1340 let p = &valid_proxies_no_scheme["https"];
1341 assert_eq!(p.scheme(), "http");
1342 assert_eq!(p.host(), "127.0.0.1");
1343
1344 let p = &valid_proxies_explicit_https["https"];
1345 assert_eq!(p.scheme(), "https");
1346 assert_eq!(p.host(), "127.0.0.1");
1347
1348 let p = &multiple_proxies["http"];
1349 assert_eq!(p.scheme(), "http");
1350 assert_eq!(p.host(), "127.0.0.1:8888");
1351
1352 let p = &multiple_proxies["https"];
1353 assert_eq!(p.scheme(), "http");
1354 assert_eq!(p.host(), "127.0.0.2:8888");
1355
1356 let p = &multiple_proxies_explicit_scheme["http"];
1357 assert_eq!(p.scheme(), "http");
1358 assert_eq!(p.host(), "127.0.0.1:8888");
1359
1360 let p = &multiple_proxies_explicit_scheme["https"];
1361 assert_eq!(p.scheme(), "https");
1362 assert_eq!(p.host(), "127.0.0.2:8888");
1363 }
1364
1365 #[test]
1366 fn test_get_sys_proxies_in_cgi() {
1367 let _lock = ENVLOCK.lock();
1369 let _g1 = env_guard("REQUEST_METHOD");
1371 let _g2 = env_guard("HTTP_PROXY");
1372
1373 env::set_var("HTTP_PROXY", "http://evil/");
1376
1377 let baseline_proxies = get_sys_proxies(None);
1378 env::set_var("REQUEST_METHOD", "GET");
1380
1381 let cgi_proxies = get_sys_proxies(None);
1382
1383 drop(_g1);
1385 drop(_g2);
1386 drop(_lock);
1388
1389 assert_eq!(baseline_proxies["http"].host(), "evil");
1391 assert!(!cgi_proxies.contains_key("http"));
1393 }
1394
1395 #[test]
1396 fn test_sys_no_proxy() {
1397 let _lock = ENVLOCK.lock();
1399 let _g1 = env_guard("HTTP_PROXY");
1401 let _g2 = env_guard("NO_PROXY");
1402
1403 let target = "http://example.domain/";
1404 env::set_var("HTTP_PROXY", target);
1405
1406 env::set_var(
1407 "NO_PROXY",
1408 ".foo.bar, bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17",
1409 );
1410
1411 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1413 p.no_proxy = NoProxy::from_env();
1414
1415 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1417 assert_eq!(intercepted_uri(&p, "http://notfoo.bar"), target);
1419 assert_eq!(intercepted_uri(&p, "http://notbar.baz"), target);
1421 assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target);
1423 assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target);
1425 assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target);
1427 assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target);
1429
1430 assert!(p.intercept(&url("http://hello.foo.bar")).is_none());
1432 assert!(p.intercept(&url("http://bar.baz")).is_none());
1434 assert!(p.intercept(&url("http://BAR.baz")).is_none());
1436 assert!(p.intercept(&url("http://foo.bar.baz")).is_none());
1438 assert!(p.intercept(&url("http://foo.bar")).is_none());
1440 assert!(p.intercept(&url("http://10.42.1.100")).is_none());
1442 assert!(p.intercept(&url("http://[::1]")).is_none());
1444 assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none());
1446 assert!(p.intercept(&url("http://10.124.7.8")).is_none());
1448
1449 drop(_g1);
1451 drop(_g2);
1452 drop(_lock);
1454 }
1455
1456 #[test]
1457 fn test_proxy_no_proxy_interception_for_proxy_types() {
1458 let proxy_url = "http://example.domain/";
1459 let no_proxy = ".no.proxy.tld";
1460
1461 let p = Proxy::all(proxy_url)
1463 .unwrap()
1464 .no_proxy(NoProxy::from_string(no_proxy));
1465
1466 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
1468
1469 assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1471
1472 let p = Proxy::http(proxy_url)
1474 .unwrap()
1475 .no_proxy(NoProxy::from_string(no_proxy));
1476
1477 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
1479
1480 assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
1482
1483 assert!(p.intercept(&url("https://hyper.rs")).is_none());
1485
1486 let p = Proxy::https(proxy_url)
1488 .unwrap()
1489 .no_proxy(NoProxy::from_string(no_proxy));
1490
1491 assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
1493
1494 assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1496
1497 assert!(p.intercept(&url("http://hyper.rs")).is_none());
1499
1500 let p = Proxy::custom(move |_url| Some(proxy_url)).no_proxy(NoProxy::from_string(no_proxy));
1502
1503 assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
1505
1506 assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1508 assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
1509 }
1510
1511 #[test]
1512 fn test_wildcard_sys_no_proxy() {
1513 let _lock = ENVLOCK.lock();
1515 let _g1 = env_guard("HTTP_PROXY");
1517 let _g2 = env_guard("NO_PROXY");
1518
1519 let target = "http://example.domain/";
1520 env::set_var("HTTP_PROXY", target);
1521
1522 env::set_var("NO_PROXY", "*");
1523
1524 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1526 p.no_proxy = NoProxy::from_env();
1527
1528 assert!(p.intercept(&url("http://foo.bar")).is_none());
1529
1530 drop(_g1);
1532 drop(_g2);
1533 drop(_lock);
1535 }
1536
1537 #[test]
1538 fn test_empty_sys_no_proxy() {
1539 let _lock = ENVLOCK.lock();
1541 let _g1 = env_guard("HTTP_PROXY");
1543 let _g2 = env_guard("NO_PROXY");
1544
1545 let target = "http://example.domain/";
1546 env::set_var("HTTP_PROXY", target);
1547
1548 env::set_var("NO_PROXY", ",");
1549
1550 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1552 p.no_proxy = NoProxy::from_env();
1553
1554 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1556
1557 drop(_g1);
1559 drop(_g2);
1560 drop(_lock);
1562 }
1563
1564 #[test]
1565 fn test_no_proxy_load() {
1566 let _lock = ENVLOCK.lock();
1568
1569 let _g1 = env_guard("no_proxy");
1570 let domain = "lower.case";
1571 env::set_var("no_proxy", domain);
1572 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1574 p.no_proxy = NoProxy::from_env();
1575 assert_eq!(
1576 p.no_proxy.expect("should have a no proxy set").domains.0[0],
1577 domain
1578 );
1579
1580 env::remove_var("no_proxy");
1581 let _g2 = env_guard("NO_PROXY");
1582 let domain = "upper.case";
1583 env::set_var("NO_PROXY", domain);
1584 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1586 p.no_proxy = NoProxy::from_env();
1587 assert_eq!(
1588 p.no_proxy.expect("should have a no proxy set").domains.0[0],
1589 domain
1590 );
1591
1592 let _g3 = env_guard("HTTP_PROXY");
1593 env::remove_var("NO_PROXY");
1594 env::remove_var("no_proxy");
1595 let target = "http://example.domain/";
1596 env::set_var("HTTP_PROXY", target);
1597
1598 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1600 p.no_proxy = NoProxy::from_env();
1601 assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
1602
1603 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1604
1605 drop(_g1);
1607 drop(_g2);
1608 drop(_g3);
1609 drop(_lock);
1611 }
1612
1613 #[cfg(any(target_os = "windows", target_os = "macos"))]
1614 #[test]
1615 fn test_type_prefix_extraction() {
1616 assert!(extract_type_prefix("test").is_none());
1617 assert!(extract_type_prefix("://test").is_none());
1618 assert!(extract_type_prefix("some:prefix://test").is_none());
1619 assert!(extract_type_prefix("some/prefix://test").is_none());
1620
1621 assert_eq!(extract_type_prefix("http://test").unwrap(), "http");
1622 assert_eq!(extract_type_prefix("a://test").unwrap(), "a");
1623 }
1624
1625 fn env_guard(name: impl Into<String>) -> EnvGuard {
1628 let name = name.into();
1629 let orig_val = env::var(&name).ok();
1630 env::remove_var(&name);
1631 EnvGuard { name, orig_val }
1632 }
1633
1634 struct EnvGuard {
1635 name: String,
1636 orig_val: Option<String>,
1637 }
1638
1639 impl Drop for EnvGuard {
1640 fn drop(&mut self) {
1641 if let Some(val) = self.orig_val.take() {
1642 env::set_var(&self.name, val);
1643 } else {
1644 env::remove_var(&self.name);
1645 }
1646 }
1647 }
1648
1649 #[test]
1650 fn test_has_http_auth() {
1651 let http_proxy_with_auth = Proxy {
1652 intercept: Intercept::Http(ProxyScheme::Http {
1653 auth: Some(HeaderValue::from_static("auth1")),
1654 host: http::uri::Authority::from_static("authority"),
1655 }),
1656 no_proxy: None,
1657 };
1658 assert!(http_proxy_with_auth.maybe_has_http_auth());
1659 assert_eq!(
1660 http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1661 Some(HeaderValue::from_static("auth1"))
1662 );
1663
1664 let http_proxy_without_auth = Proxy {
1665 intercept: Intercept::Http(ProxyScheme::Http {
1666 auth: None,
1667 host: http::uri::Authority::from_static("authority"),
1668 }),
1669 no_proxy: None,
1670 };
1671 assert!(!http_proxy_without_auth.maybe_has_http_auth());
1672 assert_eq!(
1673 http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1674 None
1675 );
1676
1677 let https_proxy_with_auth = Proxy {
1678 intercept: Intercept::Http(ProxyScheme::Https {
1679 auth: Some(HeaderValue::from_static("auth2")),
1680 host: http::uri::Authority::from_static("authority"),
1681 }),
1682 no_proxy: None,
1683 };
1684 assert!(https_proxy_with_auth.maybe_has_http_auth());
1685 assert_eq!(
1686 https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1687 Some(HeaderValue::from_static("auth2"))
1688 );
1689
1690 let all_http_proxy_with_auth = Proxy {
1691 intercept: Intercept::All(ProxyScheme::Http {
1692 auth: Some(HeaderValue::from_static("auth3")),
1693 host: http::uri::Authority::from_static("authority"),
1694 }),
1695 no_proxy: None,
1696 };
1697 assert!(all_http_proxy_with_auth.maybe_has_http_auth());
1698 assert_eq!(
1699 all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1700 Some(HeaderValue::from_static("auth3"))
1701 );
1702
1703 let all_https_proxy_with_auth = Proxy {
1704 intercept: Intercept::All(ProxyScheme::Https {
1705 auth: Some(HeaderValue::from_static("auth4")),
1706 host: http::uri::Authority::from_static("authority"),
1707 }),
1708 no_proxy: None,
1709 };
1710 assert!(all_https_proxy_with_auth.maybe_has_http_auth());
1711 assert_eq!(
1712 all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1713 Some(HeaderValue::from_static("auth4"))
1714 );
1715
1716 let all_https_proxy_without_auth = Proxy {
1717 intercept: Intercept::All(ProxyScheme::Https {
1718 auth: None,
1719 host: http::uri::Authority::from_static("authority"),
1720 }),
1721 no_proxy: None,
1722 };
1723 assert!(!all_https_proxy_without_auth.maybe_has_http_auth());
1724 assert_eq!(
1725 all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1726 None
1727 );
1728
1729 let system_http_proxy_with_auth = Proxy {
1730 intercept: Intercept::System(Arc::new({
1731 let mut m = HashMap::new();
1732 m.insert(
1733 "http".into(),
1734 ProxyScheme::Http {
1735 auth: Some(HeaderValue::from_static("auth5")),
1736 host: http::uri::Authority::from_static("authority"),
1737 },
1738 );
1739 m
1740 })),
1741 no_proxy: None,
1742 };
1743 assert!(system_http_proxy_with_auth.maybe_has_http_auth());
1744 assert_eq!(
1745 system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1746 Some(HeaderValue::from_static("auth5"))
1747 );
1748
1749 let system_https_proxy_with_auth = Proxy {
1750 intercept: Intercept::System(Arc::new({
1751 let mut m = HashMap::new();
1752 m.insert(
1753 "https".into(),
1754 ProxyScheme::Https {
1755 auth: Some(HeaderValue::from_static("auth6")),
1756 host: http::uri::Authority::from_static("authority"),
1757 },
1758 );
1759 m
1760 })),
1761 no_proxy: None,
1762 };
1763 assert!(!system_https_proxy_with_auth.maybe_has_http_auth());
1764 assert_eq!(
1765 system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1766 None
1767 );
1768 }
1769}
1770
1771#[cfg(test)]
1772mod test {
1773 mod into_proxy_scheme {
1774 use crate::Proxy;
1775 use std::error::Error;
1776 use std::mem::discriminant;
1777
1778 fn includes(haystack: &crate::error::Error, needle: url::ParseError) -> bool {
1779 let mut source = haystack.source();
1780 while let Some(error) = source {
1781 if let Some(parse_error) = error.downcast_ref::<url::ParseError>() {
1782 if discriminant(parse_error) == discriminant(&needle) {
1783 return true;
1784 }
1785 }
1786 source = error.source();
1787 }
1788 false
1789 }
1790
1791 fn check_parse_error(url: &str, needle: url::ParseError) {
1792 let error = Proxy::http(url).unwrap_err();
1793 if !includes(&error, needle) {
1794 panic!("{needle:?} expected; {error:?}, {error} found");
1795 }
1796 }
1797
1798 mod when_scheme_missing {
1799 mod and_url_is_valid {
1800 use crate::Proxy;
1801
1802 #[test]
1803 fn lookback_works() {
1804 let _ = Proxy::http("127.0.0.1").unwrap();
1805 }
1806
1807 #[test]
1808 fn loopback_port_works() {
1809 let _ = Proxy::http("127.0.0.1:8080").unwrap();
1810 }
1811
1812 #[test]
1813 fn loopback_username_works() {
1814 let _ = Proxy::http("username@127.0.0.1").unwrap();
1815 }
1816
1817 #[test]
1818 fn loopback_username_password_works() {
1819 let _ = Proxy::http("username:password@127.0.0.1").unwrap();
1820 }
1821
1822 #[test]
1823 fn loopback_username_password_port_works() {
1824 let _ = Proxy::http("ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
1825 }
1826
1827 #[test]
1828 fn domain_works() {
1829 let _ = Proxy::http("proxy.example.com").unwrap();
1830 }
1831
1832 #[test]
1833 fn domain_port_works() {
1834 let _ = Proxy::http("proxy.example.com:8080").unwrap();
1835 }
1836
1837 #[test]
1838 fn domain_username_works() {
1839 let _ = Proxy::http("username@proxy.example.com").unwrap();
1840 }
1841
1842 #[test]
1843 fn domain_username_password_works() {
1844 let _ = Proxy::http("username:password@proxy.example.com").unwrap();
1845 }
1846
1847 #[test]
1848 fn domain_username_password_port_works() {
1849 let _ =
1850 Proxy::http("ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080").unwrap();
1851 }
1852 }
1853 mod and_url_has_bad {
1854 use super::super::check_parse_error;
1855
1856 #[test]
1857 fn host() {
1858 check_parse_error("username@", url::ParseError::RelativeUrlWithoutBase);
1859 }
1860
1861 #[test]
1862 fn idna_encoding() {
1863 check_parse_error("xn---", url::ParseError::RelativeUrlWithoutBase);
1864 }
1865
1866 #[test]
1867 fn port() {
1868 check_parse_error("127.0.0.1:808080", url::ParseError::RelativeUrlWithoutBase);
1869 }
1870
1871 #[test]
1872 fn ip_v4_address() {
1873 check_parse_error("421.627.718.469", url::ParseError::RelativeUrlWithoutBase);
1874 }
1875
1876 #[test]
1877 fn ip_v6_address() {
1878 check_parse_error(
1879 "[56FE::2159:5BBC::6594]",
1880 url::ParseError::RelativeUrlWithoutBase,
1881 );
1882 }
1883
1884 #[test]
1885 fn invalid_domain_character() {
1886 check_parse_error("abc 123", url::ParseError::RelativeUrlWithoutBase);
1887 }
1888 }
1889 }
1890
1891 mod when_scheme_present {
1892 mod and_url_is_valid {
1893 use crate::Proxy;
1894
1895 #[test]
1896 fn loopback_works() {
1897 let _ = Proxy::http("http://127.0.0.1").unwrap();
1898 }
1899
1900 #[test]
1901 fn loopback_port_works() {
1902 let _ = Proxy::http("https://127.0.0.1:8080").unwrap();
1903 }
1904
1905 #[test]
1906 fn loopback_username_works() {
1907 let _ = Proxy::http("http://username@127.0.0.1").unwrap();
1908 }
1909
1910 #[test]
1911 fn loopback_username_password_works() {
1912 let _ = Proxy::http("https://username:password@127.0.0.1").unwrap();
1913 }
1914
1915 #[test]
1916 fn loopback_username_password_port_works() {
1917 let _ =
1918 Proxy::http("http://ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
1919 }
1920
1921 #[test]
1922 fn domain_works() {
1923 let _ = Proxy::http("https://proxy.example.com").unwrap();
1924 }
1925
1926 #[test]
1927 fn domain_port_works() {
1928 let _ = Proxy::http("http://proxy.example.com:8080").unwrap();
1929 }
1930
1931 #[test]
1932 fn domain_username_works() {
1933 let _ = Proxy::http("https://username@proxy.example.com").unwrap();
1934 }
1935
1936 #[test]
1937 fn domain_username_password_works() {
1938 let _ = Proxy::http("http://username:password@proxy.example.com").unwrap();
1939 }
1940
1941 #[test]
1942 fn domain_username_password_port_works() {
1943 let _ =
1944 Proxy::http("https://ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080")
1945 .unwrap();
1946 }
1947 }
1948 mod and_url_has_bad {
1949 use super::super::check_parse_error;
1950
1951 #[test]
1952 fn host() {
1953 check_parse_error("http://username@", url::ParseError::EmptyHost);
1954 }
1955
1956 #[test]
1957 fn idna_encoding() {
1958 check_parse_error("http://xn---", url::ParseError::IdnaError);
1959 }
1960
1961 #[test]
1962 fn port() {
1963 check_parse_error("http://127.0.0.1:808080", url::ParseError::InvalidPort);
1964 }
1965
1966 #[test]
1967 fn ip_v4_address() {
1968 check_parse_error(
1969 "http://421.627.718.469",
1970 url::ParseError::InvalidIpv4Address,
1971 );
1972 }
1973
1974 #[test]
1975 fn ip_v6_address() {
1976 check_parse_error(
1977 "http://[56FE::2159:5BBC::6594]",
1978 url::ParseError::InvalidIpv6Address,
1979 );
1980 }
1981
1982 #[test]
1983 fn invalid_domain_character() {
1984 check_parse_error("http://abc 123/", url::ParseError::InvalidDomainCharacter);
1985 }
1986 }
1987 }
1988 }
1989}