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(ProxyScheme::Http { auth, .. }) = system.get("http") {
283                     auth.is_some()
284                 } else {
285                     false
286                 }
287             }
288             _ => false,
289         }
290     }
291 
http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue>292     pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
293         match self.intercept {
294             Intercept::All(ProxyScheme::Http { ref auth, .. })
295             | Intercept::Http(ProxyScheme::Http { ref auth, .. }) => auth.clone(),
296             Intercept::System(ref system) => {
297                 if let Some(proxy) = system.get("http") {
298                     match proxy {
299                         ProxyScheme::Http { auth, .. } => auth.clone(),
300                         _ => None,
301                     }
302                 } else {
303                     None
304                 }
305             }
306             Intercept::Custom(ref custom) => custom.call(uri).and_then(|scheme| match scheme {
307                 ProxyScheme::Http { auth, .. } => auth,
308                 ProxyScheme::Https { auth, .. } => auth,
309                 #[cfg(feature = "socks")]
310                 _ => None,
311             }),
312             _ => None,
313         }
314     }
315 
intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme>316     pub(crate) fn intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
317         match self.intercept {
318             Intercept::All(ref u) => Some(u.clone()),
319             Intercept::Http(ref u) => {
320                 if uri.scheme() == "http" {
321                     Some(u.clone())
322                 } else {
323                     None
324                 }
325             }
326             Intercept::Https(ref u) => {
327                 if uri.scheme() == "https" {
328                     Some(u.clone())
329                 } else {
330                     None
331                 }
332             }
333             Intercept::System(ref map) => {
334                 let in_no_proxy = self
335                     .no_proxy
336                     .as_ref()
337                     .map_or(false, |np| np.contains(uri.host()));
338                 if in_no_proxy {
339                     None
340                 } else {
341                     map.get(uri.scheme()).cloned()
342                 }
343             }
344             Intercept::Custom(ref custom) => custom.call(uri),
345         }
346     }
347 
is_match<D: Dst>(&self, uri: &D) -> bool348     pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool {
349         match self.intercept {
350             Intercept::All(_) => true,
351             Intercept::Http(_) => uri.scheme() == "http",
352             Intercept::Https(_) => uri.scheme() == "https",
353             Intercept::System(ref map) => map.contains_key(uri.scheme()),
354             Intercept::Custom(ref custom) => custom.call(uri).is_some(),
355         }
356     }
357 }
358 
359 impl fmt::Debug for Proxy {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result360     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
361         f.debug_tuple("Proxy")
362             .field(&self.intercept)
363             .field(&self.no_proxy)
364             .finish()
365     }
366 }
367 
368 impl NoProxy {
369     /// Returns a new no proxy configration if the NO_PROXY/no_proxy environment variable is set.
370     /// Returns None otherwise
new() -> Option<Self>371     fn new() -> Option<Self> {
372         let raw = env::var("NO_PROXY")
373             .or_else(|_| env::var("no_proxy"))
374             .unwrap_or_default();
375         if raw.is_empty() {
376             return None;
377         }
378         let mut ips = Vec::new();
379         let mut domains = Vec::new();
380         let parts = raw.split(',');
381         for part in parts {
382             match part.parse::<IpNet>() {
383                 // If we can parse an IP net or address, then use it, otherwise, assume it is a domain
384                 Ok(ip) => ips.push(Ip::Network(ip)),
385                 Err(_) => match part.parse::<IpAddr>() {
386                     Ok(addr) => ips.push(Ip::Address(addr)),
387                     Err(_) => domains.push(part.to_owned()),
388                 },
389             }
390         }
391         Some(NoProxy {
392             ips: IpMatcher(ips),
393             domains: DomainMatcher(domains),
394         })
395     }
396 
contains(&self, host: &str) -> bool397     fn contains(&self, host: &str) -> bool {
398         // According to RFC3986, raw IPv6 hosts will be wrapped in []. So we need to strip those off
399         // the end in order to parse correctly
400         let host = if host.starts_with('[') {
401             let x: &[_] = &['[', ']'];
402             host.trim_matches(x)
403         } else {
404             host
405         };
406         match host.parse::<IpAddr>() {
407             // If we can parse an IP addr, then use it, otherwise, assume it is a domain
408             Ok(ip) => self.ips.contains(ip),
409             Err(_) => self.domains.contains(host),
410         }
411     }
412 }
413 
414 impl IpMatcher {
contains(&self, addr: IpAddr) -> bool415     fn contains(&self, addr: IpAddr) -> bool {
416         for ip in self.0.iter() {
417             match ip {
418                 Ip::Address(address) => {
419                     if &addr == address {
420                         return true;
421                     }
422                 }
423                 Ip::Network(net) => {
424                     if net.contains(&addr) {
425                         return true;
426                     }
427                 }
428             }
429         }
430         false
431     }
432 }
433 
434 impl DomainMatcher {
contains(&self, domain: &str) -> bool435     fn contains(&self, domain: &str) -> bool {
436         for d in self.0.iter() {
437             // First check for a "wildcard" domain match. A single "." will match anything.
438             // Otherwise, check that the domains are equal
439             if (d.starts_with('.') && domain.ends_with(d.get(1..).unwrap_or_default()))
440                 || d == domain
441             {
442                 return true;
443             }
444         }
445         false
446     }
447 }
448 
449 impl ProxyScheme {
450     // To start conservative, keep builders private for now.
451 
452     /// Proxy traffic via the specified URL over HTTP
http(host: &str) -> crate::Result<Self>453     fn http(host: &str) -> crate::Result<Self> {
454         Ok(ProxyScheme::Http {
455             auth: None,
456             host: host.parse().map_err(crate::error::builder)?,
457         })
458     }
459 
460     /// Proxy traffic via the specified URL over HTTPS
https(host: &str) -> crate::Result<Self>461     fn https(host: &str) -> crate::Result<Self> {
462         Ok(ProxyScheme::Https {
463             auth: None,
464             host: host.parse().map_err(crate::error::builder)?,
465         })
466     }
467 
468     /// Proxy traffic via the specified socket address over SOCKS5
469     ///
470     /// # Note
471     ///
472     /// Current SOCKS5 support is provided via blocking IO.
473     #[cfg(feature = "socks")]
socks5(addr: SocketAddr) -> crate::Result<Self>474     fn socks5(addr: SocketAddr) -> crate::Result<Self> {
475         Ok(ProxyScheme::Socks5 {
476             addr,
477             auth: None,
478             remote_dns: false,
479         })
480     }
481 
482     /// Proxy traffic via the specified socket address over SOCKS5H
483     ///
484     /// This differs from SOCKS5 in that DNS resolution is also performed via the proxy.
485     ///
486     /// # Note
487     ///
488     /// Current SOCKS5 support is provided via blocking IO.
489     #[cfg(feature = "socks")]
socks5h(addr: SocketAddr) -> crate::Result<Self>490     fn socks5h(addr: SocketAddr) -> crate::Result<Self> {
491         Ok(ProxyScheme::Socks5 {
492             addr,
493             auth: None,
494             remote_dns: true,
495         })
496     }
497 
498     /// 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, ) -> Self499     fn with_basic_auth<T: Into<String>, U: Into<String>>(
500         mut self,
501         username: T,
502         password: U,
503     ) -> Self {
504         self.set_basic_auth(username, password);
505         self
506     }
507 
set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U)508     fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
509         match *self {
510             ProxyScheme::Http { ref mut auth, .. } => {
511                 let header = encode_basic_auth(&username.into(), &password.into());
512                 *auth = Some(header);
513             }
514             ProxyScheme::Https { ref mut auth, .. } => {
515                 let header = encode_basic_auth(&username.into(), &password.into());
516                 *auth = Some(header);
517             }
518             #[cfg(feature = "socks")]
519             ProxyScheme::Socks5 { ref mut auth, .. } => {
520                 *auth = Some((username.into(), password.into()));
521             }
522         }
523     }
524 
if_no_auth(mut self, update: &Option<HeaderValue>) -> Self525     fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
526         match self {
527             ProxyScheme::Http { ref mut auth, .. } => {
528                 if auth.is_none() {
529                     *auth = update.clone();
530                 }
531             }
532             ProxyScheme::Https { ref mut auth, .. } => {
533                 if auth.is_none() {
534                     *auth = update.clone();
535                 }
536             }
537             #[cfg(feature = "socks")]
538             ProxyScheme::Socks5 { .. } => {}
539         }
540 
541         self
542     }
543 
544     /// Convert a URL into a proxy scheme
545     ///
546     /// Supported schemes: HTTP, HTTPS, (SOCKS5, SOCKS5H if `socks` feature is enabled).
547     // Private for now...
parse(url: Url) -> crate::Result<Self>548     fn parse(url: Url) -> crate::Result<Self> {
549         use url::Position;
550 
551         // Resolve URL to a host and port
552         #[cfg(feature = "socks")]
553         let to_addr = || {
554             let addrs = url
555                 .socket_addrs(|| match url.scheme() {
556                     "socks5" | "socks5h" => Some(1080),
557                     _ => None,
558                 })
559                 .map_err(crate::error::builder)?;
560             addrs
561                 .into_iter()
562                 .next()
563                 .ok_or_else(|| crate::error::builder("unknown proxy scheme"))
564         };
565 
566         let mut scheme = match url.scheme() {
567             "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
568             "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
569             #[cfg(feature = "socks")]
570             "socks5" => Self::socks5(to_addr()?)?,
571             #[cfg(feature = "socks")]
572             "socks5h" => Self::socks5h(to_addr()?)?,
573             _ => return Err(crate::error::builder("unknown proxy scheme")),
574         };
575 
576         if let Some(pwd) = url.password() {
577             let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
578             let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
579             scheme = scheme.with_basic_auth(decoded_username, decoded_password);
580         }
581 
582         Ok(scheme)
583     }
584 
585     #[cfg(test)]
scheme(&self) -> &str586     fn scheme(&self) -> &str {
587         match self {
588             ProxyScheme::Http { .. } => "http",
589             ProxyScheme::Https { .. } => "https",
590             #[cfg(feature = "socks")]
591             ProxyScheme::Socks5 { .. } => "socks5",
592         }
593     }
594 
595     #[cfg(test)]
host(&self) -> &str596     fn host(&self) -> &str {
597         match self {
598             ProxyScheme::Http { host, .. } => host.as_str(),
599             ProxyScheme::Https { host, .. } => host.as_str(),
600             #[cfg(feature = "socks")]
601             ProxyScheme::Socks5 { .. } => panic!("socks5"),
602         }
603     }
604 }
605 
606 impl fmt::Debug for ProxyScheme {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result607     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
608         match self {
609             ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{}", host),
610             ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{}", host),
611             #[cfg(feature = "socks")]
612             ProxyScheme::Socks5 {
613                 addr,
614                 auth: _auth,
615                 remote_dns,
616             } => {
617                 let h = if *remote_dns { "h" } else { "" };
618                 write!(f, "socks5{}://{}", h, addr)
619             }
620         }
621     }
622 }
623 
624 type SystemProxyMap = HashMap<String, ProxyScheme>;
625 type RegistryProxyValues = (u32, String);
626 
627 #[derive(Clone, Debug)]
628 enum Intercept {
629     All(ProxyScheme),
630     Http(ProxyScheme),
631     Https(ProxyScheme),
632     System(Arc<SystemProxyMap>),
633     Custom(Custom),
634 }
635 
636 impl Intercept {
set_basic_auth(&mut self, username: &str, password: &str)637     fn set_basic_auth(&mut self, username: &str, password: &str) {
638         match self {
639             Intercept::All(ref mut s)
640             | Intercept::Http(ref mut s)
641             | Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
642             Intercept::System(_) => unimplemented!(),
643             Intercept::Custom(ref mut custom) => {
644                 let header = encode_basic_auth(username, password);
645                 custom.auth = Some(header);
646             }
647         }
648     }
649 }
650 
651 #[derive(Clone)]
652 struct Custom {
653     // This auth only applies if the returned ProxyScheme doesn't have an auth...
654     auth: Option<HeaderValue>,
655     func: Arc<dyn Fn(&Url) -> Option<crate::Result<ProxyScheme>> + Send + Sync + 'static>,
656 }
657 
658 impl Custom {
call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme>659     fn call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
660         let url = format!(
661             "{}://{}{}{}",
662             uri.scheme(),
663             uri.host(),
664             uri.port().map(|_| ":").unwrap_or(""),
665             uri.port().map(|p| p.to_string()).unwrap_or_default()
666         )
667         .parse()
668         .expect("should be valid Url");
669 
670         (self.func)(&url)
671             .and_then(|result| result.ok())
672             .map(|scheme| scheme.if_no_auth(&self.auth))
673     }
674 }
675 
676 impl fmt::Debug for Custom {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result677     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
678         f.write_str("_")
679     }
680 }
681 
encode_basic_auth(username: &str, password: &str) -> HeaderValue682 pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
683     let val = format!("{}:{}", username, password);
684     let mut header = format!("Basic {}", base64::encode(&val))
685         .parse::<HeaderValue>()
686         .expect("base64 is always valid HeaderValue");
687     header.set_sensitive(true);
688     header
689 }
690 
691 /// A helper trait to allow testing `Proxy::intercept` without having to
692 /// construct `hyper::client::connect::Destination`s.
693 pub(crate) trait Dst {
scheme(&self) -> &str694     fn scheme(&self) -> &str;
host(&self) -> &str695     fn host(&self) -> &str;
port(&self) -> Option<u16>696     fn port(&self) -> Option<u16>;
697 }
698 
699 #[doc(hidden)]
700 impl Dst for Uri {
scheme(&self) -> &str701     fn scheme(&self) -> &str {
702         self.scheme().expect("Uri should have a scheme").as_str()
703     }
704 
host(&self) -> &str705     fn host(&self) -> &str {
706         Uri::host(self).expect("<Uri as Dst>::host should have a str")
707     }
708 
port(&self) -> Option<u16>709     fn port(&self) -> Option<u16> {
710         self.port().map(|p| p.as_u16())
711     }
712 }
713 
714 lazy_static! {
715     static ref SYS_PROXIES: Arc<SystemProxyMap> = Arc::new(get_sys_proxies(get_from_registry()));
716 }
717 
718 /// Get system proxies information.
719 ///
720 /// It can only support Linux, Unix like, and windows system.  Note that it will always
721 /// return a HashMap, even if something runs into error when find registry information in
722 /// Windows system.  Note that invalid proxy url in the system setting will be ignored.
723 ///
724 /// Returns:
725 ///     System proxies information as a hashmap like
726 ///     {"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, >, ) -> SystemProxyMap727 fn get_sys_proxies(
728     #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] registry_values: Option<
729         RegistryProxyValues,
730     >,
731 ) -> SystemProxyMap {
732     let proxies = get_from_environment();
733 
734     // TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed
735     #[cfg(target_os = "windows")]
736     {
737         if proxies.is_empty() {
738             // don't care errors if can't get proxies from registry, just return an empty HashMap.
739             if let Some(registry_values) = registry_values {
740                 return parse_registry_values(registry_values);
741             }
742         }
743     }
744     proxies
745 }
746 
insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool747 fn insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool {
748     if let Ok(valid_addr) = addr.into_proxy_scheme() {
749         proxies.insert(scheme.into(), valid_addr);
750         true
751     } else {
752         false
753     }
754 }
755 
get_from_environment() -> SystemProxyMap756 fn get_from_environment() -> SystemProxyMap {
757     let mut proxies = HashMap::new();
758 
759     if is_cgi() {
760         if log::log_enabled!(log::Level::Warn) && env::var_os("HTTP_PROXY").is_some() {
761             log::warn!("HTTP_PROXY environment variable ignored in CGI");
762         }
763     } else if !insert_from_env(&mut proxies, "http", "HTTP_PROXY") {
764         insert_from_env(&mut proxies, "http", "http_proxy");
765     }
766 
767     if !insert_from_env(&mut proxies, "https", "HTTPS_PROXY") {
768         insert_from_env(&mut proxies, "https", "https_proxy");
769     }
770 
771     proxies
772 }
773 
insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool774 fn insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool {
775     if let Ok(val) = env::var(var) {
776         insert_proxy(proxies, scheme, val)
777     } else {
778         false
779     }
780 }
781 
782 /// Check if we are being executed in a CGI context.
783 ///
784 /// If so, a malicious client can send the `Proxy:` header, and it will
785 /// be in the `HTTP_PROXY` env var. So we don't use it :)
is_cgi() -> bool786 fn is_cgi() -> bool {
787     env::var_os("REQUEST_METHOD").is_some()
788 }
789 
790 #[cfg(target_os = "windows")]
get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>>791 fn get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>> {
792     let hkcu = RegKey::predef(HKEY_CURRENT_USER);
793     let internet_setting: RegKey =
794         hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
795     // ensure the proxy is enable, if the value doesn't exist, an error will returned.
796     let proxy_enable: u32 = internet_setting.get_value("ProxyEnable")?;
797     let proxy_server: String = internet_setting.get_value("ProxyServer")?;
798 
799     Ok((proxy_enable, proxy_server))
800 }
801 
802 #[cfg(target_os = "windows")]
get_from_registry() -> Option<RegistryProxyValues>803 fn get_from_registry() -> Option<RegistryProxyValues> {
804     get_from_registry_impl().ok()
805 }
806 
807 #[cfg(not(target_os = "windows"))]
get_from_registry() -> Option<RegistryProxyValues>808 fn get_from_registry() -> Option<RegistryProxyValues> {
809     None
810 }
811 
812 #[cfg(target_os = "windows")]
parse_registry_values_impl( registry_values: RegistryProxyValues, ) -> Result<SystemProxyMap, Box<dyn Error>>813 fn parse_registry_values_impl(
814     registry_values: RegistryProxyValues,
815 ) -> Result<SystemProxyMap, Box<dyn Error>> {
816     let (proxy_enable, proxy_server) = registry_values;
817 
818     if proxy_enable == 0 {
819         return Ok(HashMap::new());
820     }
821 
822     let mut proxies = HashMap::new();
823     if proxy_server.contains("=") {
824         // per-protocol settings.
825         for p in proxy_server.split(";") {
826             let protocol_parts: Vec<&str> = p.split("=").collect();
827             match protocol_parts.as_slice() {
828                 [protocol, address] => {
829                     // If address doesn't specify an explicit protocol as protocol://address
830                     // then default to HTTP
831                     let address = if extract_type_prefix(*address).is_some() {
832                         String::from(*address)
833                     } else {
834                         format!("http://{}", address)
835                     };
836 
837                     insert_proxy(&mut proxies, *protocol, address);
838                 }
839                 _ => {
840                     // Contains invalid protocol setting, just break the loop
841                     // And make proxies to be empty.
842                     proxies.clear();
843                     break;
844                 }
845             }
846         }
847     } else {
848         if let Some(scheme) = extract_type_prefix(&proxy_server) {
849             // Explicit protocol has been specified
850             insert_proxy(&mut proxies, scheme, proxy_server.to_owned());
851         } else {
852             // No explicit protocol has been specified, default to HTTP
853             insert_proxy(&mut proxies, "http", format!("http://{}", proxy_server));
854             insert_proxy(&mut proxies, "https", format!("http://{}", proxy_server));
855         }
856     }
857     Ok(proxies)
858 }
859 
860 /// Extract the protocol from the given address, if present
861 /// For example, "https://example.com" will return Some("https")
862 #[cfg(target_os = "windows")]
extract_type_prefix(address: &str) -> Option<&str>863 fn extract_type_prefix(address: &str) -> Option<&str> {
864     if let Some(indice) = address.find("://") {
865         if indice == 0 {
866             None
867         } else {
868             let prefix = &address[..indice];
869             let contains_banned = prefix.contains(|c| c == ':' || c == '/');
870 
871             if !contains_banned {
872                 Some(prefix)
873             } else {
874                 None
875             }
876         }
877     } else {
878         None
879     }
880 }
881 
882 #[cfg(target_os = "windows")]
parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap883 fn parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap {
884     parse_registry_values_impl(registry_values).unwrap_or(HashMap::new())
885 }
886 
887 #[cfg(test)]
888 mod tests {
889     use super::*;
890     use lazy_static::lazy_static;
891     use std::sync::Mutex;
892 
893     impl Dst for Url {
scheme(&self) -> &str894         fn scheme(&self) -> &str {
895             Url::scheme(self)
896         }
897 
host(&self) -> &str898         fn host(&self) -> &str {
899             Url::host_str(self).expect("<Url as Dst>::host should have a str")
900         }
901 
port(&self) -> Option<u16>902         fn port(&self) -> Option<u16> {
903             Url::port(self)
904         }
905     }
906 
url(s: &str) -> Url907     fn url(s: &str) -> Url {
908         s.parse().unwrap()
909     }
910 
intercepted_uri(p: &Proxy, s: &str) -> Uri911     fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
912         let (scheme, host) = match p.intercept(&url(s)).unwrap() {
913             ProxyScheme::Http { host, .. } => ("http", host),
914             ProxyScheme::Https { host, .. } => ("https", host),
915             #[cfg(feature = "socks")]
916             _ => panic!("intercepted as socks"),
917         };
918         http::Uri::builder()
919             .scheme(scheme)
920             .authority(host)
921             .path_and_query("/")
922             .build()
923             .expect("intercepted_uri")
924     }
925 
926     #[test]
test_http()927     fn test_http() {
928         let target = "http://example.domain/";
929         let p = Proxy::http(target).unwrap();
930 
931         let http = "http://hyper.rs";
932         let other = "https://hyper.rs";
933 
934         assert_eq!(intercepted_uri(&p, http), target);
935         assert!(p.intercept(&url(other)).is_none());
936     }
937 
938     #[test]
test_https()939     fn test_https() {
940         let target = "http://example.domain/";
941         let p = Proxy::https(target).unwrap();
942 
943         let http = "http://hyper.rs";
944         let other = "https://hyper.rs";
945 
946         assert!(p.intercept(&url(http)).is_none());
947         assert_eq!(intercepted_uri(&p, other), target);
948     }
949 
950     #[test]
test_all()951     fn test_all() {
952         let target = "http://example.domain/";
953         let p = Proxy::all(target).unwrap();
954 
955         let http = "http://hyper.rs";
956         let https = "https://hyper.rs";
957         let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
958 
959         assert_eq!(intercepted_uri(&p, http), target);
960         assert_eq!(intercepted_uri(&p, https), target);
961         assert_eq!(intercepted_uri(&p, other), target);
962     }
963 
964     #[test]
test_custom()965     fn test_custom() {
966         let target1 = "http://example.domain/";
967         let target2 = "https://example.domain/";
968         let p = Proxy::custom(move |url| {
969             if url.host_str() == Some("hyper.rs") {
970                 target1.parse().ok()
971             } else if url.scheme() == "http" {
972                 target2.parse().ok()
973             } else {
974                 None::<Url>
975             }
976         });
977 
978         let http = "http://seanmonstar.com";
979         let https = "https://hyper.rs";
980         let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
981 
982         assert_eq!(intercepted_uri(&p, http), target2);
983         assert_eq!(intercepted_uri(&p, https), target1);
984         assert!(p.intercept(&url(other)).is_none());
985     }
986 
987     #[test]
test_proxy_scheme_parse()988     fn test_proxy_scheme_parse() {
989         let ps = "http://foo:bar@localhost:1239".into_proxy_scheme().unwrap();
990 
991         match ps {
992             ProxyScheme::Http { auth, host } => {
993                 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
994                 assert_eq!(host, "localhost:1239");
995             }
996             other => panic!("unexpected: {:?}", other),
997         }
998     }
999 
1000     #[test]
test_proxy_scheme_ip_address_default_http()1001     fn test_proxy_scheme_ip_address_default_http() {
1002         let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap();
1003 
1004         match ps {
1005             ProxyScheme::Http { auth, host } => {
1006                 assert!(auth.is_none());
1007                 assert_eq!(host, "192.168.1.1:8888");
1008             }
1009             other => panic!("unexpected: {:?}", other),
1010         }
1011     }
1012 
1013     #[test]
test_proxy_scheme_parse_default_http_with_auth()1014     fn test_proxy_scheme_parse_default_http_with_auth() {
1015         // this should fail because `foo` is interpreted as the scheme and no host can be found
1016         let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1017 
1018         match ps {
1019             ProxyScheme::Http { auth, host } => {
1020                 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1021                 assert_eq!(host, "localhost:1239");
1022             }
1023             other => panic!("unexpected: {:?}", other),
1024         }
1025     }
1026 
1027     // Smallest possible content for a mutex
1028     struct MutexInner;
1029 
1030     lazy_static! {
1031         static ref ENVLOCK: Mutex<MutexInner> = Mutex::new(MutexInner);
1032     }
1033 
1034     #[test]
test_get_sys_proxies_parsing()1035     fn test_get_sys_proxies_parsing() {
1036         // Stop other threads from modifying process-global ENV while we are.
1037         let _lock = ENVLOCK.lock();
1038         // save system setting first.
1039         let _g1 = env_guard("HTTP_PROXY");
1040         let _g2 = env_guard("http_proxy");
1041 
1042         // Mock ENV, get the results, before doing assertions
1043         // to avoid assert! -> panic! -> Mutex Poisoned.
1044         let baseline_proxies = get_sys_proxies(None);
1045         // the system proxy setting url is invalid.
1046         env::set_var("http_proxy", "file://123465");
1047         let invalid_proxies = get_sys_proxies(None);
1048         // set valid proxy
1049         env::set_var("http_proxy", "127.0.0.1/");
1050         let valid_proxies = get_sys_proxies(None);
1051 
1052         // reset user setting when guards drop
1053         drop(_g1);
1054         drop(_g2);
1055         // Let other threads run now
1056         drop(_lock);
1057 
1058         assert_eq!(baseline_proxies.contains_key("http"), false);
1059         assert_eq!(invalid_proxies.contains_key("http"), false);
1060 
1061         let p = &valid_proxies["http"];
1062         assert_eq!(p.scheme(), "http");
1063         assert_eq!(p.host(), "127.0.0.1");
1064     }
1065 
1066     #[cfg(target_os = "windows")]
1067     #[test]
test_get_sys_proxies_registry_parsing()1068     fn test_get_sys_proxies_registry_parsing() {
1069         // Stop other threads from modifying process-global ENV while we are.
1070         let _lock = ENVLOCK.lock();
1071         // save system setting first.
1072         let _g1 = env_guard("HTTP_PROXY");
1073         let _g2 = env_guard("http_proxy");
1074 
1075         // Mock ENV, get the results, before doing assertions
1076         // to avoid assert! -> panic! -> Mutex Poisoned.
1077         let baseline_proxies = get_sys_proxies(None);
1078         // the system proxy in the registry has been disabled
1079         let disabled_proxies = get_sys_proxies(Some((0, String::from("http://127.0.0.1/"))));
1080         // set valid proxy
1081         let valid_proxies = get_sys_proxies(Some((1, String::from("http://127.0.0.1/"))));
1082         let valid_proxies_no_schema = get_sys_proxies(Some((1, String::from("127.0.0.1"))));
1083         let valid_proxies_explicit_https =
1084             get_sys_proxies(Some((1, String::from("https://127.0.0.1/"))));
1085         let multiple_proxies = get_sys_proxies(Some((
1086             1,
1087             String::from("http=127.0.0.1:8888;https=127.0.0.2:8888"),
1088         )));
1089         let multiple_proxies_explicit_schema = get_sys_proxies(Some((
1090             1,
1091             String::from("http=http://127.0.0.1:8888;https=https://127.0.0.2:8888"),
1092         )));
1093 
1094         // reset user setting when guards drop
1095         drop(_g1);
1096         drop(_g2);
1097         // Let other threads run now
1098         drop(_lock);
1099 
1100         assert_eq!(baseline_proxies.contains_key("http"), false);
1101         assert_eq!(disabled_proxies.contains_key("http"), false);
1102 
1103         let p = &valid_proxies["http"];
1104         assert_eq!(p.scheme(), "http");
1105         assert_eq!(p.host(), "127.0.0.1");
1106 
1107         let p = &valid_proxies_no_schema["http"];
1108         assert_eq!(p.scheme(), "http");
1109         assert_eq!(p.host(), "127.0.0.1");
1110 
1111         let p = &valid_proxies_no_schema["https"];
1112         assert_eq!(p.scheme(), "http");
1113         assert_eq!(p.host(), "127.0.0.1");
1114 
1115         let p = &valid_proxies_explicit_https["https"];
1116         assert_eq!(p.scheme(), "https");
1117         assert_eq!(p.host(), "127.0.0.1");
1118 
1119         let p = &multiple_proxies["http"];
1120         assert_eq!(p.scheme(), "http");
1121         assert_eq!(p.host(), "127.0.0.1:8888");
1122 
1123         let p = &multiple_proxies["https"];
1124         assert_eq!(p.scheme(), "http");
1125         assert_eq!(p.host(), "127.0.0.2:8888");
1126 
1127         let p = &multiple_proxies_explicit_schema["http"];
1128         assert_eq!(p.scheme(), "http");
1129         assert_eq!(p.host(), "127.0.0.1:8888");
1130 
1131         let p = &multiple_proxies_explicit_schema["https"];
1132         assert_eq!(p.scheme(), "https");
1133         assert_eq!(p.host(), "127.0.0.2:8888");
1134     }
1135 
1136     #[test]
test_get_sys_proxies_in_cgi()1137     fn test_get_sys_proxies_in_cgi() {
1138         // Stop other threads from modifying process-global ENV while we are.
1139         let _lock = ENVLOCK.lock();
1140         // save system setting first.
1141         let _g1 = env_guard("REQUEST_METHOD");
1142         let _g2 = env_guard("HTTP_PROXY");
1143 
1144         // Mock ENV, get the results, before doing assertions
1145         // to avoid assert! -> panic! -> Mutex Poisoned.
1146         env::set_var("HTTP_PROXY", "http://evil/");
1147 
1148         let baseline_proxies = get_sys_proxies(None);
1149         // set like we're in CGI
1150         env::set_var("REQUEST_METHOD", "GET");
1151 
1152         let cgi_proxies = get_sys_proxies(None);
1153 
1154         // reset user setting when guards drop
1155         drop(_g1);
1156         drop(_g2);
1157         // Let other threads run now
1158         drop(_lock);
1159 
1160         // not in CGI yet
1161         assert_eq!(baseline_proxies["http"].host(), "evil");
1162         // In CGI
1163         assert!(!cgi_proxies.contains_key("http"));
1164     }
1165 
1166     #[test]
test_sys_no_proxy()1167     fn test_sys_no_proxy() {
1168         // Stop other threads from modifying process-global ENV while we are.
1169         let _lock = ENVLOCK.lock();
1170         // save system setting first.
1171         let _g1 = env_guard("HTTP_PROXY");
1172         let _g2 = env_guard("NO_PROXY");
1173 
1174         let target = "http://example.domain/";
1175         env::set_var("HTTP_PROXY", target);
1176 
1177         env::set_var(
1178             "NO_PROXY",
1179             ".foo.bar,bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17",
1180         );
1181 
1182         // Manually construct this so we aren't use the cache
1183         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1184         p.no_proxy = NoProxy::new();
1185 
1186         assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1187         assert_eq!(intercepted_uri(&p, "http://foo.bar.baz"), target);
1188         assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target);
1189         assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target);
1190         assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target);
1191         assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target);
1192 
1193         assert!(p.intercept(&url("http://hello.foo.bar")).is_none());
1194         assert!(p.intercept(&url("http://bar.baz")).is_none());
1195         assert!(p.intercept(&url("http://10.42.1.100")).is_none());
1196         assert!(p.intercept(&url("http://[::1]")).is_none());
1197         assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none());
1198         assert!(p.intercept(&url("http://10.124.7.8")).is_none());
1199 
1200         // reset user setting when guards drop
1201         drop(_g1);
1202         drop(_g2);
1203         // Let other threads run now
1204         drop(_lock);
1205     }
1206 
1207     #[test]
test_no_proxy_load()1208     fn test_no_proxy_load() {
1209         // Stop other threads from modifying process-global ENV while we are.
1210         let _lock = ENVLOCK.lock();
1211 
1212         let _g1 = env_guard("no_proxy");
1213         let domain = "lower.case";
1214         env::set_var("no_proxy", domain);
1215         // Manually construct this so we aren't use the cache
1216         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1217         p.no_proxy = NoProxy::new();
1218         assert_eq!(
1219             p.no_proxy.expect("should have a no proxy set").domains.0[0],
1220             domain
1221         );
1222 
1223         env::remove_var("no_proxy");
1224         let _g2 = env_guard("NO_PROXY");
1225         let domain = "upper.case";
1226         env::set_var("NO_PROXY", domain);
1227         // Manually construct this so we aren't use the cache
1228         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1229         p.no_proxy = NoProxy::new();
1230         assert_eq!(
1231             p.no_proxy.expect("should have a no proxy set").domains.0[0],
1232             domain
1233         );
1234 
1235         let _g3 = env_guard("HTTP_PROXY");
1236         env::remove_var("NO_PROXY");
1237         env::remove_var("no_proxy");
1238         let target = "http://example.domain/";
1239         env::set_var("HTTP_PROXY", target);
1240 
1241         // Manually construct this so we aren't use the cache
1242         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1243         p.no_proxy = NoProxy::new();
1244         assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
1245 
1246         assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1247 
1248         // reset user setting when guards drop
1249         drop(_g1);
1250         drop(_g2);
1251         drop(_g3);
1252         // Let other threads run now
1253         drop(_lock);
1254     }
1255 
1256     #[cfg(target_os = "windows")]
1257     #[test]
test_type_prefix_extraction()1258     fn test_type_prefix_extraction() {
1259         assert!(extract_type_prefix("test").is_none());
1260         assert!(extract_type_prefix("://test").is_none());
1261         assert!(extract_type_prefix("some:prefix://test").is_none());
1262         assert!(extract_type_prefix("some/prefix://test").is_none());
1263 
1264         assert_eq!(extract_type_prefix("http://test").unwrap(), "http");
1265         assert_eq!(extract_type_prefix("a://test").unwrap(), "a");
1266     }
1267 
1268     /// Guard an environment variable, resetting it to the original value
1269     /// when dropped.
env_guard(name: impl Into<String>) -> EnvGuard1270     fn env_guard(name: impl Into<String>) -> EnvGuard {
1271         let name = name.into();
1272         let orig_val = env::var(&name).ok();
1273         env::remove_var(&name);
1274         EnvGuard { name, orig_val }
1275     }
1276 
1277     struct EnvGuard {
1278         name: String,
1279         orig_val: Option<String>,
1280     }
1281 
1282     impl Drop for EnvGuard {
drop(&mut self)1283         fn drop(&mut self) {
1284             if let Some(val) = self.orig_val.take() {
1285                 env::set_var(&self.name, val);
1286             } else {
1287                 env::remove_var(&self.name);
1288             }
1289         }
1290     }
1291 
1292     #[test]
test_has_http_auth()1293     fn test_has_http_auth() {
1294         let http_proxy_with_auth = Proxy {
1295             intercept: Intercept::Http(ProxyScheme::Http {
1296                 auth: Some(HeaderValue::from_static("auth1")),
1297                 host: http::uri::Authority::from_static("authority"),
1298             }),
1299             no_proxy: None,
1300         };
1301         assert_eq!(http_proxy_with_auth.maybe_has_http_auth(), true);
1302         assert_eq!(
1303             http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1304             Some(HeaderValue::from_static("auth1"))
1305         );
1306 
1307         let http_proxy_without_auth = Proxy {
1308             intercept: Intercept::Http(ProxyScheme::Http {
1309                 auth: None,
1310                 host: http::uri::Authority::from_static("authority"),
1311             }),
1312             no_proxy: None,
1313         };
1314         assert_eq!(http_proxy_without_auth.maybe_has_http_auth(), false);
1315         assert_eq!(
1316             http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1317             None
1318         );
1319 
1320         let https_proxy_with_auth = Proxy {
1321             intercept: Intercept::Http(ProxyScheme::Https {
1322                 auth: Some(HeaderValue::from_static("auth2")),
1323                 host: http::uri::Authority::from_static("authority"),
1324             }),
1325             no_proxy: None,
1326         };
1327         assert_eq!(https_proxy_with_auth.maybe_has_http_auth(), false);
1328         assert_eq!(
1329             https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1330             None
1331         );
1332 
1333         let all_http_proxy_with_auth = Proxy {
1334             intercept: Intercept::All(ProxyScheme::Http {
1335                 auth: Some(HeaderValue::from_static("auth3")),
1336                 host: http::uri::Authority::from_static("authority"),
1337             }),
1338             no_proxy: None,
1339         };
1340         assert_eq!(all_http_proxy_with_auth.maybe_has_http_auth(), true);
1341         assert_eq!(
1342             all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1343             Some(HeaderValue::from_static("auth3"))
1344         );
1345 
1346         let all_https_proxy_with_auth = Proxy {
1347             intercept: Intercept::All(ProxyScheme::Https {
1348                 auth: Some(HeaderValue::from_static("auth4")),
1349                 host: http::uri::Authority::from_static("authority"),
1350             }),
1351             no_proxy: None,
1352         };
1353         assert_eq!(all_https_proxy_with_auth.maybe_has_http_auth(), false);
1354         assert_eq!(
1355             all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1356             None
1357         );
1358 
1359         let all_https_proxy_without_auth = Proxy {
1360             intercept: Intercept::All(ProxyScheme::Https {
1361                 auth: None,
1362                 host: http::uri::Authority::from_static("authority"),
1363             }),
1364             no_proxy: None,
1365         };
1366         assert_eq!(all_https_proxy_without_auth.maybe_has_http_auth(), false);
1367         assert_eq!(
1368             all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1369             None
1370         );
1371 
1372         let system_http_proxy_with_auth = Proxy {
1373             intercept: Intercept::System(Arc::new({
1374                 let mut m = HashMap::new();
1375                 m.insert(
1376                     "http".into(),
1377                     ProxyScheme::Http {
1378                         auth: Some(HeaderValue::from_static("auth5")),
1379                         host: http::uri::Authority::from_static("authority"),
1380                     },
1381                 );
1382                 m
1383             })),
1384             no_proxy: None,
1385         };
1386         assert_eq!(system_http_proxy_with_auth.maybe_has_http_auth(), true);
1387         assert_eq!(
1388             system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1389             Some(HeaderValue::from_static("auth5"))
1390         );
1391 
1392         let system_https_proxy_with_auth = Proxy {
1393             intercept: Intercept::System(Arc::new({
1394                 let mut m = HashMap::new();
1395                 m.insert(
1396                     "https".into(),
1397                     ProxyScheme::Https {
1398                         auth: Some(HeaderValue::from_static("auth6")),
1399                         host: http::uri::Authority::from_static("authority"),
1400                     },
1401                 );
1402                 m
1403             })),
1404             no_proxy: None,
1405         };
1406         assert_eq!(system_https_proxy_with_auth.maybe_has_http_auth(), false);
1407         assert_eq!(
1408             system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1409             None
1410         );
1411     }
1412 }
1413