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 traffic 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 configuration based on environment variables (or `None` if no variables are set)
370     ///
371     /// The rules are as follows:
372     /// * The environment variable `NO_PROXY` is checked, if it is not set, `no_proxy` is checked
373     /// * If neither environment variable is set, `None` is returned
374     /// * Entries are expected to be comma-separated (whitespace between entries is ignored)
375     /// * IP addresses (both IPv4 and IPv6) are allowed, as are optional subnet masks (by adding /size,
376     /// for example "`192.168.1.0/24`").
377     /// * An entry "`*`" matches all hostnames (this is the only wildcard allowed)
378     /// * Any other entry is considered a domain name (and may contain a leading dot, for example `google.com`
379     /// and `.google.com` are equivalent) and would match both that domain AND all subdomains.
380     ///
381     /// For example, if `"NO_PROXY=google.com, 192.168.1.0/24"` was set, all of the following would match
382     /// (and therefore would bypass the proxy):
383     /// * `http://google.com/`
384     /// * `http://www.google.com/`
385     /// * `http://192.168.1.42/`
386     ///
387     /// The URL `http://notgoogle.com/` would not match.
388     ///
new() -> Option<Self>389     fn new() -> Option<Self> {
390         let raw = env::var("NO_PROXY")
391             .or_else(|_| env::var("no_proxy"))
392             .unwrap_or_default();
393         if raw.is_empty() {
394             return None;
395         }
396         let mut ips = Vec::new();
397         let mut domains = Vec::new();
398         let parts = raw.split(',').map(str::trim);
399         for part in parts {
400             match part.parse::<IpNet>() {
401                 // If we can parse an IP net or address, then use it, otherwise, assume it is a domain
402                 Ok(ip) => ips.push(Ip::Network(ip)),
403                 Err(_) => match part.parse::<IpAddr>() {
404                     Ok(addr) => ips.push(Ip::Address(addr)),
405                     Err(_) => domains.push(part.to_owned()),
406                 },
407             }
408         }
409         Some(NoProxy {
410             ips: IpMatcher(ips),
411             domains: DomainMatcher(domains),
412         })
413     }
414 
contains(&self, host: &str) -> bool415     fn contains(&self, host: &str) -> bool {
416         // According to RFC3986, raw IPv6 hosts will be wrapped in []. So we need to strip those off
417         // the end in order to parse correctly
418         let host = if host.starts_with('[') {
419             let x: &[_] = &['[', ']'];
420             host.trim_matches(x)
421         } else {
422             host
423         };
424         match host.parse::<IpAddr>() {
425             // If we can parse an IP addr, then use it, otherwise, assume it is a domain
426             Ok(ip) => self.ips.contains(ip),
427             Err(_) => self.domains.contains(host),
428         }
429     }
430 }
431 
432 impl IpMatcher {
contains(&self, addr: IpAddr) -> bool433     fn contains(&self, addr: IpAddr) -> bool {
434         for ip in self.0.iter() {
435             match ip {
436                 Ip::Address(address) => {
437                     if &addr == address {
438                         return true;
439                     }
440                 }
441                 Ip::Network(net) => {
442                     if net.contains(&addr) {
443                         return true;
444                     }
445                 }
446             }
447         }
448         false
449     }
450 }
451 
452 impl DomainMatcher {
453     // The following links may be useful to understand the origin of these rules:
454     // * https://curl.se/libcurl/c/CURLOPT_NOPROXY.html
455     // * https://github.com/curl/curl/issues/1208
contains(&self, domain: &str) -> bool456     fn contains(&self, domain: &str) -> bool {
457         let domain_len = domain.len();
458         for d in self.0.iter() {
459             if d == domain || (d.starts_with('.') && &d[1..] == domain) {
460                 return true;
461             } else if domain.ends_with(d) {
462                 if d.starts_with('.') {
463                     // If the first character of d is a dot, that means the first character of domain
464                     // must also be a dot, so we are looking at a subdomain of d and that matches
465                     return true;
466                 } else if domain.as_bytes()[domain_len - d.len() - 1] == b'.' {
467                     // Given that d is a prefix of domain, if the prior character in domain is a dot
468                     // then that means we must be matching a subdomain of d, and that matches
469                     return true;
470                 }
471             } else if d == "*" {
472                 return true;
473             }
474         }
475         false
476     }
477 }
478 
479 impl ProxyScheme {
480     // To start conservative, keep builders private for now.
481 
482     /// Proxy traffic via the specified URL over HTTP
http(host: &str) -> crate::Result<Self>483     fn http(host: &str) -> crate::Result<Self> {
484         Ok(ProxyScheme::Http {
485             auth: None,
486             host: host.parse().map_err(crate::error::builder)?,
487         })
488     }
489 
490     /// Proxy traffic via the specified URL over HTTPS
https(host: &str) -> crate::Result<Self>491     fn https(host: &str) -> crate::Result<Self> {
492         Ok(ProxyScheme::Https {
493             auth: None,
494             host: host.parse().map_err(crate::error::builder)?,
495         })
496     }
497 
498     /// Proxy traffic via the specified socket address over SOCKS5
499     ///
500     /// # Note
501     ///
502     /// Current SOCKS5 support is provided via blocking IO.
503     #[cfg(feature = "socks")]
socks5(addr: SocketAddr) -> crate::Result<Self>504     fn socks5(addr: SocketAddr) -> crate::Result<Self> {
505         Ok(ProxyScheme::Socks5 {
506             addr,
507             auth: None,
508             remote_dns: false,
509         })
510     }
511 
512     /// Proxy traffic via the specified socket address over SOCKS5H
513     ///
514     /// This differs from SOCKS5 in that DNS resolution is also performed via the proxy.
515     ///
516     /// # Note
517     ///
518     /// Current SOCKS5 support is provided via blocking IO.
519     #[cfg(feature = "socks")]
socks5h(addr: SocketAddr) -> crate::Result<Self>520     fn socks5h(addr: SocketAddr) -> crate::Result<Self> {
521         Ok(ProxyScheme::Socks5 {
522             addr,
523             auth: None,
524             remote_dns: true,
525         })
526     }
527 
528     /// 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, ) -> Self529     fn with_basic_auth<T: Into<String>, U: Into<String>>(
530         mut self,
531         username: T,
532         password: U,
533     ) -> Self {
534         self.set_basic_auth(username, password);
535         self
536     }
537 
set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U)538     fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
539         match *self {
540             ProxyScheme::Http { ref mut auth, .. } => {
541                 let header = encode_basic_auth(&username.into(), &password.into());
542                 *auth = Some(header);
543             }
544             ProxyScheme::Https { ref mut auth, .. } => {
545                 let header = encode_basic_auth(&username.into(), &password.into());
546                 *auth = Some(header);
547             }
548             #[cfg(feature = "socks")]
549             ProxyScheme::Socks5 { ref mut auth, .. } => {
550                 *auth = Some((username.into(), password.into()));
551             }
552         }
553     }
554 
if_no_auth(mut self, update: &Option<HeaderValue>) -> Self555     fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
556         match self {
557             ProxyScheme::Http { ref mut auth, .. } => {
558                 if auth.is_none() {
559                     *auth = update.clone();
560                 }
561             }
562             ProxyScheme::Https { ref mut auth, .. } => {
563                 if auth.is_none() {
564                     *auth = update.clone();
565                 }
566             }
567             #[cfg(feature = "socks")]
568             ProxyScheme::Socks5 { .. } => {}
569         }
570 
571         self
572     }
573 
574     /// Convert a URL into a proxy scheme
575     ///
576     /// Supported schemes: HTTP, HTTPS, (SOCKS5, SOCKS5H if `socks` feature is enabled).
577     // Private for now...
parse(url: Url) -> crate::Result<Self>578     fn parse(url: Url) -> crate::Result<Self> {
579         use url::Position;
580 
581         // Resolve URL to a host and port
582         #[cfg(feature = "socks")]
583         let to_addr = || {
584             let addrs = url
585                 .socket_addrs(|| match url.scheme() {
586                     "socks5" | "socks5h" => Some(1080),
587                     _ => None,
588                 })
589                 .map_err(crate::error::builder)?;
590             addrs
591                 .into_iter()
592                 .next()
593                 .ok_or_else(|| crate::error::builder("unknown proxy scheme"))
594         };
595 
596         let mut scheme = match url.scheme() {
597             "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
598             "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
599             #[cfg(feature = "socks")]
600             "socks5" => Self::socks5(to_addr()?)?,
601             #[cfg(feature = "socks")]
602             "socks5h" => Self::socks5h(to_addr()?)?,
603             _ => return Err(crate::error::builder("unknown proxy scheme")),
604         };
605 
606         if let Some(pwd) = url.password() {
607             let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
608             let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
609             scheme = scheme.with_basic_auth(decoded_username, decoded_password);
610         }
611 
612         Ok(scheme)
613     }
614 
615     #[cfg(test)]
scheme(&self) -> &str616     fn scheme(&self) -> &str {
617         match self {
618             ProxyScheme::Http { .. } => "http",
619             ProxyScheme::Https { .. } => "https",
620             #[cfg(feature = "socks")]
621             ProxyScheme::Socks5 { .. } => "socks5",
622         }
623     }
624 
625     #[cfg(test)]
host(&self) -> &str626     fn host(&self) -> &str {
627         match self {
628             ProxyScheme::Http { host, .. } => host.as_str(),
629             ProxyScheme::Https { host, .. } => host.as_str(),
630             #[cfg(feature = "socks")]
631             ProxyScheme::Socks5 { .. } => panic!("socks5"),
632         }
633     }
634 }
635 
636 impl fmt::Debug for ProxyScheme {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result637     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
638         match self {
639             ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{}", host),
640             ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{}", host),
641             #[cfg(feature = "socks")]
642             ProxyScheme::Socks5 {
643                 addr,
644                 auth: _auth,
645                 remote_dns,
646             } => {
647                 let h = if *remote_dns { "h" } else { "" };
648                 write!(f, "socks5{}://{}", h, addr)
649             }
650         }
651     }
652 }
653 
654 type SystemProxyMap = HashMap<String, ProxyScheme>;
655 type RegistryProxyValues = (u32, String);
656 
657 #[derive(Clone, Debug)]
658 enum Intercept {
659     All(ProxyScheme),
660     Http(ProxyScheme),
661     Https(ProxyScheme),
662     System(Arc<SystemProxyMap>),
663     Custom(Custom),
664 }
665 
666 impl Intercept {
set_basic_auth(&mut self, username: &str, password: &str)667     fn set_basic_auth(&mut self, username: &str, password: &str) {
668         match self {
669             Intercept::All(ref mut s)
670             | Intercept::Http(ref mut s)
671             | Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
672             Intercept::System(_) => unimplemented!(),
673             Intercept::Custom(ref mut custom) => {
674                 let header = encode_basic_auth(username, password);
675                 custom.auth = Some(header);
676             }
677         }
678     }
679 }
680 
681 #[derive(Clone)]
682 struct Custom {
683     // This auth only applies if the returned ProxyScheme doesn't have an auth...
684     auth: Option<HeaderValue>,
685     func: Arc<dyn Fn(&Url) -> Option<crate::Result<ProxyScheme>> + Send + Sync + 'static>,
686 }
687 
688 impl Custom {
call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme>689     fn call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
690         let url = format!(
691             "{}://{}{}{}",
692             uri.scheme(),
693             uri.host(),
694             uri.port().map(|_| ":").unwrap_or(""),
695             uri.port().map(|p| p.to_string()).unwrap_or_default()
696         )
697         .parse()
698         .expect("should be valid Url");
699 
700         (self.func)(&url)
701             .and_then(|result| result.ok())
702             .map(|scheme| scheme.if_no_auth(&self.auth))
703     }
704 }
705 
706 impl fmt::Debug for Custom {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result707     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
708         f.write_str("_")
709     }
710 }
711 
encode_basic_auth(username: &str, password: &str) -> HeaderValue712 pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
713     let val = format!("{}:{}", username, password);
714     let mut header = format!("Basic {}", base64::encode(&val))
715         .parse::<HeaderValue>()
716         .expect("base64 is always valid HeaderValue");
717     header.set_sensitive(true);
718     header
719 }
720 
721 /// A helper trait to allow testing `Proxy::intercept` without having to
722 /// construct `hyper::client::connect::Destination`s.
723 pub(crate) trait Dst {
scheme(&self) -> &str724     fn scheme(&self) -> &str;
host(&self) -> &str725     fn host(&self) -> &str;
port(&self) -> Option<u16>726     fn port(&self) -> Option<u16>;
727 }
728 
729 #[doc(hidden)]
730 impl Dst for Uri {
scheme(&self) -> &str731     fn scheme(&self) -> &str {
732         self.scheme().expect("Uri should have a scheme").as_str()
733     }
734 
host(&self) -> &str735     fn host(&self) -> &str {
736         Uri::host(self).expect("<Uri as Dst>::host should have a str")
737     }
738 
port(&self) -> Option<u16>739     fn port(&self) -> Option<u16> {
740         self.port().map(|p| p.as_u16())
741     }
742 }
743 
744 lazy_static! {
745     static ref SYS_PROXIES: Arc<SystemProxyMap> = Arc::new(get_sys_proxies(get_from_registry()));
746 }
747 
748 /// Get system proxies information.
749 ///
750 /// It can only support Linux, Unix like, and windows system.  Note that it will always
751 /// return a HashMap, even if something runs into error when find registry information in
752 /// Windows system.  Note that invalid proxy url in the system setting will be ignored.
753 ///
754 /// Returns:
755 ///     System proxies information as a hashmap like
756 ///     {"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, >, ) -> SystemProxyMap757 fn get_sys_proxies(
758     #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] registry_values: Option<
759         RegistryProxyValues,
760     >,
761 ) -> SystemProxyMap {
762     let proxies = get_from_environment();
763 
764     // TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed
765     #[cfg(target_os = "windows")]
766     {
767         if proxies.is_empty() {
768             // don't care errors if can't get proxies from registry, just return an empty HashMap.
769             if let Some(registry_values) = registry_values {
770                 return parse_registry_values(registry_values);
771             }
772         }
773     }
774     proxies
775 }
776 
insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool777 fn insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool {
778     if let Ok(valid_addr) = addr.into_proxy_scheme() {
779         proxies.insert(scheme.into(), valid_addr);
780         true
781     } else {
782         false
783     }
784 }
785 
get_from_environment() -> SystemProxyMap786 fn get_from_environment() -> SystemProxyMap {
787     let mut proxies = HashMap::new();
788 
789     if is_cgi() {
790         if log::log_enabled!(log::Level::Warn) && env::var_os("HTTP_PROXY").is_some() {
791             log::warn!("HTTP_PROXY environment variable ignored in CGI");
792         }
793     } else if !insert_from_env(&mut proxies, "http", "HTTP_PROXY") {
794         insert_from_env(&mut proxies, "http", "http_proxy");
795     }
796 
797     if !insert_from_env(&mut proxies, "https", "HTTPS_PROXY") {
798         insert_from_env(&mut proxies, "https", "https_proxy");
799     }
800 
801     proxies
802 }
803 
insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool804 fn insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool {
805     if let Ok(val) = env::var(var) {
806         insert_proxy(proxies, scheme, val)
807     } else {
808         false
809     }
810 }
811 
812 /// Check if we are being executed in a CGI context.
813 ///
814 /// If so, a malicious client can send the `Proxy:` header, and it will
815 /// be in the `HTTP_PROXY` env var. So we don't use it :)
is_cgi() -> bool816 fn is_cgi() -> bool {
817     env::var_os("REQUEST_METHOD").is_some()
818 }
819 
820 #[cfg(target_os = "windows")]
get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>>821 fn get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>> {
822     let hkcu = RegKey::predef(HKEY_CURRENT_USER);
823     let internet_setting: RegKey =
824         hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
825     // ensure the proxy is enable, if the value doesn't exist, an error will returned.
826     let proxy_enable: u32 = internet_setting.get_value("ProxyEnable")?;
827     let proxy_server: String = internet_setting.get_value("ProxyServer")?;
828 
829     Ok((proxy_enable, proxy_server))
830 }
831 
832 #[cfg(target_os = "windows")]
get_from_registry() -> Option<RegistryProxyValues>833 fn get_from_registry() -> Option<RegistryProxyValues> {
834     get_from_registry_impl().ok()
835 }
836 
837 #[cfg(not(target_os = "windows"))]
get_from_registry() -> Option<RegistryProxyValues>838 fn get_from_registry() -> Option<RegistryProxyValues> {
839     None
840 }
841 
842 #[cfg(target_os = "windows")]
parse_registry_values_impl( registry_values: RegistryProxyValues, ) -> Result<SystemProxyMap, Box<dyn Error>>843 fn parse_registry_values_impl(
844     registry_values: RegistryProxyValues,
845 ) -> Result<SystemProxyMap, Box<dyn Error>> {
846     let (proxy_enable, proxy_server) = registry_values;
847 
848     if proxy_enable == 0 {
849         return Ok(HashMap::new());
850     }
851 
852     let mut proxies = HashMap::new();
853     if proxy_server.contains("=") {
854         // per-protocol settings.
855         for p in proxy_server.split(";") {
856             let protocol_parts: Vec<&str> = p.split("=").collect();
857             match protocol_parts.as_slice() {
858                 [protocol, address] => {
859                     // If address doesn't specify an explicit protocol as protocol://address
860                     // then default to HTTP
861                     let address = if extract_type_prefix(*address).is_some() {
862                         String::from(*address)
863                     } else {
864                         format!("http://{}", address)
865                     };
866 
867                     insert_proxy(&mut proxies, *protocol, address);
868                 }
869                 _ => {
870                     // Contains invalid protocol setting, just break the loop
871                     // And make proxies to be empty.
872                     proxies.clear();
873                     break;
874                 }
875             }
876         }
877     } else {
878         if let Some(scheme) = extract_type_prefix(&proxy_server) {
879             // Explicit protocol has been specified
880             insert_proxy(&mut proxies, scheme, proxy_server.to_owned());
881         } else {
882             // No explicit protocol has been specified, default to HTTP
883             insert_proxy(&mut proxies, "http", format!("http://{}", proxy_server));
884             insert_proxy(&mut proxies, "https", format!("http://{}", proxy_server));
885         }
886     }
887     Ok(proxies)
888 }
889 
890 /// Extract the protocol from the given address, if present
891 /// For example, "https://example.com" will return Some("https")
892 #[cfg(target_os = "windows")]
extract_type_prefix(address: &str) -> Option<&str>893 fn extract_type_prefix(address: &str) -> Option<&str> {
894     if let Some(indice) = address.find("://") {
895         if indice == 0 {
896             None
897         } else {
898             let prefix = &address[..indice];
899             let contains_banned = prefix.contains(|c| c == ':' || c == '/');
900 
901             if !contains_banned {
902                 Some(prefix)
903             } else {
904                 None
905             }
906         }
907     } else {
908         None
909     }
910 }
911 
912 #[cfg(target_os = "windows")]
parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap913 fn parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap {
914     parse_registry_values_impl(registry_values).unwrap_or(HashMap::new())
915 }
916 
917 #[cfg(test)]
918 mod tests {
919     use super::*;
920     use lazy_static::lazy_static;
921     use std::sync::Mutex;
922 
923     impl Dst for Url {
scheme(&self) -> &str924         fn scheme(&self) -> &str {
925             Url::scheme(self)
926         }
927 
host(&self) -> &str928         fn host(&self) -> &str {
929             Url::host_str(self).expect("<Url as Dst>::host should have a str")
930         }
931 
port(&self) -> Option<u16>932         fn port(&self) -> Option<u16> {
933             Url::port(self)
934         }
935     }
936 
url(s: &str) -> Url937     fn url(s: &str) -> Url {
938         s.parse().unwrap()
939     }
940 
intercepted_uri(p: &Proxy, s: &str) -> Uri941     fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
942         let (scheme, host) = match p.intercept(&url(s)).unwrap() {
943             ProxyScheme::Http { host, .. } => ("http", host),
944             ProxyScheme::Https { host, .. } => ("https", host),
945             #[cfg(feature = "socks")]
946             _ => panic!("intercepted as socks"),
947         };
948         http::Uri::builder()
949             .scheme(scheme)
950             .authority(host)
951             .path_and_query("/")
952             .build()
953             .expect("intercepted_uri")
954     }
955 
956     #[test]
test_http()957     fn test_http() {
958         let target = "http://example.domain/";
959         let p = Proxy::http(target).unwrap();
960 
961         let http = "http://hyper.rs";
962         let other = "https://hyper.rs";
963 
964         assert_eq!(intercepted_uri(&p, http), target);
965         assert!(p.intercept(&url(other)).is_none());
966     }
967 
968     #[test]
test_https()969     fn test_https() {
970         let target = "http://example.domain/";
971         let p = Proxy::https(target).unwrap();
972 
973         let http = "http://hyper.rs";
974         let other = "https://hyper.rs";
975 
976         assert!(p.intercept(&url(http)).is_none());
977         assert_eq!(intercepted_uri(&p, other), target);
978     }
979 
980     #[test]
test_all()981     fn test_all() {
982         let target = "http://example.domain/";
983         let p = Proxy::all(target).unwrap();
984 
985         let http = "http://hyper.rs";
986         let https = "https://hyper.rs";
987         let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
988 
989         assert_eq!(intercepted_uri(&p, http), target);
990         assert_eq!(intercepted_uri(&p, https), target);
991         assert_eq!(intercepted_uri(&p, other), target);
992     }
993 
994     #[test]
test_custom()995     fn test_custom() {
996         let target1 = "http://example.domain/";
997         let target2 = "https://example.domain/";
998         let p = Proxy::custom(move |url| {
999             if url.host_str() == Some("hyper.rs") {
1000                 target1.parse().ok()
1001             } else if url.scheme() == "http" {
1002                 target2.parse().ok()
1003             } else {
1004                 None::<Url>
1005             }
1006         });
1007 
1008         let http = "http://seanmonstar.com";
1009         let https = "https://hyper.rs";
1010         let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
1011 
1012         assert_eq!(intercepted_uri(&p, http), target2);
1013         assert_eq!(intercepted_uri(&p, https), target1);
1014         assert!(p.intercept(&url(other)).is_none());
1015     }
1016 
1017     #[test]
test_proxy_scheme_parse()1018     fn test_proxy_scheme_parse() {
1019         let ps = "http://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     #[test]
test_proxy_scheme_ip_address_default_http()1031     fn test_proxy_scheme_ip_address_default_http() {
1032         let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap();
1033 
1034         match ps {
1035             ProxyScheme::Http { auth, host } => {
1036                 assert!(auth.is_none());
1037                 assert_eq!(host, "192.168.1.1:8888");
1038             }
1039             other => panic!("unexpected: {:?}", other),
1040         }
1041     }
1042 
1043     #[test]
test_proxy_scheme_parse_default_http_with_auth()1044     fn test_proxy_scheme_parse_default_http_with_auth() {
1045         // this should fail because `foo` is interpreted as the scheme and no host can be found
1046         let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1047 
1048         match ps {
1049             ProxyScheme::Http { auth, host } => {
1050                 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1051                 assert_eq!(host, "localhost:1239");
1052             }
1053             other => panic!("unexpected: {:?}", other),
1054         }
1055     }
1056 
1057     // Smallest possible content for a mutex
1058     struct MutexInner;
1059 
1060     lazy_static! {
1061         static ref ENVLOCK: Mutex<MutexInner> = Mutex::new(MutexInner);
1062     }
1063 
1064     #[test]
test_get_sys_proxies_parsing()1065     fn test_get_sys_proxies_parsing() {
1066         // Stop other threads from modifying process-global ENV while we are.
1067         let _lock = ENVLOCK.lock();
1068         // save system setting first.
1069         let _g1 = env_guard("HTTP_PROXY");
1070         let _g2 = env_guard("http_proxy");
1071 
1072         // Mock ENV, get the results, before doing assertions
1073         // to avoid assert! -> panic! -> Mutex Poisoned.
1074         let baseline_proxies = get_sys_proxies(None);
1075         // the system proxy setting url is invalid.
1076         env::set_var("http_proxy", "file://123465");
1077         let invalid_proxies = get_sys_proxies(None);
1078         // set valid proxy
1079         env::set_var("http_proxy", "127.0.0.1/");
1080         let valid_proxies = get_sys_proxies(None);
1081 
1082         // reset user setting when guards drop
1083         drop(_g1);
1084         drop(_g2);
1085         // Let other threads run now
1086         drop(_lock);
1087 
1088         assert!(!baseline_proxies.contains_key("http"));
1089         assert!(!invalid_proxies.contains_key("http"));
1090 
1091         let p = &valid_proxies["http"];
1092         assert_eq!(p.scheme(), "http");
1093         assert_eq!(p.host(), "127.0.0.1");
1094     }
1095 
1096     #[cfg(target_os = "windows")]
1097     #[test]
test_get_sys_proxies_registry_parsing()1098     fn test_get_sys_proxies_registry_parsing() {
1099         // Stop other threads from modifying process-global ENV while we are.
1100         let _lock = ENVLOCK.lock();
1101         // save system setting first.
1102         let _g1 = env_guard("HTTP_PROXY");
1103         let _g2 = env_guard("http_proxy");
1104 
1105         // Mock ENV, get the results, before doing assertions
1106         // to avoid assert! -> panic! -> Mutex Poisoned.
1107         let baseline_proxies = get_sys_proxies(None);
1108         // the system proxy in the registry has been disabled
1109         let disabled_proxies = get_sys_proxies(Some((0, String::from("http://127.0.0.1/"))));
1110         // set valid proxy
1111         let valid_proxies = get_sys_proxies(Some((1, String::from("http://127.0.0.1/"))));
1112         let valid_proxies_no_schema = get_sys_proxies(Some((1, String::from("127.0.0.1"))));
1113         let valid_proxies_explicit_https =
1114             get_sys_proxies(Some((1, String::from("https://127.0.0.1/"))));
1115         let multiple_proxies = get_sys_proxies(Some((
1116             1,
1117             String::from("http=127.0.0.1:8888;https=127.0.0.2:8888"),
1118         )));
1119         let multiple_proxies_explicit_schema = get_sys_proxies(Some((
1120             1,
1121             String::from("http=http://127.0.0.1:8888;https=https://127.0.0.2:8888"),
1122         )));
1123 
1124         // reset user setting when guards drop
1125         drop(_g1);
1126         drop(_g2);
1127         // Let other threads run now
1128         drop(_lock);
1129 
1130         assert_eq!(baseline_proxies.contains_key("http"), false);
1131         assert_eq!(disabled_proxies.contains_key("http"), false);
1132 
1133         let p = &valid_proxies["http"];
1134         assert_eq!(p.scheme(), "http");
1135         assert_eq!(p.host(), "127.0.0.1");
1136 
1137         let p = &valid_proxies_no_schema["http"];
1138         assert_eq!(p.scheme(), "http");
1139         assert_eq!(p.host(), "127.0.0.1");
1140 
1141         let p = &valid_proxies_no_schema["https"];
1142         assert_eq!(p.scheme(), "http");
1143         assert_eq!(p.host(), "127.0.0.1");
1144 
1145         let p = &valid_proxies_explicit_https["https"];
1146         assert_eq!(p.scheme(), "https");
1147         assert_eq!(p.host(), "127.0.0.1");
1148 
1149         let p = &multiple_proxies["http"];
1150         assert_eq!(p.scheme(), "http");
1151         assert_eq!(p.host(), "127.0.0.1:8888");
1152 
1153         let p = &multiple_proxies["https"];
1154         assert_eq!(p.scheme(), "http");
1155         assert_eq!(p.host(), "127.0.0.2:8888");
1156 
1157         let p = &multiple_proxies_explicit_schema["http"];
1158         assert_eq!(p.scheme(), "http");
1159         assert_eq!(p.host(), "127.0.0.1:8888");
1160 
1161         let p = &multiple_proxies_explicit_schema["https"];
1162         assert_eq!(p.scheme(), "https");
1163         assert_eq!(p.host(), "127.0.0.2:8888");
1164     }
1165 
1166     #[test]
test_get_sys_proxies_in_cgi()1167     fn test_get_sys_proxies_in_cgi() {
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("REQUEST_METHOD");
1172         let _g2 = env_guard("HTTP_PROXY");
1173 
1174         // Mock ENV, get the results, before doing assertions
1175         // to avoid assert! -> panic! -> Mutex Poisoned.
1176         env::set_var("HTTP_PROXY", "http://evil/");
1177 
1178         let baseline_proxies = get_sys_proxies(None);
1179         // set like we're in CGI
1180         env::set_var("REQUEST_METHOD", "GET");
1181 
1182         let cgi_proxies = get_sys_proxies(None);
1183 
1184         // reset user setting when guards drop
1185         drop(_g1);
1186         drop(_g2);
1187         // Let other threads run now
1188         drop(_lock);
1189 
1190         // not in CGI yet
1191         assert_eq!(baseline_proxies["http"].host(), "evil");
1192         // In CGI
1193         assert!(!cgi_proxies.contains_key("http"));
1194     }
1195 
1196     #[test]
test_sys_no_proxy()1197     fn test_sys_no_proxy() {
1198         // Stop other threads from modifying process-global ENV while we are.
1199         let _lock = ENVLOCK.lock();
1200         // save system setting first.
1201         let _g1 = env_guard("HTTP_PROXY");
1202         let _g2 = env_guard("NO_PROXY");
1203 
1204         let target = "http://example.domain/";
1205         env::set_var("HTTP_PROXY", target);
1206 
1207         env::set_var(
1208             "NO_PROXY",
1209             ".foo.bar, bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17",
1210         );
1211 
1212         // Manually construct this so we aren't use the cache
1213         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1214         p.no_proxy = NoProxy::new();
1215 
1216         // random url, not in no_proxy
1217         assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1218         // make sure that random non-subdomain string prefixes don't match
1219         assert_eq!(intercepted_uri(&p, "http://notfoo.bar"), target);
1220         // make sure that random non-subdomain string prefixes don't match
1221         assert_eq!(intercepted_uri(&p, "http://notbar.baz"), target);
1222         // ipv4 address out of range
1223         assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target);
1224         // ipv4 address out of range
1225         assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target);
1226         // ipv6 address out of range
1227         assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target);
1228         // ipv6 address out of range
1229         assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target);
1230 
1231         // make sure subdomains (with leading .) match
1232         assert!(p.intercept(&url("http://hello.foo.bar")).is_none());
1233         // make sure exact matches (without leading .) match (also makes sure spaces between entries work)
1234         assert!(p.intercept(&url("http://bar.baz")).is_none());
1235         // check case sensitivity
1236         assert!(p.intercept(&url("http://BAR.baz")).is_none());
1237         // make sure subdomains (without leading . in no_proxy) match
1238         assert!(p.intercept(&url("http://foo.bar.baz")).is_none());
1239         // make sure subdomains (without leading . in no_proxy) match - this differs from cURL
1240         assert!(p.intercept(&url("http://foo.bar")).is_none());
1241         // ipv4 address match within range
1242         assert!(p.intercept(&url("http://10.42.1.100")).is_none());
1243         // ipv6 address exact match
1244         assert!(p.intercept(&url("http://[::1]")).is_none());
1245         // ipv6 address match within range
1246         assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none());
1247         // ipv4 address exact match
1248         assert!(p.intercept(&url("http://10.124.7.8")).is_none());
1249 
1250         // reset user setting when guards drop
1251         drop(_g1);
1252         drop(_g2);
1253         // Let other threads run now
1254         drop(_lock);
1255     }
1256 
1257     #[test]
test_wildcard_sys_no_proxy()1258     fn test_wildcard_sys_no_proxy() {
1259         // Stop other threads from modifying process-global ENV while we are.
1260         let _lock = ENVLOCK.lock();
1261         // save system setting first.
1262         let _g1 = env_guard("HTTP_PROXY");
1263         let _g2 = env_guard("NO_PROXY");
1264 
1265         let target = "http://example.domain/";
1266         env::set_var("HTTP_PROXY", target);
1267 
1268         env::set_var("NO_PROXY", "*");
1269 
1270         // Manually construct this so we aren't use the cache
1271         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1272         p.no_proxy = NoProxy::new();
1273 
1274         assert!(p.intercept(&url("http://foo.bar")).is_none());
1275 
1276         // reset user setting when guards drop
1277         drop(_g1);
1278         drop(_g2);
1279         // Let other threads run now
1280         drop(_lock);
1281     }
1282 
1283     #[test]
test_empty_sys_no_proxy()1284     fn test_empty_sys_no_proxy() {
1285         // Stop other threads from modifying process-global ENV while we are.
1286         let _lock = ENVLOCK.lock();
1287         // save system setting first.
1288         let _g1 = env_guard("HTTP_PROXY");
1289         let _g2 = env_guard("NO_PROXY");
1290 
1291         let target = "http://example.domain/";
1292         env::set_var("HTTP_PROXY", target);
1293 
1294         env::set_var("NO_PROXY", ",");
1295 
1296         // Manually construct this so we aren't use the cache
1297         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1298         p.no_proxy = NoProxy::new();
1299 
1300         // everything should go through proxy, "effectively" nothing is in no_proxy
1301         assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1302 
1303         // reset user setting when guards drop
1304         drop(_g1);
1305         drop(_g2);
1306         // Let other threads run now
1307         drop(_lock);
1308     }
1309 
1310     #[test]
test_no_proxy_load()1311     fn test_no_proxy_load() {
1312         // Stop other threads from modifying process-global ENV while we are.
1313         let _lock = ENVLOCK.lock();
1314 
1315         let _g1 = env_guard("no_proxy");
1316         let domain = "lower.case";
1317         env::set_var("no_proxy", domain);
1318         // Manually construct this so we aren't use the cache
1319         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1320         p.no_proxy = NoProxy::new();
1321         assert_eq!(
1322             p.no_proxy.expect("should have a no proxy set").domains.0[0],
1323             domain
1324         );
1325 
1326         env::remove_var("no_proxy");
1327         let _g2 = env_guard("NO_PROXY");
1328         let domain = "upper.case";
1329         env::set_var("NO_PROXY", domain);
1330         // Manually construct this so we aren't use the cache
1331         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1332         p.no_proxy = NoProxy::new();
1333         assert_eq!(
1334             p.no_proxy.expect("should have a no proxy set").domains.0[0],
1335             domain
1336         );
1337 
1338         let _g3 = env_guard("HTTP_PROXY");
1339         env::remove_var("NO_PROXY");
1340         env::remove_var("no_proxy");
1341         let target = "http://example.domain/";
1342         env::set_var("HTTP_PROXY", target);
1343 
1344         // Manually construct this so we aren't use the cache
1345         let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1346         p.no_proxy = NoProxy::new();
1347         assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
1348 
1349         assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1350 
1351         // reset user setting when guards drop
1352         drop(_g1);
1353         drop(_g2);
1354         drop(_g3);
1355         // Let other threads run now
1356         drop(_lock);
1357     }
1358 
1359     #[cfg(target_os = "windows")]
1360     #[test]
test_type_prefix_extraction()1361     fn test_type_prefix_extraction() {
1362         assert!(extract_type_prefix("test").is_none());
1363         assert!(extract_type_prefix("://test").is_none());
1364         assert!(extract_type_prefix("some:prefix://test").is_none());
1365         assert!(extract_type_prefix("some/prefix://test").is_none());
1366 
1367         assert_eq!(extract_type_prefix("http://test").unwrap(), "http");
1368         assert_eq!(extract_type_prefix("a://test").unwrap(), "a");
1369     }
1370 
1371     /// Guard an environment variable, resetting it to the original value
1372     /// when dropped.
env_guard(name: impl Into<String>) -> EnvGuard1373     fn env_guard(name: impl Into<String>) -> EnvGuard {
1374         let name = name.into();
1375         let orig_val = env::var(&name).ok();
1376         env::remove_var(&name);
1377         EnvGuard { name, orig_val }
1378     }
1379 
1380     struct EnvGuard {
1381         name: String,
1382         orig_val: Option<String>,
1383     }
1384 
1385     impl Drop for EnvGuard {
drop(&mut self)1386         fn drop(&mut self) {
1387             if let Some(val) = self.orig_val.take() {
1388                 env::set_var(&self.name, val);
1389             } else {
1390                 env::remove_var(&self.name);
1391             }
1392         }
1393     }
1394 
1395     #[test]
test_has_http_auth()1396     fn test_has_http_auth() {
1397         let http_proxy_with_auth = Proxy {
1398             intercept: Intercept::Http(ProxyScheme::Http {
1399                 auth: Some(HeaderValue::from_static("auth1")),
1400                 host: http::uri::Authority::from_static("authority"),
1401             }),
1402             no_proxy: None,
1403         };
1404         assert!(http_proxy_with_auth.maybe_has_http_auth());
1405         assert_eq!(
1406             http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1407             Some(HeaderValue::from_static("auth1"))
1408         );
1409 
1410         let http_proxy_without_auth = Proxy {
1411             intercept: Intercept::Http(ProxyScheme::Http {
1412                 auth: None,
1413                 host: http::uri::Authority::from_static("authority"),
1414             }),
1415             no_proxy: None,
1416         };
1417         assert!(!http_proxy_without_auth.maybe_has_http_auth());
1418         assert_eq!(
1419             http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1420             None
1421         );
1422 
1423         let https_proxy_with_auth = Proxy {
1424             intercept: Intercept::Http(ProxyScheme::Https {
1425                 auth: Some(HeaderValue::from_static("auth2")),
1426                 host: http::uri::Authority::from_static("authority"),
1427             }),
1428             no_proxy: None,
1429         };
1430         assert!(!https_proxy_with_auth.maybe_has_http_auth());
1431         assert_eq!(
1432             https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1433             None
1434         );
1435 
1436         let all_http_proxy_with_auth = Proxy {
1437             intercept: Intercept::All(ProxyScheme::Http {
1438                 auth: Some(HeaderValue::from_static("auth3")),
1439                 host: http::uri::Authority::from_static("authority"),
1440             }),
1441             no_proxy: None,
1442         };
1443         assert!(all_http_proxy_with_auth.maybe_has_http_auth());
1444         assert_eq!(
1445             all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1446             Some(HeaderValue::from_static("auth3"))
1447         );
1448 
1449         let all_https_proxy_with_auth = Proxy {
1450             intercept: Intercept::All(ProxyScheme::Https {
1451                 auth: Some(HeaderValue::from_static("auth4")),
1452                 host: http::uri::Authority::from_static("authority"),
1453             }),
1454             no_proxy: None,
1455         };
1456         assert!(!all_https_proxy_with_auth.maybe_has_http_auth());
1457         assert_eq!(
1458             all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1459             None
1460         );
1461 
1462         let all_https_proxy_without_auth = Proxy {
1463             intercept: Intercept::All(ProxyScheme::Https {
1464                 auth: None,
1465                 host: http::uri::Authority::from_static("authority"),
1466             }),
1467             no_proxy: None,
1468         };
1469         assert!(!all_https_proxy_without_auth.maybe_has_http_auth());
1470         assert_eq!(
1471             all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1472             None
1473         );
1474 
1475         let system_http_proxy_with_auth = Proxy {
1476             intercept: Intercept::System(Arc::new({
1477                 let mut m = HashMap::new();
1478                 m.insert(
1479                     "http".into(),
1480                     ProxyScheme::Http {
1481                         auth: Some(HeaderValue::from_static("auth5")),
1482                         host: http::uri::Authority::from_static("authority"),
1483                     },
1484                 );
1485                 m
1486             })),
1487             no_proxy: None,
1488         };
1489         assert!(system_http_proxy_with_auth.maybe_has_http_auth());
1490         assert_eq!(
1491             system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1492             Some(HeaderValue::from_static("auth5"))
1493         );
1494 
1495         let system_https_proxy_with_auth = Proxy {
1496             intercept: Intercept::System(Arc::new({
1497                 let mut m = HashMap::new();
1498                 m.insert(
1499                     "https".into(),
1500                     ProxyScheme::Https {
1501                         auth: Some(HeaderValue::from_static("auth6")),
1502                         host: http::uri::Authority::from_static("authority"),
1503                     },
1504                 );
1505                 m
1506             })),
1507             no_proxy: None,
1508         };
1509         assert!(!system_https_proxy_with_auth.maybe_has_http_auth());
1510         assert_eq!(
1511             system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1512             None
1513         );
1514     }
1515 }
1516