1 use std::fmt;
2 #[cfg(feature = "socks")]
3 use std::net::SocketAddr;
4 use std::sync::Arc;
5 
6 use crate::into_url::{IntoUrl, IntoUrlSealed};
7 use crate::Url;
8 use http::{header::HeaderValue, Uri};
9 use ipnet::IpNet;
10 use percent_encoding::percent_decode;
11 use std::collections::HashMap;
12 use std::env;
13 #[cfg(target_os = "windows")]
14 use std::error::Error;
15 use std::net::IpAddr;
16 #[cfg(target_os = "windows")]
17 use winreg::enums::HKEY_CURRENT_USER;
18 #[cfg(target_os = "windows")]
19 use winreg::RegKey;
20 
21 /// Configuration of a proxy that a `Client` should pass requests to.
22 ///
23 /// A `Proxy` has a couple pieces to it:
24 ///
25 /// - a URL of how to talk to the proxy
26 /// - rules on what `Client` requests should be directed to the proxy
27 ///
28 /// For instance, let's look at `Proxy::http`:
29 ///
30 /// ```rust
31 /// # fn run() -> Result<(), Box<std::error::Error>> {
32 /// let proxy = reqwest::Proxy::http("https://secure.example")?;
33 /// # Ok(())
34 /// # }
35 /// ```
36 ///
37 /// This proxy will intercept all HTTP requests, and make use of the proxy
38 /// at `https://secure.example`. A request to `http://hyper.rs` will talk
39 /// to your proxy. A request to `https://hyper.rs` will not.
40 ///
41 /// Multiple `Proxy` rules can be configured for a `Client`. The `Client` will
42 /// check each `Proxy` in the order it was added. This could mean that a
43 /// `Proxy` added first with eager intercept rules, such as `Proxy::all`,
44 /// would prevent a `Proxy` later in the list from ever working, so take care.
45 ///
46 /// By enabling the `"socks"` feature it is possible to use a socks proxy:
47 /// ```rust
48 /// # fn run() -> Result<(), Box<std::error::Error>> {
49 /// let proxy = reqwest::Proxy::http("socks5://192.168.1.1:9000")?;
50 /// # Ok(())
51 /// # }
52 /// ```
53 #[derive(Clone)]
54 pub struct Proxy {
55     intercept: Intercept,
56     no_proxy: Option<NoProxy>,
57 }
58 
59 /// Represents a possible matching entry for an IP address
60 #[derive(Clone, Debug)]
61 enum Ip {
62     Address(IpAddr),
63     Network(IpNet),
64 }
65 
66 /// A wrapper around a list of IP cidr blocks or addresses with a [IpMatcher::contains] method for
67 /// checking if an IP address is contained within the matcher
68 #[derive(Clone, Debug, Default)]
69 struct IpMatcher(Vec<Ip>);
70 
71 /// A wrapper around a list of domains with a [DomainMatcher::contains] method for checking if a
72 /// domain is contained within the matcher
73 #[derive(Clone, Debug, Default)]
74 struct DomainMatcher(Vec<String>);
75 
76 /// A configuration for filtering out requests that shouldn't be proxied
77 #[derive(Clone, Debug, Default)]
78 struct NoProxy {
79     ips: IpMatcher,
80     domains: DomainMatcher,
81 }
82 
83 /// A particular scheme used for proxying requests.
84 ///
85 /// For example, HTTP vs SOCKS5
86 #[derive(Clone)]
87 pub enum ProxyScheme {
88     Http {
89         auth: Option<HeaderValue>,
90         host: http::uri::Authority,
91     },
92     Https {
93         auth: Option<HeaderValue>,
94         host: http::uri::Authority,
95     },
96     #[cfg(feature = "socks")]
97     Socks5 {
98         addr: SocketAddr,
99         auth: Option<(String, String)>,
100         remote_dns: bool,
101     },
102 }
103 
104 /// Trait used for converting into a proxy scheme. This trait supports
105 /// parsing from a URL-like type, whilst also supporting proxy schemes
106 /// built directly using the factory methods.
107 pub trait IntoProxyScheme {
into_proxy_scheme(self) -> crate::Result<ProxyScheme>108     fn into_proxy_scheme(self) -> crate::Result<ProxyScheme>;
109 }
110 
111 impl<S: IntoUrl> IntoProxyScheme for S {
into_proxy_scheme(self) -> crate::Result<ProxyScheme>112     fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> {
113         // validate the URL
114         let url = match self.as_str().into_url() {
115             Ok(ok) => ok,
116             Err(e) => {
117                 // the issue could have been caused by a missing scheme, so we try adding http://
118                 format!("http://{}", self.as_str())
119                     .into_url()
120                     .map_err(|_| {
121                         // return the original error
122                         crate::error::builder(e)
123                     })?
124             }
125         };
126         ProxyScheme::parse(url)
127     }
128 }
129 
130 // These bounds are accidentally leaked by the blanket impl of IntoProxyScheme
131 // for all types that implement IntoUrl. So, this function exists to detect
132 // if we were to break those bounds for a user.
_implied_bounds()133 fn _implied_bounds() {
134     fn prox<T: IntoProxyScheme>(_t: T) {}
135 
136     fn url<T: IntoUrl>(t: T) {
137         prox(t);
138     }
139 }
140 
141 impl IntoProxyScheme for ProxyScheme {
into_proxy_scheme(self) -> crate::Result<ProxyScheme>142     fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> {
143         Ok(self)
144     }
145 }
146 
147 impl Proxy {
148     /// Proxy all HTTP traffic to the passed URL.
149     ///
150     /// # Example
151     ///
152     /// ```
153     /// # extern crate reqwest;
154     /// # fn run() -> Result<(), Box<std::error::Error>> {
155     /// let client = reqwest::Client::builder()
156     ///     .proxy(reqwest::Proxy::http("https://my.prox")?)
157     ///     .build()?;
158     /// # Ok(())
159     /// # }
160     /// # fn main() {}
161     /// ```
http<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy>162     pub fn http<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
163         Ok(Proxy::new(Intercept::Http(
164             proxy_scheme.into_proxy_scheme()?,
165         )))
166     }
167 
168     /// Proxy all HTTPS traffic to the passed URL.
169     ///
170     /// # Example
171     ///
172     /// ```
173     /// # extern crate reqwest;
174     /// # fn run() -> Result<(), Box<std::error::Error>> {
175     /// let client = reqwest::Client::builder()
176     ///     .proxy(reqwest::Proxy::https("https://example.prox:4545")?)
177     ///     .build()?;
178     /// # Ok(())
179     /// # }
180     /// # fn main() {}
181     /// ```
https<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy>182     pub fn https<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
183         Ok(Proxy::new(Intercept::Https(
184             proxy_scheme.into_proxy_scheme()?,
185         )))
186     }
187 
188     /// Proxy **all** traffic to the passed URL.
189     ///
190     /// # Example
191     ///
192     /// ```
193     /// # extern crate reqwest;
194     /// # fn run() -> Result<(), Box<std::error::Error>> {
195     /// let client = reqwest::Client::builder()
196     ///     .proxy(reqwest::Proxy::all("http://pro.xy")?)
197     ///     .build()?;
198     /// # Ok(())
199     /// # }
200     /// # fn main() {}
201     /// ```
all<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy>202     pub fn all<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
203         Ok(Proxy::new(Intercept::All(
204             proxy_scheme.into_proxy_scheme()?,
205         )))
206     }
207 
208     /// Provide a custom function to determine what traffix to proxy to where.
209     ///
210     /// # Example
211     ///
212     /// ```
213     /// # extern crate reqwest;
214     /// # fn run() -> Result<(), Box<std::error::Error>> {
215     /// let target = reqwest::Url::parse("https://my.prox")?;
216     /// let client = reqwest::Client::builder()
217     ///     .proxy(reqwest::Proxy::custom(move |url| {
218     ///         if url.host_str() == Some("hyper.rs") {
219     ///             Some(target.clone())
220     ///         } else {
221     ///             None
222     ///         }
223     ///     }))
224     ///     .build()?;
225     /// # Ok(())
226     /// # }
227     /// # fn main() {}
custom<F, U: IntoProxyScheme>(fun: F) -> Proxy where F: Fn(&Url) -> Option<U> + Send + Sync + 'static,228     pub fn custom<F, U: IntoProxyScheme>(fun: F) -> Proxy
229     where
230         F: Fn(&Url) -> Option<U> + Send + Sync + 'static,
231     {
232         Proxy::new(Intercept::Custom(Custom {
233             auth: None,
234             func: Arc::new(move |url| fun(url).map(IntoProxyScheme::into_proxy_scheme)),
235         }))
236     }
237 
system() -> Proxy238     pub(crate) fn system() -> Proxy {
239         let mut proxy = if cfg!(feature = "__internal_proxy_sys_no_cache") {
240             Proxy::new(Intercept::System(Arc::new(get_sys_proxies(
241                 get_from_registry(),
242             ))))
243         } else {
244             Proxy::new(Intercept::System(SYS_PROXIES.clone()))
245         };
246         proxy.no_proxy = NoProxy::new();
247         proxy
248     }
249 
new(intercept: Intercept) -> Proxy250     fn new(intercept: Intercept) -> Proxy {
251         Proxy {
252             intercept,
253             no_proxy: None,
254         }
255     }
256 
257     /// Set the `Proxy-Authorization` header using Basic auth.
258     ///
259     /// # Example
260     ///
261     /// ```
262     /// # extern crate reqwest;
263     /// # fn run() -> Result<(), Box<std::error::Error>> {
264     /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
265     ///     .basic_auth("Aladdin", "open sesame");
266     /// # Ok(())
267     /// # }
268     /// # fn main() {}
269     /// ```
basic_auth(mut self, username: &str, password: &str) -> Proxy270     pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy {
271         self.intercept.set_basic_auth(username, password);
272         self
273     }
274 
maybe_has_http_auth(&self) -> bool275     pub(crate) fn maybe_has_http_auth(&self) -> bool {
276         match self.intercept {
277             Intercept::All(ProxyScheme::Http { auth: Some(..), .. })
278             | Intercept::Http(ProxyScheme::Http { auth: Some(..), .. })
279             // Custom *may* match 'http', so assume so.
280             | Intercept::Custom(_) => true,
281             Intercept::System(ref system) => {
282                 if let Some(proxy) = system.get("http") {
283                     match proxy {
284                         ProxyScheme::Http { auth, .. } => auth.is_some(),
285                         _ => false,
286                     }
287                 } else {
288                     false
289                 }
290             }
291             _ => false,
292         }
293     }
294 
http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue>295     pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
296         match self.intercept {
297             Intercept::All(ProxyScheme::Http { ref auth, .. })
298             | Intercept::Http(ProxyScheme::Http { ref auth, .. }) => auth.clone(),
299             Intercept::System(ref system) => {
300                 if let Some(proxy) = system.get("http") {
301                     match proxy {
302                         ProxyScheme::Http { auth, .. } => auth.clone(),
303                         _ => None,
304                     }
305                 } else {
306                     None
307                 }
308             }
309             Intercept::Custom(ref custom) => custom.call(uri).and_then(|scheme| match scheme {
310                 ProxyScheme::Http { auth, .. } => auth,
311                 ProxyScheme::Https { auth, .. } => auth,
312                 #[cfg(feature = "socks")]
313                 _ => None,
314             }),
315             _ => None,
316         }
317     }
318 
intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme>319     pub(crate) fn intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
320         match self.intercept {
321             Intercept::All(ref u) => Some(u.clone()),
322             Intercept::Http(ref u) => {
323                 if uri.scheme() == "http" {
324                     Some(u.clone())
325                 } else {
326                     None
327                 }
328             }
329             Intercept::Https(ref u) => {
330                 if uri.scheme() == "https" {
331                     Some(u.clone())
332                 } else {
333                     None
334                 }
335             }
336             Intercept::System(ref map) => {
337                 let in_no_proxy = self
338                     .no_proxy
339                     .as_ref()
340                     .map_or(false, |np| np.contains(uri.host()));
341                 if in_no_proxy {
342                     None
343                 } else {
344                     map.get(uri.scheme()).cloned()
345                 }
346             }
347             Intercept::Custom(ref custom) => custom.call(uri),
348         }
349     }
350 
is_match<D: Dst>(&self, uri: &D) -> bool351     pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool {
352         match self.intercept {
353             Intercept::All(_) => true,
354             Intercept::Http(_) => uri.scheme() == "http",
355             Intercept::Https(_) => uri.scheme() == "https",
356             Intercept::System(ref map) => map.contains_key(uri.scheme()),
357             Intercept::Custom(ref custom) => custom.call(uri).is_some(),
358         }
359     }
360 }
361 
362 impl fmt::Debug for Proxy {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result363     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
364         f.debug_tuple("Proxy")
365             .field(&self.intercept)
366             .field(&self.no_proxy)
367             .finish()
368     }
369 }
370 
371 impl NoProxy {
372     /// Returns a new no proxy configration if the NO_PROXY/no_proxy environment variable is set.
373     /// Returns None otherwise
new() -> Option<Self>374     fn new() -> Option<Self> {
375         let raw = env::var("NO_PROXY")
376             .or_else(|_| env::var("no_proxy"))
377             .unwrap_or_default();
378         if raw.is_empty() {
379             return None;
380         }
381         let mut ips = Vec::new();
382         let mut domains = Vec::new();
383         let parts = raw.split(',');
384         for part in parts {
385             match part.parse::<IpNet>() {
386                 // If we can parse an IP net or address, then use it, otherwise, assume it is a domain
387                 Ok(ip) => ips.push(Ip::Network(ip)),
388                 Err(_) => match part.parse::<IpAddr>() {
389                     Ok(addr) => ips.push(Ip::Address(addr)),
390                     Err(_) => domains.push(part.to_owned()),
391                 },
392             }
393         }
394         Some(NoProxy {
395             ips: IpMatcher(ips),
396             domains: DomainMatcher(domains),
397         })
398     }
399 
contains(&self, host: &str) -> bool400     fn contains(&self, host: &str) -> bool {
401         // According to RFC3986, raw IPv6 hosts will be wrapped in []. So we need to strip those off
402         // the end in order to parse correctly
403         let host = if host.starts_with('[') {
404             let x: &[_] = &['[', ']'];
405             host.trim_matches(x)
406         } else {
407             host
408         };
409         match host.parse::<IpAddr>() {
410             // If we can parse an IP addr, then use it, otherwise, assume it is a domain
411             Ok(ip) => self.ips.contains(ip),
412             Err(_) => self.domains.contains(host),
413         }
414     }
415 }
416 
417 impl IpMatcher {
contains(&self, addr: IpAddr) -> bool418     fn contains(&self, addr: IpAddr) -> bool {
419         for ip in self.0.iter() {
420             match ip {
421                 Ip::Address(address) => {
422                     if &addr == address {
423                         return true;
424                     }
425                 }
426                 Ip::Network(net) => {
427                     if net.contains(&addr) {
428                         return true;
429                     }
430                 }
431             }
432         }
433         false
434     }
435 }
436 
437 impl DomainMatcher {
contains(&self, domain: &str) -> bool438     fn contains(&self, domain: &str) -> bool {
439         for d in self.0.iter() {
440             // First check for a "wildcard" domain match. A single "." will match anything.
441             // Otherwise, check that the domains are equal
442             if (d.starts_with('.') && domain.ends_with(d.get(1..).unwrap_or_default()))
443                 || d == domain
444             {
445                 return true;
446             }
447         }
448         false
449     }
450 }
451 
452 impl ProxyScheme {
453     // To start conservative, keep builders private for now.
454 
455     /// Proxy traffic via the specified URL over HTTP
http(host: &str) -> crate::Result<Self>456     fn http(host: &str) -> crate::Result<Self> {
457         Ok(ProxyScheme::Http {
458             auth: None,
459             host: host.parse().map_err(crate::error::builder)?,
460         })
461     }
462 
463     /// Proxy traffic via the specified URL over HTTPS
https(host: &str) -> crate::Result<Self>464     fn https(host: &str) -> crate::Result<Self> {
465         Ok(ProxyScheme::Https {
466             auth: None,
467             host: host.parse().map_err(crate::error::builder)?,
468         })
469     }
470 
471     /// Proxy traffic via the specified socket address over SOCKS5
472     ///
473     /// # Note
474     ///
475     /// Current SOCKS5 support is provided via blocking IO.
476     #[cfg(feature = "socks")]
socks5(addr: SocketAddr) -> crate::Result<Self>477     fn socks5(addr: SocketAddr) -> crate::Result<Self> {
478         Ok(ProxyScheme::Socks5 {
479             addr,
480             auth: None,
481             remote_dns: false,
482         })
483     }
484 
485     /// Proxy traffic via the specified socket address over SOCKS5H
486     ///
487     /// This differs from SOCKS5 in that DNS resolution is also performed via the proxy.
488     ///
489     /// # Note
490     ///
491     /// Current SOCKS5 support is provided via blocking IO.
492     #[cfg(feature = "socks")]
socks5h(addr: SocketAddr) -> crate::Result<Self>493     fn socks5h(addr: SocketAddr) -> crate::Result<Self> {
494         Ok(ProxyScheme::Socks5 {
495             addr,
496             auth: None,
497             remote_dns: true,
498         })
499     }
500 
501     /// Use a username and password when connecting to the proxy server
with_basic_auth<T: Into<String>, U: Into<String>>( mut self, username: T, password: U, ) -> Self502     fn with_basic_auth<T: Into<String>, U: Into<String>>(
503         mut self,
504         username: T,
505         password: U,
506     ) -> Self {
507         self.set_basic_auth(username, password);
508         self
509     }
510 
set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U)511     fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
512         match *self {
513             ProxyScheme::Http { ref mut auth, .. } => {
514                 let header = encode_basic_auth(&username.into(), &password.into());
515                 *auth = Some(header);
516             }
517             ProxyScheme::Https { ref mut auth, .. } => {
518                 let header = encode_basic_auth(&username.into(), &password.into());
519                 *auth = Some(header);
520             }
521             #[cfg(feature = "socks")]
522             ProxyScheme::Socks5 { ref mut auth, .. } => {
523                 *auth = Some((username.into(), password.into()));
524             }
525         }
526     }
527 
if_no_auth(mut self, update: &Option<HeaderValue>) -> Self528     fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
529         match self {
530             ProxyScheme::Http { ref mut auth, .. } => {
531                 if auth.is_none() {
532                     *auth = update.clone();
533                 }
534             }
535             ProxyScheme::Https { ref mut auth, .. } => {
536                 if auth.is_none() {
537                     *auth = update.clone();
538                 }
539             }
540             #[cfg(feature = "socks")]
541             ProxyScheme::Socks5 { .. } => {}
542         }
543 
544         self
545     }
546 
547     /// Convert a URL into a proxy scheme
548     ///
549     /// Supported schemes: HTTP, HTTPS, (SOCKS5, SOCKS5H if `socks` feature is enabled).
550     // Private for now...
parse(url: Url) -> crate::Result<Self>551     fn parse(url: Url) -> crate::Result<Self> {
552         use url::Position;
553 
554         // Resolve URL to a host and port
555         #[cfg(feature = "socks")]
556         let to_addr = || {
557             let addrs = url
558                 .socket_addrs(|| match url.scheme() {
559                     "socks5" | "socks5h" => Some(1080),
560                     _ => None,
561                 })
562                 .map_err(crate::error::builder)?;
563             addrs
564                 .into_iter()
565                 .next()
566                 .ok_or_else(|| crate::error::builder("unknown proxy scheme"))
567         };
568 
569         let mut scheme = match url.scheme() {
570             "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
571             "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
572             #[cfg(feature = "socks")]
573             "socks5" => Self::socks5(to_addr()?)?,
574             #[cfg(feature = "socks")]
575             "socks5h" => Self::socks5h(to_addr()?)?,
576             _ => return Err(crate::error::builder("unknown proxy scheme")),
577         };
578 
579         if let Some(pwd) = url.password() {
580             let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
581             let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
582             scheme = scheme.with_basic_auth(decoded_username, decoded_password);
583         }
584 
585         Ok(scheme)
586     }
587 
588     #[cfg(test)]
scheme(&self) -> &str589     fn scheme(&self) -> &str {
590         match self {
591             ProxyScheme::Http { .. } => "http",
592             ProxyScheme::Https { .. } => "https",
593             #[cfg(feature = "socks")]
594             ProxyScheme::Socks5 { .. } => "socks5",
595         }
596     }
597 
598     #[cfg(test)]
host(&self) -> &str599     fn host(&self) -> &str {
600         match self {
601             ProxyScheme::Http { host, .. } => host.as_str(),
602             ProxyScheme::Https { host, .. } => host.as_str(),
603             #[cfg(feature = "socks")]
604             ProxyScheme::Socks5 { .. } => panic!("socks5"),
605         }
606     }
607 }
608 
609 impl fmt::Debug for ProxyScheme {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result610     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
611         match self {
612             ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{}", host),
613             ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{}", host),
614             #[cfg(feature = "socks")]
615             ProxyScheme::Socks5 {
616                 addr,
617                 auth: _auth,
618                 remote_dns,
619             } => {
620                 let h = if *remote_dns { "h" } else { "" };
621                 write!(f, "socks5{}://{}", h, addr)
622             }
623         }
624     }
625 }
626 
627 type SystemProxyMap = HashMap<String, ProxyScheme>;
628 type RegistryProxyValues = (u32, String);
629 
630 #[derive(Clone, Debug)]
631 enum Intercept {
632     All(ProxyScheme),
633     Http(ProxyScheme),
634     Https(ProxyScheme),
635     System(Arc<SystemProxyMap>),
636     Custom(Custom),
637 }
638 
639 impl Intercept {
set_basic_auth(&mut self, username: &str, password: &str)640     fn set_basic_auth(&mut self, username: &str, password: &str) {
641         match self {
642             Intercept::All(ref mut s)
643             | Intercept::Http(ref mut s)
644             | Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
645             Intercept::System(_) => unimplemented!(),
646             Intercept::Custom(ref mut custom) => {
647                 let header = encode_basic_auth(username, password);
648                 custom.auth = Some(header);
649             }
650         }
651     }
652 }
653 
654 #[derive(Clone)]
655 struct Custom {
656     // This auth only applies if the returned ProxyScheme doesn't have an auth...
657     auth: Option<HeaderValue>,
658     func: Arc<dyn Fn(&Url) -> Option<crate::Result<ProxyScheme>> + Send + Sync + 'static>,
659 }
660 
661 impl Custom {
call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme>662     fn call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
663         let url = format!(
664             "{}://{}{}{}",
665             uri.scheme(),
666             uri.host(),
667             uri.port().map(|_| ":").unwrap_or(""),
668             uri.port().map(|p| p.to_string()).unwrap_or_default()
669         )
670         .parse()
671         .expect("should be valid Url");
672 
673         (self.func)(&url)
674             .and_then(|result| result.ok())
675             .map(|scheme| scheme.if_no_auth(&self.auth))
676     }
677 }
678 
679 impl fmt::Debug for Custom {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result680     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
681         f.write_str("_")
682     }
683 }
684 
encode_basic_auth(username: &str, password: &str) -> HeaderValue685 pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
686     let val = format!("{}:{}", username, password);
687     let mut header = format!("Basic {}", base64::encode(&val))
688         .parse::<HeaderValue>()
689         .expect("base64 is always valid HeaderValue");
690     header.set_sensitive(true);
691     header
692 }
693 
694 /// A helper trait to allow testing `Proxy::intercept` without having to
695 /// construct `hyper::client::connect::Destination`s.
696 pub(crate) trait Dst {
scheme(&self) -> &str697     fn scheme(&self) -> &str;
host(&self) -> &str698     fn host(&self) -> &str;
port(&self) -> Option<u16>699     fn port(&self) -> Option<u16>;
700 }
701 
702 #[doc(hidden)]
703 impl Dst for Uri {
scheme(&self) -> &str704     fn scheme(&self) -> &str {
705         self.scheme().expect("Uri should have a scheme").as_str()
706     }
707 
host(&self) -> &str708     fn host(&self) -> &str {
709         Uri::host(self).expect("<Uri as Dst>::host should have a str")
710     }
711 
port(&self) -> Option<u16>712     fn port(&self) -> Option<u16> {
713         self.port().map(|p| p.as_u16())
714     }
715 }
716 
717 lazy_static! {
718     static ref SYS_PROXIES: Arc<SystemProxyMap> = Arc::new(get_sys_proxies(get_from_registry()));
719 }
720 
721 /// Get system proxies information.
722 ///
723 /// It can only support Linux, Unix like, and windows system.  Note that it will always
724 /// return a HashMap, even if something runs into error when find registry information in
725 /// Windows system.  Note that invalid proxy url in the system setting will be ignored.
726 ///
727 /// Returns:
728 ///     System proxies information as a hashmap like
729 ///     {"http": Url::parse("http://127.0.0.1:80"), "https": Url::parse("https://127.0.0.1:80")}
get_sys_proxies( #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] registry_values: Option< RegistryProxyValues, >, ) -> SystemProxyMap730 fn get_sys_proxies(
731     #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] registry_values: Option<
732         RegistryProxyValues,
733     >,
734 ) -> SystemProxyMap {
735     let proxies = get_from_environment();
736 
737     // TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed
738     #[cfg(target_os = "windows")]
739     {
740         if proxies.is_empty() {
741             // don't care errors if can't get proxies from registry, just return an empty HashMap.
742             if let Some(registry_values) = registry_values {
743                 return parse_registry_values(registry_values);
744             }
745         }
746     }
747     proxies
748 }
749 
insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool750 fn insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool {
751     if let Ok(valid_addr) = addr.into_proxy_scheme() {
752         proxies.insert(scheme.into(), valid_addr);
753         true
754     } else {
755         false
756     }
757 }
758 
get_from_environment() -> SystemProxyMap759 fn get_from_environment() -> SystemProxyMap {
760     let mut proxies = HashMap::new();
761 
762     if is_cgi() {
763         if log::log_enabled!(log::Level::Warn) && env::var_os("HTTP_PROXY").is_some() {
764             log::warn!("HTTP_PROXY environment variable ignored in CGI");
765         }
766     } else if !insert_from_env(&mut proxies, "http", "HTTP_PROXY") {
767         insert_from_env(&mut proxies, "http", "http_proxy");
768     }
769 
770     if !insert_from_env(&mut proxies, "https", "HTTPS_PROXY") {
771         insert_from_env(&mut proxies, "https", "https_proxy");
772     }
773 
774     proxies
775 }
776 
insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool777 fn insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool {
778     if let Ok(val) = env::var(var) {
779         insert_proxy(proxies, scheme, val)
780     } else {
781         false
782     }
783 }
784 
785 /// Check if we are being executed in a CGI context.
786 ///
787 /// If so, a malicious client can send the `Proxy:` header, and it will
788 /// be in the `HTTP_PROXY` env var. So we don't use it :)
is_cgi() -> bool789 fn is_cgi() -> bool {
790     env::var_os("REQUEST_METHOD").is_some()
791 }
792 
793 #[cfg(target_os = "windows")]
get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>>794 fn get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>> {
795     let hkcu = RegKey::predef(HKEY_CURRENT_USER);
796     let internet_setting: RegKey =
797         hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
798     // ensure the proxy is enable, if the value doesn't exist, an error will returned.
799     let proxy_enable: u32 = internet_setting.get_value("ProxyEnable")?;
800     let proxy_server: String = internet_setting.get_value("ProxyServer")?;
801 
802     Ok((proxy_enable, proxy_server))
803 }
804 
805 #[cfg(target_os = "windows")]
get_from_registry() -> Option<RegistryProxyValues>806 fn get_from_registry() -> Option<RegistryProxyValues> {
807     get_from_registry_impl().ok()
808 }
809 
810 #[cfg(not(target_os = "windows"))]
get_from_registry() -> Option<RegistryProxyValues>811 fn get_from_registry() -> Option<RegistryProxyValues> {
812     None
813 }
814 
815 #[cfg(target_os = "windows")]
parse_registry_values_impl( registry_values: RegistryProxyValues, ) -> Result<SystemProxyMap, Box<dyn Error>>816 fn parse_registry_values_impl(
817     registry_values: RegistryProxyValues,
818 ) -> Result<SystemProxyMap, Box<dyn Error>> {
819     let (proxy_enable, proxy_server) = registry_values;
820 
821     if proxy_enable == 0 {
822         return Ok(HashMap::new());
823     }
824 
825     let mut proxies = HashMap::new();
826     if proxy_server.contains("=") {
827         // per-protocol settings.
828         for p in proxy_server.split(";") {
829             let protocol_parts: Vec<&str> = p.split("=").collect();
830             match protocol_parts.as_slice() {
831                 [protocol, address] => {
832                     // If address doesn't specify an explicit protocol as protocol://address
833                     // then default to HTTP
834                     let address = if extract_type_prefix(*address).is_some() {
835                         String::from(*address)
836                     } else {
837                         format!("http://{}", address)
838                     };
839 
840                     insert_proxy(&mut proxies, *protocol, address);
841                 }
842                 _ => {
843                     // Contains invalid protocol setting, just break the loop
844                     // And make proxies to be empty.
845                     proxies.clear();
846                     break;
847                 }
848             }
849         }
850     } else {
851         if let Some(scheme) = extract_type_prefix(&proxy_server) {
852             // Explicit protocol has been specified
853             insert_proxy(&mut proxies, scheme, proxy_server.to_owned());
854         } else {
855             // No explicit protocol has been specified, default to HTTP
856             insert_proxy(&mut proxies, "http", format!("http://{}", proxy_server));
857             insert_proxy(&mut proxies, "https", format!("http://{}", proxy_server));
858         }
859     }
860     Ok(proxies)
861 }
862 
863 /// Extract the protocol from the given address, if present
864 /// For example, "https://example.com" will return Some("https")
865 #[cfg(target_os = "windows")]
extract_type_prefix(address: &str) -> Option<&str>866 fn extract_type_prefix(address: &str) -> Option<&str> {
867     if let Some(indice) = address.find("://") {
868         if indice == 0 {
869             None
870         } else {
871             let prefix = &address[..indice];
872             let contains_banned = prefix.contains(|c| c == ':' || c == '/');
873 
874             if !contains_banned {
875                 Some(prefix)
876             } else {
877                 None
878             }
879         }
880     } else {
881         None
882     }
883 }
884 
885 #[cfg(target_os = "windows")]
parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap886 fn parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap {
887     parse_registry_values_impl(registry_values).unwrap_or(HashMap::new())
888 }
889 
890 #[cfg(test)]
891 mod tests {
892     use super::*;
893     use lazy_static::lazy_static;
894     use std::sync::Mutex;
895 
896     impl Dst for Url {
scheme(&self) -> &str897         fn scheme(&self) -> &str {
898             Url::scheme(self)
899         }
900 
host(&self) -> &str901         fn host(&self) -> &str {
902             Url::host_str(self).expect("<Url as Dst>::host should have a str")
903         }
904 
port(&self) -> Option<u16>905         fn port(&self) -> Option<u16> {
906             Url::port(self)
907         }
908     }
909 
url(s: &str) -> Url910     fn url(s: &str) -> Url {
911         s.parse().unwrap()
912     }
913 
intercepted_uri(p: &Proxy, s: &str) -> Uri914     fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
915         let (scheme, host) = match p.intercept(&url(s)).unwrap() {
916             ProxyScheme::Http { host, .. } => ("http", host),
917             ProxyScheme::Https { host, .. } => ("https", host),
918             #[cfg(feature = "socks")]
919             _ => panic!("intercepted as socks"),
920         };
921         http::Uri::builder()
922             .scheme(scheme)
923             .authority(host)
924             .path_and_query("/")
925             .build()
926             .expect("intercepted_uri")
927     }
928 
929     #[test]
test_http()930     fn test_http() {
931         let target = "http://example.domain/";
932         let p = Proxy::http(target).unwrap();
933 
934         let http = "http://hyper.rs";
935         let other = "https://hyper.rs";
936 
937         assert_eq!(intercepted_uri(&p, http), target);
938         assert!(p.intercept(&url(other)).is_none());
939     }
940 
941     #[test]
test_https()942     fn test_https() {
943         let target = "http://example.domain/";
944         let p = Proxy::https(target).unwrap();
945 
946         let http = "http://hyper.rs";
947         let other = "https://hyper.rs";
948 
949         assert!(p.intercept(&url(http)).is_none());
950         assert_eq!(intercepted_uri(&p, other), target);
951     }
952 
953     #[test]
test_all()954     fn test_all() {
955         let target = "http://example.domain/";
956         let p = Proxy::all(target).unwrap();
957 
958         let http = "http://hyper.rs";
959         let https = "https://hyper.rs";
960         let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
961 
962         assert_eq!(intercepted_uri(&p, http), target);
963         assert_eq!(intercepted_uri(&p, https), target);
964         assert_eq!(intercepted_uri(&p, other), target);
965     }
966 
967     #[test]
test_custom()968     fn test_custom() {
969         let target1 = "http://example.domain/";
970         let target2 = "https://example.domain/";
971         let p = Proxy::custom(move |url| {
972             if url.host_str() == Some("hyper.rs") {
973                 target1.parse().ok()
974             } else if url.scheme() == "http" {
975                 target2.parse().ok()
976             } else {
977                 None::<Url>
978             }
979         });
980 
981         let http = "http://seanmonstar.com";
982         let https = "https://hyper.rs";
983         let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
984 
985         assert_eq!(intercepted_uri(&p, http), target2);
986         assert_eq!(intercepted_uri(&p, https), target1);
987         assert!(p.intercept(&url(other)).is_none());
988     }
989 
990     #[test]
test_proxy_scheme_parse()991     fn test_proxy_scheme_parse() {
992         let ps = "http://foo:bar@localhost:1239".into_proxy_scheme().unwrap();
993 
994         match ps {
995             ProxyScheme::Http { auth, host } => {
996                 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
997                 assert_eq!(host, "localhost:1239");
998             }
999             other => panic!("unexpected: {:?}", other),
1000         }
1001     }
1002 
1003     #[test]
test_proxy_scheme_ip_address_default_http()1004     fn test_proxy_scheme_ip_address_default_http() {
1005         let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap();
1006 
1007         match ps {
1008             ProxyScheme::Http { auth, host } => {
1009                 assert!(auth.is_none());
1010                 assert_eq!(host, "192.168.1.1:8888");
1011             }
1012             other => panic!("unexpected: {:?}", other),
1013         }
1014     }
1015 
1016     #[test]
test_proxy_scheme_parse_default_http_with_auth()1017     fn test_proxy_scheme_parse_default_http_with_auth() {
1018         // this should fail because `foo` is interpreted as the scheme and no host can be found
1019         let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1020 
1021         match ps {
1022             ProxyScheme::Http { auth, host } => {
1023                 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1024                 assert_eq!(host, "localhost:1239");
1025             }
1026             other => panic!("unexpected: {:?}", other),
1027         }
1028     }
1029 
1030     // Smallest possible content for a mutex
1031     struct MutexInner;
1032 
1033     lazy_static! {
1034         static ref ENVLOCK: Mutex<MutexInner> = Mutex::new(MutexInner);
1035     }
1036 
1037     #[test]
test_get_sys_proxies_parsing()1038     fn test_get_sys_proxies_parsing() {
1039         // Stop other threads from modifying process-global ENV while we are.
1040         let _lock = ENVLOCK.lock();
1041         // save system setting first.
1042         let _g1 = env_guard("HTTP_PROXY");
1043         let _g2 = env_guard("http_proxy");
1044 
1045         // Mock ENV, get the results, before doing assertions
1046         // to avoid assert! -> panic! -> Mutex Poisoned.
1047         let baseline_proxies = get_sys_proxies(None);
1048         // the system proxy setting url is invalid.
1049         env::set_var("http_proxy", "file://123465");
1050         let invalid_proxies = get_sys_proxies(None);
1051         // set valid proxy
1052         env::set_var("http_proxy", "127.0.0.1/");
1053         let valid_proxies = get_sys_proxies(None);
1054 
1055         // reset user setting when guards drop
1056         drop(_g1);
1057         drop(_g2);
1058         // Let other threads run now
1059         drop(_lock);
1060 
1061         assert_eq!(baseline_proxies.contains_key("http"), false);
1062         assert_eq!(invalid_proxies.contains_key("http"), false);
1063 
1064         let p = &valid_proxies["http"];
1065         assert_eq!(p.scheme(), "http");
1066         assert_eq!(p.host(), "127.0.0.1");
1067     }
1068 
1069     #[cfg(target_os = "windows")]
1070     #[test]
test_get_sys_proxies_registry_parsing()1071     fn test_get_sys_proxies_registry_parsing() {
1072         // Stop other threads from modifying process-global ENV while we are.
1073         let _lock = ENVLOCK.lock();
1074         // save system setting first.
1075         let _g1 = env_guard("HTTP_PROXY");
1076         let _g2 = env_guard("http_proxy");
1077 
1078         // Mock ENV, get the results, before doing assertions
1079         // to avoid assert! -> panic! -> Mutex Poisoned.
1080         let baseline_proxies = get_sys_proxies(None);
1081         // the system proxy in the registry has been disabled
1082         let disabled_proxies = get_sys_proxies(Some((0, String::from("http://127.0.0.1/"))));
1083         // set valid proxy
1084         let valid_proxies = get_sys_proxies(Some((1, String::from("http://127.0.0.1/"))));
1085         let valid_proxies_no_schema = get_sys_proxies(Some((1, String::from("127.0.0.1"))));
1086         let valid_proxies_explicit_https =
1087             get_sys_proxies(Some((1, String::from("https://127.0.0.1/"))));
1088         let multiple_proxies = get_sys_proxies(Some((
1089             1,
1090             String::from("http=127.0.0.1:8888;https=127.0.0.2:8888"),
1091         )));
1092         let multiple_proxies_explicit_schema = get_sys_proxies(Some((
1093             1,
1094             String::from("http=http://127.0.0.1:8888;https=https://127.0.0.2:8888"),
1095         )));
1096 
1097         // reset user setting when guards drop
1098         drop(_g1);
1099         drop(_g2);
1100         // Let other threads run now
1101         drop(_lock);
1102 
1103         assert_eq!(baseline_proxies.contains_key("http"), false);
1104         assert_eq!(disabled_proxies.contains_key("http"), false);
1105 
1106         let p = &valid_proxies["http"];
1107         assert_eq!(p.scheme(), "http");
1108         assert_eq!(p.host(), "127.0.0.1");
1109 
1110         let p = &valid_proxies_no_schema["http"];
1111         assert_eq!(p.scheme(), "http");
1112         assert_eq!(p.host(), "127.0.0.1");
1113 
1114         let p = &valid_proxies_no_schema["https"];
1115         assert_eq!(p.scheme(), "http");
1116         assert_eq!(p.host(), "127.0.0.1");
1117 
1118         let p = &valid_proxies_explicit_https["https"];
1119         assert_eq!(p.scheme(), "https");
1120         assert_eq!(p.host(), "127.0.0.1");
1121 
1122         let p = &multiple_proxies["http"];
1123         assert_eq!(p.scheme(), "http");
1124         assert_eq!(p.host(), "127.0.0.1:8888");
1125 
1126         let p = &multiple_proxies["https"];
1127         assert_eq!(p.scheme(), "http");
1128         assert_eq!(p.host(), "127.0.0.2:8888");
1129 
1130         let p = &multiple_proxies_explicit_schema["http"];
1131         assert_eq!(p.scheme(), "http");
1132         assert_eq!(p.host(), "127.0.0.1:8888");
1133 
1134         let p = &multiple_proxies_explicit_schema["https"];
1135         assert_eq!(p.scheme(), "https");
1136         assert_eq!(p.host(), "127.0.0.2:8888");
1137     }
1138 
1139     #[test]
test_get_sys_proxies_in_cgi()1140     fn test_get_sys_proxies_in_cgi() {
1141         // Stop other threads from modifying process-global ENV while we are.
1142         let _lock = ENVLOCK.lock();
1143         // save system setting first.
1144         let _g1 = env_guard("REQUEST_METHOD");
1145         let _g2 = env_guard("HTTP_PROXY");
1146 
1147         // Mock ENV, get the results, before doing assertions
1148         // to avoid assert! -> panic! -> Mutex Poisoned.
1149         env::set_var("HTTP_PROXY", "http://evil/");
1150 
1151         let baseline_proxies = get_sys_proxies(None);
1152         // set like we're in CGI
1153         env::set_var("REQUEST_METHOD", "GET");
1154 
1155         let cgi_proxies = get_sys_proxies(None);
1156 
1157         // reset user setting when guards drop
1158         drop(_g1);
1159         drop(_g2);
1160         // Let other threads run now
1161         drop(_lock);
1162 
1163         // not in CGI yet
1164         assert_eq!(baseline_proxies["http"].host(), "evil");
1165         // In CGI
1166         assert!(!cgi_proxies.contains_key("http"));
1167     }
1168 
1169     #[test]
test_sys_no_proxy()1170     fn test_sys_no_proxy() {
1171         // Stop other threads from modifying process-global ENV while we are.
1172         let _lock = ENVLOCK.lock();
1173         // save system setting first.
1174         let _g1 = env_guard("HTTP_PROXY");
1175         let _g2 = env_guard("NO_PROXY");
1176 
1177         let target = "http://example.domain/";
1178         env::set_var("HTTP_PROXY", target);
1179 
1180         env::set_var(
1181             "NO_PROXY",
1182             ".foo.bar,bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17",
1183         );
1184 
1185         // Manually construct this so we aren't use the cache
1186         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1187         p.no_proxy = NoProxy::new();
1188 
1189         assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1190         assert_eq!(intercepted_uri(&p, "http://foo.bar.baz"), target);
1191         assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target);
1192         assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target);
1193         assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target);
1194         assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target);
1195 
1196         assert!(p.intercept(&url("http://hello.foo.bar")).is_none());
1197         assert!(p.intercept(&url("http://bar.baz")).is_none());
1198         assert!(p.intercept(&url("http://10.42.1.100")).is_none());
1199         assert!(p.intercept(&url("http://[::1]")).is_none());
1200         assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none());
1201         assert!(p.intercept(&url("http://10.124.7.8")).is_none());
1202 
1203         // reset user setting when guards drop
1204         drop(_g1);
1205         drop(_g2);
1206         // Let other threads run now
1207         drop(_lock);
1208     }
1209 
1210     #[test]
test_no_proxy_load()1211     fn test_no_proxy_load() {
1212         // Stop other threads from modifying process-global ENV while we are.
1213         let _lock = ENVLOCK.lock();
1214 
1215         let _g1 = env_guard("no_proxy");
1216         let domain = "lower.case";
1217         env::set_var("no_proxy", domain);
1218         // Manually construct this so we aren't use the cache
1219         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1220         p.no_proxy = NoProxy::new();
1221         assert_eq!(
1222             p.no_proxy.expect("should have a no proxy set").domains.0[0],
1223             domain
1224         );
1225 
1226         env::remove_var("no_proxy");
1227         let _g2 = env_guard("NO_PROXY");
1228         let domain = "upper.case";
1229         env::set_var("NO_PROXY", domain);
1230         // Manually construct this so we aren't use the cache
1231         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1232         p.no_proxy = NoProxy::new();
1233         assert_eq!(
1234             p.no_proxy.expect("should have a no proxy set").domains.0[0],
1235             domain
1236         );
1237 
1238         let _g3 = env_guard("HTTP_PROXY");
1239         env::remove_var("NO_PROXY");
1240         env::remove_var("no_proxy");
1241         let target = "http://example.domain/";
1242         env::set_var("HTTP_PROXY", target);
1243 
1244         // Manually construct this so we aren't use the cache
1245         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1246         p.no_proxy = NoProxy::new();
1247         assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
1248 
1249         assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1250 
1251         // reset user setting when guards drop
1252         drop(_g1);
1253         drop(_g2);
1254         drop(_g3);
1255         // Let other threads run now
1256         drop(_lock);
1257     }
1258 
1259     #[cfg(target_os = "windows")]
1260     #[test]
test_type_prefix_extraction()1261     fn test_type_prefix_extraction() {
1262         assert!(extract_type_prefix("test").is_none());
1263         assert!(extract_type_prefix("://test").is_none());
1264         assert!(extract_type_prefix("some:prefix://test").is_none());
1265         assert!(extract_type_prefix("some/prefix://test").is_none());
1266 
1267         assert_eq!(extract_type_prefix("http://test").unwrap(), "http");
1268         assert_eq!(extract_type_prefix("a://test").unwrap(), "a");
1269     }
1270 
1271     /// Guard an environment variable, resetting it to the original value
1272     /// when dropped.
env_guard(name: impl Into<String>) -> EnvGuard1273     fn env_guard(name: impl Into<String>) -> EnvGuard {
1274         let name = name.into();
1275         let orig_val = env::var(&name).ok();
1276         env::remove_var(&name);
1277         EnvGuard { name, orig_val }
1278     }
1279 
1280     struct EnvGuard {
1281         name: String,
1282         orig_val: Option<String>,
1283     }
1284 
1285     impl Drop for EnvGuard {
drop(&mut self)1286         fn drop(&mut self) {
1287             if let Some(val) = self.orig_val.take() {
1288                 env::set_var(&self.name, val);
1289             } else {
1290                 env::remove_var(&self.name);
1291             }
1292         }
1293     }
1294 
1295     #[test]
test_has_http_auth()1296     fn test_has_http_auth() {
1297         let http_proxy_with_auth = Proxy {
1298             intercept: Intercept::Http(ProxyScheme::Http {
1299                 auth: Some(HeaderValue::from_static("auth1")),
1300                 host: http::uri::Authority::from_static("authority"),
1301             }),
1302             no_proxy: None,
1303         };
1304         assert_eq!(http_proxy_with_auth.maybe_has_http_auth(), true);
1305         assert_eq!(
1306             http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1307             Some(HeaderValue::from_static("auth1"))
1308         );
1309 
1310         let http_proxy_without_auth = Proxy {
1311             intercept: Intercept::Http(ProxyScheme::Http {
1312                 auth: None,
1313                 host: http::uri::Authority::from_static("authority"),
1314             }),
1315             no_proxy: None,
1316         };
1317         assert_eq!(http_proxy_without_auth.maybe_has_http_auth(), false);
1318         assert_eq!(
1319             http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1320             None
1321         );
1322 
1323         let https_proxy_with_auth = Proxy {
1324             intercept: Intercept::Http(ProxyScheme::Https {
1325                 auth: Some(HeaderValue::from_static("auth2")),
1326                 host: http::uri::Authority::from_static("authority"),
1327             }),
1328             no_proxy: None,
1329         };
1330         assert_eq!(https_proxy_with_auth.maybe_has_http_auth(), false);
1331         assert_eq!(
1332             https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1333             None
1334         );
1335 
1336         let all_http_proxy_with_auth = Proxy {
1337             intercept: Intercept::All(ProxyScheme::Http {
1338                 auth: Some(HeaderValue::from_static("auth3")),
1339                 host: http::uri::Authority::from_static("authority"),
1340             }),
1341             no_proxy: None,
1342         };
1343         assert_eq!(all_http_proxy_with_auth.maybe_has_http_auth(), true);
1344         assert_eq!(
1345             all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1346             Some(HeaderValue::from_static("auth3"))
1347         );
1348 
1349         let all_https_proxy_with_auth = Proxy {
1350             intercept: Intercept::All(ProxyScheme::Https {
1351                 auth: Some(HeaderValue::from_static("auth4")),
1352                 host: http::uri::Authority::from_static("authority"),
1353             }),
1354             no_proxy: None,
1355         };
1356         assert_eq!(all_https_proxy_with_auth.maybe_has_http_auth(), false);
1357         assert_eq!(
1358             all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1359             None
1360         );
1361 
1362         let all_https_proxy_without_auth = Proxy {
1363             intercept: Intercept::All(ProxyScheme::Https {
1364                 auth: None,
1365                 host: http::uri::Authority::from_static("authority"),
1366             }),
1367             no_proxy: None,
1368         };
1369         assert_eq!(all_https_proxy_without_auth.maybe_has_http_auth(), false);
1370         assert_eq!(
1371             all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1372             None
1373         );
1374 
1375         let system_http_proxy_with_auth = Proxy {
1376             intercept: Intercept::System(Arc::new({
1377                 let mut m = HashMap::new();
1378                 m.insert(
1379                     "http".into(),
1380                     ProxyScheme::Http {
1381                         auth: Some(HeaderValue::from_static("auth5")),
1382                         host: http::uri::Authority::from_static("authority"),
1383                     },
1384                 );
1385                 m
1386             })),
1387             no_proxy: None,
1388         };
1389         assert_eq!(system_http_proxy_with_auth.maybe_has_http_auth(), true);
1390         assert_eq!(
1391             system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1392             Some(HeaderValue::from_static("auth5"))
1393         );
1394 
1395         let system_https_proxy_with_auth = Proxy {
1396             intercept: Intercept::System(Arc::new({
1397                 let mut m = HashMap::new();
1398                 m.insert(
1399                     "https".into(),
1400                     ProxyScheme::Https {
1401                         auth: Some(HeaderValue::from_static("auth6")),
1402                         host: http::uri::Authority::from_static("authority"),
1403                     },
1404                 );
1405                 m
1406             })),
1407             no_proxy: None,
1408         };
1409         assert_eq!(system_https_proxy_with_auth.maybe_has_http_auth(), false);
1410         assert_eq!(
1411             system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1412             None
1413         );
1414     }
1415 }
1416