1use std::fmt;
2#[cfg(feature = "socks")]
3use std::net::SocketAddr;
4use std::sync::Arc;
5
6use crate::into_url::{IntoUrl, IntoUrlSealed};
7use crate::Url;
8use http::{header::HeaderValue, Uri};
9use ipnet::IpNet;
10use percent_encoding::percent_decode;
11use std::collections::HashMap;
12use std::env;
13use std::error::Error;
14use std::net::IpAddr;
15#[cfg(all(target_os = "macos", feature = "macos-system-configuration"))]
16use system_configuration::{
17 core_foundation::{
18 base::CFType,
19 dictionary::CFDictionary,
20 number::CFNumber,
21 string::{CFString, CFStringRef},
22 },
23 dynamic_store::SCDynamicStoreBuilder,
24 sys::schema_definitions::kSCPropNetProxiesHTTPEnable,
25 sys::schema_definitions::kSCPropNetProxiesHTTPPort,
26 sys::schema_definitions::kSCPropNetProxiesHTTPProxy,
27 sys::schema_definitions::kSCPropNetProxiesHTTPSEnable,
28 sys::schema_definitions::kSCPropNetProxiesHTTPSPort,
29 sys::schema_definitions::kSCPropNetProxiesHTTPSProxy,
30};
31
32#[cfg(target_os = "windows")]
33use windows_registry::CURRENT_USER;
34
35#[derive(Clone)]
68pub struct Proxy {
69 intercept: Intercept,
70 no_proxy: Option<NoProxy>,
71}
72
73#[derive(Clone, Debug)]
75enum Ip {
76 Address(IpAddr),
77 Network(IpNet),
78}
79
80#[derive(Clone, Debug, Default)]
83struct IpMatcher(Vec<Ip>);
84
85#[derive(Clone, Debug, Default)]
88struct DomainMatcher(Vec<String>);
89
90#[derive(Clone, Debug, Default)]
92pub struct NoProxy {
93 ips: IpMatcher,
94 domains: DomainMatcher,
95}
96
97#[derive(Clone)]
101pub enum ProxyScheme {
102 Http {
103 auth: Option<HeaderValue>,
104 host: http::uri::Authority,
105 },
106 Https {
107 auth: Option<HeaderValue>,
108 host: http::uri::Authority,
109 },
110 #[cfg(feature = "socks")]
111 Socks4 { addr: SocketAddr, remote_dns: bool },
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 if *parse_error == url::ParseError::RelativeUrlWithoutBase {
148 presumed_to_have_scheme = false;
149 break;
150 }
151 } else if err.downcast_ref::<crate::error::BadScheme>().is_some() {
152 presumed_to_have_scheme = false;
153 break;
154 }
155 source = err.source();
156 }
157 if presumed_to_have_scheme {
158 return Err(crate::error::builder(e));
159 }
160 let try_this = format!("http://{}", self.as_str());
162 try_this.into_url().map_err(|_| {
163 crate::error::builder(e)
165 })?
166 }
167 };
168 ProxyScheme::parse(url)
169 }
170}
171
172fn _implied_bounds() {
176 fn prox<T: IntoProxyScheme>(_t: T) {}
177
178 fn url<T: IntoUrl>(t: T) {
179 prox(t);
180 }
181}
182
183impl IntoProxyScheme for ProxyScheme {
184 fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> {
185 Ok(self)
186 }
187}
188
189impl Proxy {
190 pub fn http<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
205 Ok(Proxy::new(Intercept::Http(
206 proxy_scheme.into_proxy_scheme()?,
207 )))
208 }
209
210 pub fn https<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
225 Ok(Proxy::new(Intercept::Https(
226 proxy_scheme.into_proxy_scheme()?,
227 )))
228 }
229
230 pub fn all<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
245 Ok(Proxy::new(Intercept::All(
246 proxy_scheme.into_proxy_scheme()?,
247 )))
248 }
249
250 pub fn custom<F, U: IntoProxyScheme>(fun: F) -> Proxy
272 where
273 F: Fn(&Url) -> Option<U> + Send + Sync + 'static,
274 {
275 Proxy::new(Intercept::Custom(Custom {
276 auth: None,
277 func: Arc::new(move |url| fun(url).map(IntoProxyScheme::into_proxy_scheme)),
278 }))
279 }
280
281 pub(crate) fn system() -> Proxy {
282 let mut proxy = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(
283 get_from_platform(),
284 ))));
285 proxy.no_proxy = NoProxy::from_env();
286
287 #[cfg(target_os = "windows")]
288 {
289 if proxy.no_proxy.is_none() {
293 let win_exceptions: String = get_windows_proxy_exceptions();
294 proxy.no_proxy = NoProxy::from_string(&win_exceptions);
295 }
296 }
297
298 proxy
299 }
300
301 fn new(intercept: Intercept) -> Proxy {
302 Proxy {
303 intercept,
304 no_proxy: None,
305 }
306 }
307
308 pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy {
322 self.intercept.set_basic_auth(username, password);
323 self
324 }
325
326 pub fn custom_http_auth(mut self, header_value: HeaderValue) -> Proxy {
341 self.intercept.set_custom_http_auth(header_value);
342 self
343 }
344
345 pub fn no_proxy(mut self, no_proxy: Option<NoProxy>) -> Proxy {
359 self.no_proxy = no_proxy;
360 self
361 }
362
363 pub(crate) fn maybe_has_http_auth(&self) -> bool {
364 match &self.intercept {
365 Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().is_some(),
366 Intercept::Custom(_) => true,
368 Intercept::System(system) => system
369 .get("http")
370 .and_then(|s| s.maybe_http_auth())
371 .is_some(),
372 Intercept::Https(_) => false,
373 }
374 }
375
376 pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
377 match &self.intercept {
378 Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().cloned(),
379 Intercept::System(system) => system
380 .get("http")
381 .and_then(|s| s.maybe_http_auth().cloned()),
382 Intercept::Custom(custom) => {
383 custom.call(uri).and_then(|s| s.maybe_http_auth().cloned())
384 }
385 Intercept::Https(_) => None,
386 }
387 }
388
389 pub(crate) fn intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
390 let in_no_proxy = self
391 .no_proxy
392 .as_ref()
393 .map_or(false, |np| np.contains(uri.host()));
394 match self.intercept {
395 Intercept::All(ref u) => {
396 if !in_no_proxy {
397 Some(u.clone())
398 } else {
399 None
400 }
401 }
402 Intercept::Http(ref u) => {
403 if !in_no_proxy && uri.scheme() == "http" {
404 Some(u.clone())
405 } else {
406 None
407 }
408 }
409 Intercept::Https(ref u) => {
410 if !in_no_proxy && uri.scheme() == "https" {
411 Some(u.clone())
412 } else {
413 None
414 }
415 }
416 Intercept::System(ref map) => {
417 if in_no_proxy {
418 None
419 } else {
420 map.get(uri.scheme()).cloned()
421 }
422 }
423 Intercept::Custom(ref custom) => {
424 if !in_no_proxy {
425 custom.call(uri)
426 } else {
427 None
428 }
429 }
430 }
431 }
432
433 pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool {
434 match self.intercept {
435 Intercept::All(_) => true,
436 Intercept::Http(_) => uri.scheme() == "http",
437 Intercept::Https(_) => uri.scheme() == "https",
438 Intercept::System(ref map) => map.contains_key(uri.scheme()),
439 Intercept::Custom(ref custom) => custom.call(uri).is_some(),
440 }
441 }
442}
443
444impl fmt::Debug for Proxy {
445 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
446 f.debug_tuple("Proxy")
447 .field(&self.intercept)
448 .field(&self.no_proxy)
449 .finish()
450 }
451}
452
453impl NoProxy {
454 pub fn from_env() -> Option<NoProxy> {
457 let raw = env::var("NO_PROXY")
458 .or_else(|_| env::var("no_proxy"))
459 .ok()?;
460
461 Some(Self::from_string(&raw).unwrap_or_default())
465 }
466
467 pub fn from_string(no_proxy_list: &str) -> Option<Self> {
487 if no_proxy_list.is_empty() {
488 return None;
489 }
490 let mut ips = Vec::new();
491 let mut domains = Vec::new();
492 let parts = no_proxy_list.split(',').map(str::trim);
493 for part in parts {
494 match part.parse::<IpNet>() {
495 Ok(ip) => ips.push(Ip::Network(ip)),
497 Err(_) => match part.parse::<IpAddr>() {
498 Ok(addr) => ips.push(Ip::Address(addr)),
499 Err(_) => domains.push(part.to_owned()),
500 },
501 }
502 }
503 Some(NoProxy {
504 ips: IpMatcher(ips),
505 domains: DomainMatcher(domains),
506 })
507 }
508
509 fn contains(&self, host: &str) -> bool {
510 let host = if host.starts_with('[') {
513 let x: &[_] = &['[', ']'];
514 host.trim_matches(x)
515 } else {
516 host
517 };
518 match host.parse::<IpAddr>() {
519 Ok(ip) => self.ips.contains(ip),
521 Err(_) => self.domains.contains(host),
522 }
523 }
524}
525
526impl IpMatcher {
527 fn contains(&self, addr: IpAddr) -> bool {
528 for ip in &self.0 {
529 match ip {
530 Ip::Address(address) => {
531 if &addr == address {
532 return true;
533 }
534 }
535 Ip::Network(net) => {
536 if net.contains(&addr) {
537 return true;
538 }
539 }
540 }
541 }
542 false
543 }
544}
545
546impl DomainMatcher {
547 fn contains(&self, domain: &str) -> bool {
551 let domain_len = domain.len();
552 for d in &self.0 {
553 if d == domain || d.strip_prefix('.') == Some(domain) {
554 return true;
555 } else if domain.ends_with(d) {
556 if d.starts_with('.') {
557 return true;
560 } else if domain.as_bytes().get(domain_len - d.len() - 1) == Some(&b'.') {
561 return true;
564 }
565 } else if d == "*" {
566 return true;
567 }
568 }
569 false
570 }
571}
572
573impl ProxyScheme {
574 fn http(host: &str) -> crate::Result<Self> {
578 Ok(ProxyScheme::Http {
579 auth: None,
580 host: host.parse().map_err(crate::error::builder)?,
581 })
582 }
583
584 fn https(host: &str) -> crate::Result<Self> {
586 Ok(ProxyScheme::Https {
587 auth: None,
588 host: host.parse().map_err(crate::error::builder)?,
589 })
590 }
591
592 #[cfg(feature = "socks")]
598 fn socks4(addr: SocketAddr) -> crate::Result<Self> {
599 Ok(ProxyScheme::Socks4 {
600 addr,
601 remote_dns: false,
602 })
603 }
604
605 #[cfg(feature = "socks")]
613 fn socks4a(addr: SocketAddr) -> crate::Result<Self> {
614 Ok(ProxyScheme::Socks4 {
615 addr,
616 remote_dns: true,
617 })
618 }
619
620 #[cfg(feature = "socks")]
626 fn socks5(addr: SocketAddr) -> crate::Result<Self> {
627 Ok(ProxyScheme::Socks5 {
628 addr,
629 auth: None,
630 remote_dns: false,
631 })
632 }
633
634 #[cfg(feature = "socks")]
642 fn socks5h(addr: SocketAddr) -> crate::Result<Self> {
643 Ok(ProxyScheme::Socks5 {
644 addr,
645 auth: None,
646 remote_dns: true,
647 })
648 }
649
650 fn with_basic_auth<T: Into<String>, U: Into<String>>(
652 mut self,
653 username: T,
654 password: U,
655 ) -> Self {
656 self.set_basic_auth(username, password);
657 self
658 }
659
660 fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
661 match *self {
662 ProxyScheme::Http { ref mut auth, .. } => {
663 let header = encode_basic_auth(&username.into(), &password.into());
664 *auth = Some(header);
665 }
666 ProxyScheme::Https { ref mut auth, .. } => {
667 let header = encode_basic_auth(&username.into(), &password.into());
668 *auth = Some(header);
669 }
670 #[cfg(feature = "socks")]
671 ProxyScheme::Socks4 { .. } => {
672 panic!("Socks4 is not supported for this method")
673 }
674 #[cfg(feature = "socks")]
675 ProxyScheme::Socks5 { ref mut auth, .. } => {
676 *auth = Some((username.into(), password.into()));
677 }
678 }
679 }
680
681 fn set_custom_http_auth(&mut self, header_value: HeaderValue) {
682 match *self {
683 ProxyScheme::Http { ref mut auth, .. } => {
684 *auth = Some(header_value);
685 }
686 ProxyScheme::Https { ref mut auth, .. } => {
687 *auth = Some(header_value);
688 }
689 #[cfg(feature = "socks")]
690 ProxyScheme::Socks4 { .. } => {
691 panic!("Socks4 is not supported for this method")
692 }
693 #[cfg(feature = "socks")]
694 ProxyScheme::Socks5 { .. } => {
695 panic!("Socks5 is not supported for this method")
696 }
697 }
698 }
699
700 fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
701 match self {
702 ProxyScheme::Http { ref mut auth, .. } => {
703 if auth.is_none() {
704 *auth = update.clone();
705 }
706 }
707 ProxyScheme::Https { ref mut auth, .. } => {
708 if auth.is_none() {
709 *auth = update.clone();
710 }
711 }
712 #[cfg(feature = "socks")]
713 ProxyScheme::Socks4 { .. } => {}
714 #[cfg(feature = "socks")]
715 ProxyScheme::Socks5 { .. } => {}
716 }
717
718 self
719 }
720
721 fn parse(url: Url) -> crate::Result<Self> {
726 use url::Position;
727
728 #[cfg(feature = "socks")]
730 let to_addr = || {
731 let addrs = url
732 .socket_addrs(|| match url.scheme() {
733 "socks4" | "socks4a" | "socks5" | "socks5h" => Some(1080),
734 _ => None,
735 })
736 .map_err(crate::error::builder)?;
737 addrs
738 .into_iter()
739 .next()
740 .ok_or_else(|| crate::error::builder("unknown proxy scheme"))
741 };
742
743 let mut scheme = match url.scheme() {
744 "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
745 "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
746 #[cfg(feature = "socks")]
747 "socks4" => Self::socks4(to_addr()?)?,
748 #[cfg(feature = "socks")]
749 "socks4a" => Self::socks4a(to_addr()?)?,
750 #[cfg(feature = "socks")]
751 "socks5" => Self::socks5(to_addr()?)?,
752 #[cfg(feature = "socks")]
753 "socks5h" => Self::socks5h(to_addr()?)?,
754 _ => return Err(crate::error::builder("unknown proxy scheme")),
755 };
756
757 if let Some(pwd) = url.password() {
758 let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
759 let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
760 scheme = scheme.with_basic_auth(decoded_username, decoded_password);
761 }
762
763 Ok(scheme)
764 }
765
766 #[cfg(test)]
767 fn scheme(&self) -> &str {
768 match self {
769 ProxyScheme::Http { .. } => "http",
770 ProxyScheme::Https { .. } => "https",
771 #[cfg(feature = "socks")]
772 ProxyScheme::Socks4 { .. } => "socks4",
773 #[cfg(feature = "socks")]
774 ProxyScheme::Socks5 { .. } => "socks5",
775 }
776 }
777
778 #[cfg(test)]
779 fn host(&self) -> &str {
780 match self {
781 ProxyScheme::Http { host, .. } => host.as_str(),
782 ProxyScheme::Https { host, .. } => host.as_str(),
783 #[cfg(feature = "socks")]
784 ProxyScheme::Socks4 { .. } => panic!("socks4"),
785 #[cfg(feature = "socks")]
786 ProxyScheme::Socks5 { .. } => panic!("socks5"),
787 }
788 }
789}
790
791impl fmt::Debug for ProxyScheme {
792 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
793 match self {
794 ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{host}"),
795 ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{host}"),
796 #[cfg(feature = "socks")]
797 ProxyScheme::Socks4 { addr, remote_dns } => {
798 let h = if *remote_dns { "a" } else { "" };
799 write!(f, "socks4{}://{}", h, addr)
800 }
801 #[cfg(feature = "socks")]
802 ProxyScheme::Socks5 {
803 addr,
804 auth: _auth,
805 remote_dns,
806 } => {
807 let h = if *remote_dns { "h" } else { "" };
808 write!(f, "socks5{h}://{addr}")
809 }
810 }
811 }
812}
813
814type SystemProxyMap = HashMap<String, ProxyScheme>;
815
816#[derive(Clone, Debug)]
817enum Intercept {
818 All(ProxyScheme),
819 Http(ProxyScheme),
820 Https(ProxyScheme),
821 System(Arc<SystemProxyMap>),
822 Custom(Custom),
823}
824
825impl Intercept {
826 fn set_basic_auth(&mut self, username: &str, password: &str) {
827 match self {
828 Intercept::All(ref mut s)
829 | Intercept::Http(ref mut s)
830 | Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
831 Intercept::System(_) => unimplemented!(),
832 Intercept::Custom(ref mut custom) => {
833 let header = encode_basic_auth(username, password);
834 custom.auth = Some(header);
835 }
836 }
837 }
838
839 fn set_custom_http_auth(&mut self, header_value: HeaderValue) {
840 match self {
841 Intercept::All(ref mut s)
842 | Intercept::Http(ref mut s)
843 | Intercept::Https(ref mut s) => s.set_custom_http_auth(header_value),
844 Intercept::System(_) => unimplemented!(),
845 Intercept::Custom(ref mut custom) => {
846 custom.auth = Some(header_value);
847 }
848 }
849 }
850}
851
852#[derive(Clone)]
853struct Custom {
854 auth: Option<HeaderValue>,
856 func: Arc<dyn Fn(&Url) -> Option<crate::Result<ProxyScheme>> + Send + Sync + 'static>,
857}
858
859impl Custom {
860 fn call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
861 let url = format!(
862 "{}://{}{}{}",
863 uri.scheme(),
864 uri.host(),
865 uri.port().map_or("", |_| ":"),
866 uri.port().map_or(String::new(), |p| p.to_string())
867 )
868 .parse()
869 .expect("should be valid Url");
870
871 (self.func)(&url)
872 .and_then(|result| result.ok())
873 .map(|scheme| scheme.if_no_auth(&self.auth))
874 }
875}
876
877impl fmt::Debug for Custom {
878 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
879 f.write_str("_")
880 }
881}
882
883pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
884 crate::util::basic_auth(username, Some(password))
885}
886
887pub(crate) trait Dst {
890 fn scheme(&self) -> &str;
891 fn host(&self) -> &str;
892 fn port(&self) -> Option<u16>;
893}
894
895#[doc(hidden)]
896impl Dst for Uri {
897 fn scheme(&self) -> &str {
898 self.scheme().expect("Uri should have a scheme").as_str()
899 }
900
901 fn host(&self) -> &str {
902 Uri::host(self).expect("<Uri as Dst>::host should have a str")
903 }
904
905 fn port(&self) -> Option<u16> {
906 self.port().map(|p| p.as_u16())
907 }
908}
909
910fn get_sys_proxies(
921 #[cfg_attr(
922 not(any(target_os = "windows", target_os = "macos")),
923 allow(unused_variables)
924 )]
925 platform_proxies: Option<String>,
926) -> SystemProxyMap {
927 let proxies = get_from_environment();
928
929 #[cfg(any(target_os = "windows", target_os = "macos"))]
930 if proxies.is_empty() {
931 if let Some(platform_proxies) = platform_proxies {
934 return parse_platform_values(platform_proxies);
935 }
936 }
937
938 proxies
939}
940
941fn insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool {
942 if addr.trim().is_empty() {
943 false
945 } else if let Ok(valid_addr) = addr.into_proxy_scheme() {
946 proxies.insert(scheme.into(), valid_addr);
947 true
948 } else {
949 false
950 }
951}
952
953fn get_from_environment() -> SystemProxyMap {
954 let mut proxies = HashMap::new();
955
956 if !(insert_from_env(&mut proxies, "http", "ALL_PROXY")
957 && insert_from_env(&mut proxies, "https", "ALL_PROXY"))
958 {
959 insert_from_env(&mut proxies, "http", "all_proxy");
960 insert_from_env(&mut proxies, "https", "all_proxy");
961 }
962
963 if is_cgi() {
964 if log::log_enabled!(log::Level::Warn) && env::var_os("HTTP_PROXY").is_some() {
965 log::warn!("HTTP_PROXY environment variable ignored in CGI");
966 }
967 } else if !insert_from_env(&mut proxies, "http", "HTTP_PROXY") {
968 insert_from_env(&mut proxies, "http", "http_proxy");
969 }
970
971 if !insert_from_env(&mut proxies, "https", "HTTPS_PROXY") {
972 insert_from_env(&mut proxies, "https", "https_proxy");
973 }
974
975 proxies
976}
977
978fn insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool {
979 if let Ok(val) = env::var(var) {
980 insert_proxy(proxies, scheme, val)
981 } else {
982 false
983 }
984}
985
986fn is_cgi() -> bool {
991 env::var_os("REQUEST_METHOD").is_some()
992}
993
994#[cfg(target_os = "windows")]
995fn get_from_platform_impl() -> Result<Option<String>, Box<dyn Error>> {
996 let internet_setting = windows_registry::CURRENT_USER
997 .open("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
998 let proxy_enable = internet_setting.get_u32("ProxyEnable")?;
1000 let proxy_server = internet_setting.get_string("ProxyServer")?;
1001
1002 Ok((proxy_enable == 1).then_some(proxy_server))
1003}
1004
1005#[cfg(all(target_os = "macos", feature = "macos-system-configuration"))]
1006fn parse_setting_from_dynamic_store(
1007 proxies_map: &CFDictionary<CFString, CFType>,
1008 enabled_key: CFStringRef,
1009 host_key: CFStringRef,
1010 port_key: CFStringRef,
1011 scheme: &str,
1012) -> Option<String> {
1013 let proxy_enabled = proxies_map
1014 .find(enabled_key)
1015 .and_then(|flag| flag.downcast::<CFNumber>())
1016 .and_then(|flag| flag.to_i32())
1017 .unwrap_or(0)
1018 == 1;
1019
1020 if proxy_enabled {
1021 let proxy_host = proxies_map
1022 .find(host_key)
1023 .and_then(|host| host.downcast::<CFString>())
1024 .map(|host| host.to_string());
1025 let proxy_port = proxies_map
1026 .find(port_key)
1027 .and_then(|port| port.downcast::<CFNumber>())
1028 .and_then(|port| port.to_i32());
1029
1030 return match (proxy_host, proxy_port) {
1031 (Some(proxy_host), Some(proxy_port)) => {
1032 Some(format!("{scheme}={proxy_host}:{proxy_port}"))
1033 }
1034 (Some(proxy_host), None) => Some(format!("{scheme}={proxy_host}")),
1035 (None, Some(_)) => None,
1036 (None, None) => None,
1037 };
1038 }
1039
1040 None
1041}
1042
1043#[cfg(all(target_os = "macos", feature = "macos-system-configuration"))]
1044fn get_from_platform_impl() -> Result<Option<String>, Box<dyn Error>> {
1045 let store = SCDynamicStoreBuilder::new("reqwest").build();
1046
1047 let proxies_map = if let Some(proxies_map) = store.get_proxies() {
1048 proxies_map
1049 } else {
1050 return Ok(None);
1051 };
1052
1053 let http_proxy_config = parse_setting_from_dynamic_store(
1054 &proxies_map,
1055 unsafe { kSCPropNetProxiesHTTPEnable },
1056 unsafe { kSCPropNetProxiesHTTPProxy },
1057 unsafe { kSCPropNetProxiesHTTPPort },
1058 "http",
1059 );
1060 let https_proxy_config = parse_setting_from_dynamic_store(
1061 &proxies_map,
1062 unsafe { kSCPropNetProxiesHTTPSEnable },
1063 unsafe { kSCPropNetProxiesHTTPSProxy },
1064 unsafe { kSCPropNetProxiesHTTPSPort },
1065 "https",
1066 );
1067
1068 match http_proxy_config.as_ref().zip(https_proxy_config.as_ref()) {
1069 Some((http_config, https_config)) => Ok(Some(format!("{http_config};{https_config}"))),
1070 None => Ok(http_proxy_config.or(https_proxy_config)),
1071 }
1072}
1073
1074#[cfg(any(
1075 target_os = "windows",
1076 all(target_os = "macos", feature = "macos-system-configuration")
1077))]
1078fn get_from_platform() -> Option<String> {
1079 get_from_platform_impl().ok().flatten()
1080}
1081
1082#[cfg(not(any(
1083 target_os = "windows",
1084 all(target_os = "macos", feature = "macos-system-configuration")
1085)))]
1086fn get_from_platform() -> Option<String> {
1087 None
1088}
1089
1090#[cfg(any(target_os = "windows", target_os = "macos"))]
1091fn parse_platform_values_impl(platform_values: String) -> SystemProxyMap {
1092 let mut proxies = HashMap::new();
1093 if platform_values.contains("=") {
1094 for p in platform_values.split(";") {
1096 let protocol_parts: Vec<&str> = p.split("=").collect();
1097 match protocol_parts.as_slice() {
1098 [protocol, address] => {
1099 let address = if extract_type_prefix(*address).is_some() {
1102 String::from(*address)
1103 } else {
1104 format!("http://{address}")
1105 };
1106
1107 insert_proxy(&mut proxies, *protocol, address);
1108 }
1109 _ => {
1110 proxies.clear();
1113 break;
1114 }
1115 }
1116 }
1117 } else {
1118 if let Some(scheme) = extract_type_prefix(&platform_values) {
1119 insert_proxy(&mut proxies, scheme, platform_values.to_owned());
1121 } else {
1122 insert_proxy(&mut proxies, "http", format!("http://{platform_values}"));
1124 insert_proxy(&mut proxies, "https", format!("http://{platform_values}"));
1125 }
1126 }
1127 proxies
1128}
1129
1130#[cfg(any(target_os = "windows", target_os = "macos"))]
1133fn extract_type_prefix(address: &str) -> Option<&str> {
1134 if let Some(indice) = address.find("://") {
1135 if indice == 0 {
1136 None
1137 } else {
1138 let prefix = &address[..indice];
1139 let contains_banned = prefix.contains(|c| c == ':' || c == '/');
1140
1141 if !contains_banned {
1142 Some(prefix)
1143 } else {
1144 None
1145 }
1146 }
1147 } else {
1148 None
1149 }
1150}
1151
1152#[cfg(any(target_os = "windows", target_os = "macos"))]
1153fn parse_platform_values(platform_values: String) -> SystemProxyMap {
1154 parse_platform_values_impl(platform_values)
1155}
1156
1157#[cfg(target_os = "windows")]
1158fn get_windows_proxy_exceptions() -> String {
1159 let mut exceptions = String::new();
1160 if let Ok(key) =
1161 CURRENT_USER.create(r"Software\Microsoft\Windows\CurrentVersion\Internet Settings")
1162 {
1163 if let Ok(value) = key.get_string("ProxyOverride") {
1164 exceptions = value
1165 .split(';')
1166 .map(|s| s.trim())
1167 .collect::<Vec<&str>>()
1168 .join(",")
1169 .replace("*.", "");
1170 }
1171 }
1172 exceptions
1173}
1174
1175#[cfg(test)]
1176mod tests {
1177 use super::*;
1178 use once_cell::sync::Lazy;
1179 use std::sync::Mutex;
1180
1181 impl Dst for Url {
1182 fn scheme(&self) -> &str {
1183 Url::scheme(self)
1184 }
1185
1186 fn host(&self) -> &str {
1187 Url::host_str(self).expect("<Url as Dst>::host should have a str")
1188 }
1189
1190 fn port(&self) -> Option<u16> {
1191 Url::port(self)
1192 }
1193 }
1194
1195 fn url(s: &str) -> Url {
1196 s.parse().unwrap()
1197 }
1198
1199 fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
1200 let (scheme, host) = match p.intercept(&url(s)).unwrap() {
1201 ProxyScheme::Http { host, .. } => ("http", host),
1202 ProxyScheme::Https { host, .. } => ("https", host),
1203 #[cfg(feature = "socks")]
1204 _ => panic!("intercepted as socks"),
1205 };
1206 http::Uri::builder()
1207 .scheme(scheme)
1208 .authority(host)
1209 .path_and_query("/")
1210 .build()
1211 .expect("intercepted_uri")
1212 }
1213
1214 #[test]
1215 fn test_http() {
1216 let target = "http://example.domain/";
1217 let p = Proxy::http(target).unwrap();
1218
1219 let http = "http://hyper.rs";
1220 let other = "https://hyper.rs";
1221
1222 assert_eq!(intercepted_uri(&p, http), target);
1223 assert!(p.intercept(&url(other)).is_none());
1224 }
1225
1226 #[test]
1227 fn test_https() {
1228 let target = "http://example.domain/";
1229 let p = Proxy::https(target).unwrap();
1230
1231 let http = "http://hyper.rs";
1232 let other = "https://hyper.rs";
1233
1234 assert!(p.intercept(&url(http)).is_none());
1235 assert_eq!(intercepted_uri(&p, other), target);
1236 }
1237
1238 #[test]
1239 fn test_all() {
1240 let target = "http://example.domain/";
1241 let p = Proxy::all(target).unwrap();
1242
1243 let http = "http://hyper.rs";
1244 let https = "https://hyper.rs";
1245 let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
1246
1247 assert_eq!(intercepted_uri(&p, http), target);
1248 assert_eq!(intercepted_uri(&p, https), target);
1249 assert_eq!(intercepted_uri(&p, other), target);
1250 }
1251
1252 #[test]
1253 fn test_custom() {
1254 let target1 = "http://example.domain/";
1255 let target2 = "https://example.domain/";
1256 let p = Proxy::custom(move |url| {
1257 if url.host_str() == Some("hyper.rs") {
1258 target1.parse().ok()
1259 } else if url.scheme() == "http" {
1260 target2.parse().ok()
1261 } else {
1262 None::<Url>
1263 }
1264 });
1265
1266 let http = "http://seanmonstar.com";
1267 let https = "https://hyper.rs";
1268 let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
1269
1270 assert_eq!(intercepted_uri(&p, http), target2);
1271 assert_eq!(intercepted_uri(&p, https), target1);
1272 assert!(p.intercept(&url(other)).is_none());
1273 }
1274
1275 #[test]
1276 fn test_proxy_scheme_parse() {
1277 let ps = "http://foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1278
1279 match ps {
1280 ProxyScheme::Http { auth, host } => {
1281 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1282 assert_eq!(host, "localhost:1239");
1283 }
1284 other => panic!("unexpected: {other:?}"),
1285 }
1286 }
1287
1288 #[test]
1289 fn test_proxy_scheme_ip_address_default_http() {
1290 let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap();
1291
1292 match ps {
1293 ProxyScheme::Http { auth, host } => {
1294 assert!(auth.is_none());
1295 assert_eq!(host, "192.168.1.1:8888");
1296 }
1297 other => panic!("unexpected: {other:?}"),
1298 }
1299 }
1300
1301 #[test]
1302 fn test_proxy_scheme_parse_default_http_with_auth() {
1303 let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1305
1306 match ps {
1307 ProxyScheme::Http { auth, host } => {
1308 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1309 assert_eq!(host, "localhost:1239");
1310 }
1311 other => panic!("unexpected: {other:?}"),
1312 }
1313 }
1314
1315 #[test]
1316 fn test_domain_matcher() {
1317 let domains = vec![".foo.bar".into(), "bar.foo".into()];
1318 let matcher = DomainMatcher(domains);
1319
1320 assert!(matcher.contains("foo.bar"));
1322 assert!(matcher.contains("www.foo.bar"));
1324
1325 assert!(matcher.contains("bar.foo"));
1327 assert!(matcher.contains("www.bar.foo"));
1329
1330 assert!(!matcher.contains("notfoo.bar"));
1332 assert!(!matcher.contains("notbar.foo"));
1333 }
1334
1335 struct MutexInner;
1337
1338 static ENVLOCK: Lazy<Mutex<MutexInner>> = Lazy::new(|| Mutex::new(MutexInner));
1339
1340 #[test]
1341 fn test_get_sys_proxies_parsing() {
1342 let _lock = ENVLOCK.lock();
1344 let _g1 = env_guard("HTTP_PROXY");
1346 let _g2 = env_guard("http_proxy");
1347 let _g3 = env_guard("ALL_PROXY");
1348
1349 let baseline_proxies = get_sys_proxies(None);
1352 env::set_var("http_proxy", "file://123465");
1354 let invalid_proxies = get_sys_proxies(None);
1355 env::set_var("http_proxy", "127.0.0.1/");
1357 let valid_proxies = get_sys_proxies(None);
1358 env::set_var("ALL_PROXY", "127.0.0.2/");
1360 let all_proxies = get_sys_proxies(None);
1361
1362 drop(_g1);
1364 drop(_g2);
1365 drop(_lock);
1367
1368 assert!(!baseline_proxies.contains_key("http"));
1369 assert!(!invalid_proxies.contains_key("http"));
1370
1371 let p = &valid_proxies["http"];
1372 assert_eq!(p.scheme(), "http");
1373 assert_eq!(p.host(), "127.0.0.1");
1374
1375 assert_eq!(all_proxies.len(), 2);
1376 assert_eq!(all_proxies["https"].host(), "127.0.0.2");
1378 assert_eq!(all_proxies["http"].host(), "127.0.0.1");
1380 }
1381
1382 #[cfg(any(target_os = "windows", target_os = "macos"))]
1383 #[test]
1384 fn test_get_sys_proxies_registry_parsing() {
1385 let _lock = ENVLOCK.lock();
1387 let _g1 = env_guard("HTTP_PROXY");
1389 let _g2 = env_guard("http_proxy");
1390
1391 let baseline_proxies = get_sys_proxies(None);
1394 let valid_proxies = get_sys_proxies(Some(String::from("http://127.0.0.1/")));
1396 let valid_proxies_no_scheme = get_sys_proxies(Some(String::from("127.0.0.1")));
1397 let valid_proxies_explicit_https =
1398 get_sys_proxies(Some(String::from("https://127.0.0.1/")));
1399 let multiple_proxies = get_sys_proxies(Some(String::from(
1400 "http=127.0.0.1:8888;https=127.0.0.2:8888",
1401 )));
1402 let multiple_proxies_explicit_scheme = get_sys_proxies(Some(String::from(
1403 "http=http://127.0.0.1:8888;https=https://127.0.0.2:8888",
1404 )));
1405
1406 drop(_g1);
1408 drop(_g2);
1409 drop(_lock);
1411
1412 assert_eq!(baseline_proxies.contains_key("http"), false);
1413
1414 let p = &valid_proxies["http"];
1415 assert_eq!(p.scheme(), "http");
1416 assert_eq!(p.host(), "127.0.0.1");
1417
1418 let p = &valid_proxies_no_scheme["http"];
1419 assert_eq!(p.scheme(), "http");
1420 assert_eq!(p.host(), "127.0.0.1");
1421
1422 let p = &valid_proxies_no_scheme["https"];
1423 assert_eq!(p.scheme(), "http");
1424 assert_eq!(p.host(), "127.0.0.1");
1425
1426 let p = &valid_proxies_explicit_https["https"];
1427 assert_eq!(p.scheme(), "https");
1428 assert_eq!(p.host(), "127.0.0.1");
1429
1430 let p = &multiple_proxies["http"];
1431 assert_eq!(p.scheme(), "http");
1432 assert_eq!(p.host(), "127.0.0.1:8888");
1433
1434 let p = &multiple_proxies["https"];
1435 assert_eq!(p.scheme(), "http");
1436 assert_eq!(p.host(), "127.0.0.2:8888");
1437
1438 let p = &multiple_proxies_explicit_scheme["http"];
1439 assert_eq!(p.scheme(), "http");
1440 assert_eq!(p.host(), "127.0.0.1:8888");
1441
1442 let p = &multiple_proxies_explicit_scheme["https"];
1443 assert_eq!(p.scheme(), "https");
1444 assert_eq!(p.host(), "127.0.0.2:8888");
1445 }
1446
1447 #[test]
1448 fn test_get_sys_proxies_in_cgi() {
1449 let _lock = ENVLOCK.lock();
1451 let _g1 = env_guard("REQUEST_METHOD");
1453 let _g2 = env_guard("HTTP_PROXY");
1454
1455 env::set_var("HTTP_PROXY", "http://evil/");
1458
1459 let baseline_proxies = get_sys_proxies(None);
1460 env::set_var("REQUEST_METHOD", "GET");
1462
1463 let cgi_proxies = get_sys_proxies(None);
1464
1465 drop(_g1);
1467 drop(_g2);
1468 drop(_lock);
1470
1471 assert_eq!(baseline_proxies["http"].host(), "evil");
1473 assert!(!cgi_proxies.contains_key("http"));
1475 }
1476
1477 #[test]
1478 fn test_sys_no_proxy() {
1479 let _lock = ENVLOCK.lock();
1481 let _g1 = env_guard("HTTP_PROXY");
1483 let _g2 = env_guard("NO_PROXY");
1484
1485 let target = "http://example.domain/";
1486 env::set_var("HTTP_PROXY", target);
1487
1488 env::set_var(
1489 "NO_PROXY",
1490 ".foo.bar, bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17",
1491 );
1492
1493 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1495 p.no_proxy = NoProxy::from_env();
1496
1497 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1499 assert_eq!(intercepted_uri(&p, "http://notfoo.bar"), target);
1501 assert_eq!(intercepted_uri(&p, "http://notbar.baz"), target);
1503 assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target);
1505 assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target);
1507 assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target);
1509 assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target);
1511
1512 assert!(p.intercept(&url("http://hello.foo.bar")).is_none());
1514 assert!(p.intercept(&url("http://bar.baz")).is_none());
1516 assert!(p.intercept(&url("http://BAR.baz")).is_none());
1518 assert!(p.intercept(&url("http://foo.bar.baz")).is_none());
1520 assert!(p.intercept(&url("http://foo.bar")).is_none());
1522 assert!(p.intercept(&url("http://10.42.1.100")).is_none());
1524 assert!(p.intercept(&url("http://[::1]")).is_none());
1526 assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none());
1528 assert!(p.intercept(&url("http://10.124.7.8")).is_none());
1530
1531 drop(_g1);
1533 drop(_g2);
1534 drop(_lock);
1536 }
1537
1538 #[test]
1539 fn test_proxy_no_proxy_interception_for_proxy_types() {
1540 let proxy_url = "http://example.domain/";
1541 let no_proxy = ".no.proxy.tld";
1542
1543 let p = Proxy::all(proxy_url)
1545 .unwrap()
1546 .no_proxy(NoProxy::from_string(no_proxy));
1547
1548 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
1550
1551 assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1553
1554 let p = Proxy::http(proxy_url)
1556 .unwrap()
1557 .no_proxy(NoProxy::from_string(no_proxy));
1558
1559 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
1561
1562 assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
1564
1565 assert!(p.intercept(&url("https://hyper.rs")).is_none());
1567
1568 let p = Proxy::https(proxy_url)
1570 .unwrap()
1571 .no_proxy(NoProxy::from_string(no_proxy));
1572
1573 assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
1575
1576 assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1578
1579 assert!(p.intercept(&url("http://hyper.rs")).is_none());
1581
1582 let p = Proxy::custom(move |_url| Some(proxy_url)).no_proxy(NoProxy::from_string(no_proxy));
1584
1585 assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
1587
1588 assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1590 assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
1591 }
1592
1593 #[test]
1594 fn test_wildcard_sys_no_proxy() {
1595 let _lock = ENVLOCK.lock();
1597 let _g1 = env_guard("HTTP_PROXY");
1599 let _g2 = env_guard("NO_PROXY");
1600
1601 let target = "http://example.domain/";
1602 env::set_var("HTTP_PROXY", target);
1603
1604 env::set_var("NO_PROXY", "*");
1605
1606 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1608 p.no_proxy = NoProxy::from_env();
1609
1610 assert!(p.intercept(&url("http://foo.bar")).is_none());
1611
1612 drop(_g1);
1614 drop(_g2);
1615 drop(_lock);
1617 }
1618
1619 #[test]
1620 fn test_empty_sys_no_proxy() {
1621 let _lock = ENVLOCK.lock();
1623 let _g1 = env_guard("HTTP_PROXY");
1625 let _g2 = env_guard("NO_PROXY");
1626
1627 let target = "http://example.domain/";
1628 env::set_var("HTTP_PROXY", target);
1629
1630 env::set_var("NO_PROXY", ",");
1631
1632 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1634 p.no_proxy = NoProxy::from_env();
1635
1636 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1638
1639 env::set_var("NO_PROXY", "");
1641
1642 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1644 p.no_proxy = NoProxy::from_env();
1645 assert!(p.no_proxy.is_some());
1647
1648 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1650
1651 drop(_g1);
1653 drop(_g2);
1654 drop(_lock);
1656 }
1657
1658 #[test]
1659 fn test_no_proxy_load() {
1660 let _lock = ENVLOCK.lock();
1662
1663 let _g1 = env_guard("no_proxy");
1664 let domain = "lower.case";
1665 env::set_var("no_proxy", domain);
1666 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1668 p.no_proxy = NoProxy::from_env();
1669 assert_eq!(
1670 p.no_proxy.expect("should have a no proxy set").domains.0[0],
1671 domain
1672 );
1673
1674 env::remove_var("no_proxy");
1675 let _g2 = env_guard("NO_PROXY");
1676 let domain = "upper.case";
1677 env::set_var("NO_PROXY", domain);
1678 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1680 p.no_proxy = NoProxy::from_env();
1681 assert_eq!(
1682 p.no_proxy.expect("should have a no proxy set").domains.0[0],
1683 domain
1684 );
1685
1686 let _g3 = env_guard("HTTP_PROXY");
1687 env::remove_var("NO_PROXY");
1688 env::remove_var("no_proxy");
1689 let target = "http://example.domain/";
1690 env::set_var("HTTP_PROXY", target);
1691
1692 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1694 p.no_proxy = NoProxy::from_env();
1695 assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
1696
1697 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1698
1699 drop(_g1);
1701 drop(_g2);
1702 drop(_g3);
1703 drop(_lock);
1705 }
1706
1707 #[cfg(any(target_os = "windows", target_os = "macos"))]
1708 #[test]
1709 fn test_type_prefix_extraction() {
1710 assert!(extract_type_prefix("test").is_none());
1711 assert!(extract_type_prefix("://test").is_none());
1712 assert!(extract_type_prefix("some:prefix://test").is_none());
1713 assert!(extract_type_prefix("some/prefix://test").is_none());
1714
1715 assert_eq!(extract_type_prefix("http://test").unwrap(), "http");
1716 assert_eq!(extract_type_prefix("a://test").unwrap(), "a");
1717 }
1718
1719 fn env_guard(name: impl Into<String>) -> EnvGuard {
1722 let name = name.into();
1723 let orig_val = env::var(&name).ok();
1724 env::remove_var(&name);
1725 EnvGuard { name, orig_val }
1726 }
1727
1728 struct EnvGuard {
1729 name: String,
1730 orig_val: Option<String>,
1731 }
1732
1733 impl Drop for EnvGuard {
1734 fn drop(&mut self) {
1735 if let Some(val) = self.orig_val.take() {
1736 env::set_var(&self.name, val);
1737 } else {
1738 env::remove_var(&self.name);
1739 }
1740 }
1741 }
1742
1743 #[test]
1744 fn test_has_http_auth() {
1745 let http_proxy_with_auth = Proxy {
1746 intercept: Intercept::Http(ProxyScheme::Http {
1747 auth: Some(HeaderValue::from_static("auth1")),
1748 host: http::uri::Authority::from_static("authority"),
1749 }),
1750 no_proxy: None,
1751 };
1752 assert!(http_proxy_with_auth.maybe_has_http_auth());
1753 assert_eq!(
1754 http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1755 Some(HeaderValue::from_static("auth1"))
1756 );
1757
1758 let http_proxy_without_auth = Proxy {
1759 intercept: Intercept::Http(ProxyScheme::Http {
1760 auth: None,
1761 host: http::uri::Authority::from_static("authority"),
1762 }),
1763 no_proxy: None,
1764 };
1765 assert!(!http_proxy_without_auth.maybe_has_http_auth());
1766 assert_eq!(
1767 http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1768 None
1769 );
1770
1771 let https_proxy_with_auth = Proxy {
1772 intercept: Intercept::Http(ProxyScheme::Https {
1773 auth: Some(HeaderValue::from_static("auth2")),
1774 host: http::uri::Authority::from_static("authority"),
1775 }),
1776 no_proxy: None,
1777 };
1778 assert!(https_proxy_with_auth.maybe_has_http_auth());
1779 assert_eq!(
1780 https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1781 Some(HeaderValue::from_static("auth2"))
1782 );
1783
1784 let all_http_proxy_with_auth = Proxy {
1785 intercept: Intercept::All(ProxyScheme::Http {
1786 auth: Some(HeaderValue::from_static("auth3")),
1787 host: http::uri::Authority::from_static("authority"),
1788 }),
1789 no_proxy: None,
1790 };
1791 assert!(all_http_proxy_with_auth.maybe_has_http_auth());
1792 assert_eq!(
1793 all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1794 Some(HeaderValue::from_static("auth3"))
1795 );
1796
1797 let all_https_proxy_with_auth = Proxy {
1798 intercept: Intercept::All(ProxyScheme::Https {
1799 auth: Some(HeaderValue::from_static("auth4")),
1800 host: http::uri::Authority::from_static("authority"),
1801 }),
1802 no_proxy: None,
1803 };
1804 assert!(all_https_proxy_with_auth.maybe_has_http_auth());
1805 assert_eq!(
1806 all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1807 Some(HeaderValue::from_static("auth4"))
1808 );
1809
1810 let all_https_proxy_without_auth = Proxy {
1811 intercept: Intercept::All(ProxyScheme::Https {
1812 auth: None,
1813 host: http::uri::Authority::from_static("authority"),
1814 }),
1815 no_proxy: None,
1816 };
1817 assert!(!all_https_proxy_without_auth.maybe_has_http_auth());
1818 assert_eq!(
1819 all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1820 None
1821 );
1822
1823 let system_http_proxy_with_auth = Proxy {
1824 intercept: Intercept::System(Arc::new({
1825 let mut m = HashMap::new();
1826 m.insert(
1827 "http".into(),
1828 ProxyScheme::Http {
1829 auth: Some(HeaderValue::from_static("auth5")),
1830 host: http::uri::Authority::from_static("authority"),
1831 },
1832 );
1833 m
1834 })),
1835 no_proxy: None,
1836 };
1837 assert!(system_http_proxy_with_auth.maybe_has_http_auth());
1838 assert_eq!(
1839 system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1840 Some(HeaderValue::from_static("auth5"))
1841 );
1842
1843 let system_https_proxy_with_auth = Proxy {
1844 intercept: Intercept::System(Arc::new({
1845 let mut m = HashMap::new();
1846 m.insert(
1847 "https".into(),
1848 ProxyScheme::Https {
1849 auth: Some(HeaderValue::from_static("auth6")),
1850 host: http::uri::Authority::from_static("authority"),
1851 },
1852 );
1853 m
1854 })),
1855 no_proxy: None,
1856 };
1857 assert!(!system_https_proxy_with_auth.maybe_has_http_auth());
1858 assert_eq!(
1859 system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1860 None
1861 );
1862 }
1863}
1864
1865#[cfg(test)]
1866mod test {
1867 mod into_proxy_scheme {
1868 use crate::Proxy;
1869 use std::error::Error;
1870 use std::mem::discriminant;
1871
1872 fn includes(haystack: &crate::error::Error, needle: url::ParseError) -> bool {
1873 let mut source = haystack.source();
1874 while let Some(error) = source {
1875 if let Some(parse_error) = error.downcast_ref::<url::ParseError>() {
1876 if discriminant(parse_error) == discriminant(&needle) {
1877 return true;
1878 }
1879 }
1880 source = error.source();
1881 }
1882 false
1883 }
1884
1885 fn check_parse_error(url: &str, needle: url::ParseError) {
1886 let error = Proxy::http(url).unwrap_err();
1887 if !includes(&error, needle) {
1888 panic!("{needle:?} expected; {error:?}, {error} found");
1889 }
1890 }
1891
1892 mod when_scheme_missing {
1893 mod and_url_is_valid {
1894 use crate::Proxy;
1895
1896 #[test]
1897 fn lookback_works() {
1898 let _ = Proxy::http("127.0.0.1").unwrap();
1899 }
1900
1901 #[test]
1902 fn loopback_port_works() {
1903 let _ = Proxy::http("127.0.0.1:8080").unwrap();
1904 }
1905
1906 #[test]
1907 fn loopback_username_works() {
1908 let _ = Proxy::http("username@127.0.0.1").unwrap();
1909 }
1910
1911 #[test]
1912 fn loopback_username_password_works() {
1913 let _ = Proxy::http("username:password@127.0.0.1").unwrap();
1914 }
1915
1916 #[test]
1917 fn loopback_username_password_port_works() {
1918 let _ = Proxy::http("ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
1919 }
1920
1921 #[test]
1922 fn domain_works() {
1923 let _ = Proxy::http("proxy.example.com").unwrap();
1924 }
1925
1926 #[test]
1927 fn domain_port_works() {
1928 let _ = Proxy::http("proxy.example.com:8080").unwrap();
1929 }
1930
1931 #[test]
1932 fn domain_username_works() {
1933 let _ = Proxy::http("username@proxy.example.com").unwrap();
1934 }
1935
1936 #[test]
1937 fn domain_username_password_works() {
1938 let _ = Proxy::http("username:password@proxy.example.com").unwrap();
1939 }
1940
1941 #[test]
1942 fn domain_username_password_port_works() {
1943 let _ =
1944 Proxy::http("ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080").unwrap();
1945 }
1946 }
1947 mod and_url_has_bad {
1948 use super::super::check_parse_error;
1949
1950 #[test]
1951 fn host() {
1952 check_parse_error("username@", url::ParseError::RelativeUrlWithoutBase);
1953 }
1954
1955 #[test]
1956 fn idna_encoding() {
1957 check_parse_error("xn---", url::ParseError::RelativeUrlWithoutBase);
1958 }
1959
1960 #[test]
1961 fn port() {
1962 check_parse_error("127.0.0.1:808080", url::ParseError::RelativeUrlWithoutBase);
1963 }
1964
1965 #[test]
1966 fn ip_v4_address() {
1967 check_parse_error("421.627.718.469", url::ParseError::RelativeUrlWithoutBase);
1968 }
1969
1970 #[test]
1971 fn ip_v6_address() {
1972 check_parse_error(
1973 "[56FE::2159:5BBC::6594]",
1974 url::ParseError::RelativeUrlWithoutBase,
1975 );
1976 }
1977
1978 #[test]
1979 fn invalid_domain_character() {
1980 check_parse_error("abc 123", url::ParseError::RelativeUrlWithoutBase);
1981 }
1982 }
1983 }
1984
1985 mod when_scheme_present {
1986 mod and_url_is_valid {
1987 use crate::Proxy;
1988
1989 #[test]
1990 fn loopback_works() {
1991 let _ = Proxy::http("http://127.0.0.1").unwrap();
1992 }
1993
1994 #[test]
1995 fn loopback_port_works() {
1996 let _ = Proxy::http("https://127.0.0.1:8080").unwrap();
1997 }
1998
1999 #[test]
2000 fn loopback_username_works() {
2001 let _ = Proxy::http("http://username@127.0.0.1").unwrap();
2002 }
2003
2004 #[test]
2005 fn loopback_username_password_works() {
2006 let _ = Proxy::http("https://username:password@127.0.0.1").unwrap();
2007 }
2008
2009 #[test]
2010 fn loopback_username_password_port_works() {
2011 let _ =
2012 Proxy::http("http://ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
2013 }
2014
2015 #[test]
2016 fn domain_works() {
2017 let _ = Proxy::http("https://proxy.example.com").unwrap();
2018 }
2019
2020 #[test]
2021 fn domain_port_works() {
2022 let _ = Proxy::http("http://proxy.example.com:8080").unwrap();
2023 }
2024
2025 #[test]
2026 fn domain_username_works() {
2027 let _ = Proxy::http("https://username@proxy.example.com").unwrap();
2028 }
2029
2030 #[test]
2031 fn domain_username_password_works() {
2032 let _ = Proxy::http("http://username:password@proxy.example.com").unwrap();
2033 }
2034
2035 #[test]
2036 fn domain_username_password_port_works() {
2037 let _ =
2038 Proxy::http("https://ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080")
2039 .unwrap();
2040 }
2041 }
2042 mod and_url_has_bad {
2043 use super::super::check_parse_error;
2044
2045 #[test]
2046 fn host() {
2047 check_parse_error("http://username@", url::ParseError::EmptyHost);
2048 }
2049
2050 #[test]
2051 fn idna_encoding() {
2052 check_parse_error("http://xn---", url::ParseError::IdnaError);
2053 }
2054
2055 #[test]
2056 fn port() {
2057 check_parse_error("http://127.0.0.1:808080", url::ParseError::InvalidPort);
2058 }
2059
2060 #[test]
2061 fn ip_v4_address() {
2062 check_parse_error(
2063 "http://421.627.718.469",
2064 url::ParseError::InvalidIpv4Address,
2065 );
2066 }
2067
2068 #[test]
2069 fn ip_v6_address() {
2070 check_parse_error(
2071 "http://[56FE::2159:5BBC::6594]",
2072 url::ParseError::InvalidIpv6Address,
2073 );
2074 }
2075 }
2076 }
2077 }
2078}