reqwest/
proxy.rs

1use std::fmt;
2#[cfg(feature = "socks")]
3use std::net::SocketAddr;
4use std::sync::Arc;
5
6use crate::into_url::{IntoUrl, IntoUrlSealed};
7use crate::Url;
8use http::{header::HeaderValue, Uri};
9use ipnet::IpNet;
10use percent_encoding::percent_decode;
11use std::collections::HashMap;
12use std::env;
13use std::error::Error;
14use std::net::IpAddr;
15#[cfg(all(target_os = "macos", feature = "macos-system-configuration"))]
16use system_configuration::{
17    core_foundation::{
18        base::CFType,
19        dictionary::CFDictionary,
20        number::CFNumber,
21        string::{CFString, CFStringRef},
22    },
23    dynamic_store::SCDynamicStoreBuilder,
24    sys::schema_definitions::kSCPropNetProxiesHTTPEnable,
25    sys::schema_definitions::kSCPropNetProxiesHTTPPort,
26    sys::schema_definitions::kSCPropNetProxiesHTTPProxy,
27    sys::schema_definitions::kSCPropNetProxiesHTTPSEnable,
28    sys::schema_definitions::kSCPropNetProxiesHTTPSPort,
29    sys::schema_definitions::kSCPropNetProxiesHTTPSProxy,
30};
31
32#[cfg(target_os = "windows")]
33use windows_registry::CURRENT_USER;
34
35/// Configuration of a proxy that a `Client` should pass requests to.
36///
37/// A `Proxy` has a couple pieces to it:
38///
39/// - a URL of how to talk to the proxy
40/// - rules on what `Client` requests should be directed to the proxy
41///
42/// For instance, let's look at `Proxy::http`:
43///
44/// ```rust
45/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
46/// let proxy = reqwest::Proxy::http("https://secure.example")?;
47/// # Ok(())
48/// # }
49/// ```
50///
51/// This proxy will intercept all HTTP requests, and make use of the proxy
52/// at `https://secure.example`. A request to `http://hyper.rs` will talk
53/// to your proxy. A request to `https://hyper.rs` will not.
54///
55/// Multiple `Proxy` rules can be configured for a `Client`. The `Client` will
56/// check each `Proxy` in the order it was added. This could mean that a
57/// `Proxy` added first with eager intercept rules, such as `Proxy::all`,
58/// would prevent a `Proxy` later in the list from ever working, so take care.
59///
60/// By enabling the `"socks"` feature it is possible to use a socks proxy:
61/// ```rust
62/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
63/// let proxy = reqwest::Proxy::http("socks5://192.168.1.1:9000")?;
64/// # Ok(())
65/// # }
66/// ```
67#[derive(Clone)]
68pub struct Proxy {
69    intercept: Intercept,
70    no_proxy: Option<NoProxy>,
71}
72
73/// Represents a possible matching entry for an IP address
74#[derive(Clone, Debug)]
75enum Ip {
76    Address(IpAddr),
77    Network(IpNet),
78}
79
80/// A wrapper around a list of IP cidr blocks or addresses with a [IpMatcher::contains] method for
81/// checking if an IP address is contained within the matcher
82#[derive(Clone, Debug, Default)]
83struct IpMatcher(Vec<Ip>);
84
85/// A wrapper around a list of domains with a [DomainMatcher::contains] method for checking if a
86/// domain is contained within the matcher
87#[derive(Clone, Debug, Default)]
88struct DomainMatcher(Vec<String>);
89
90/// A configuration for filtering out requests that shouldn't be proxied
91#[derive(Clone, Debug, Default)]
92pub struct NoProxy {
93    ips: IpMatcher,
94    domains: DomainMatcher,
95}
96
97/// A particular scheme used for proxying requests.
98///
99/// For example, HTTP vs SOCKS5
100#[derive(Clone)]
101pub enum ProxyScheme {
102    Http {
103        auth: Option<HeaderValue>,
104        host: http::uri::Authority,
105    },
106    Https {
107        auth: Option<HeaderValue>,
108        host: http::uri::Authority,
109    },
110    #[cfg(feature = "socks")]
111    Socks4 { addr: SocketAddr, remote_dns: bool },
112    #[cfg(feature = "socks")]
113    Socks5 {
114        addr: SocketAddr,
115        auth: Option<(String, String)>,
116        remote_dns: bool,
117    },
118}
119
120impl ProxyScheme {
121    fn maybe_http_auth(&self) -> Option<&HeaderValue> {
122        match self {
123            ProxyScheme::Http { auth, .. } | ProxyScheme::Https { auth, .. } => auth.as_ref(),
124            #[cfg(feature = "socks")]
125            _ => None,
126        }
127    }
128}
129
130/// Trait used for converting into a proxy scheme. This trait supports
131/// parsing from a URL-like type, whilst also supporting proxy schemes
132/// built directly using the factory methods.
133pub trait IntoProxyScheme {
134    fn into_proxy_scheme(self) -> crate::Result<ProxyScheme>;
135}
136
137impl<S: IntoUrl> IntoProxyScheme for S {
138    fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> {
139        // validate the URL
140        let url = match self.as_str().into_url() {
141            Ok(ok) => ok,
142            Err(e) => {
143                let mut presumed_to_have_scheme = true;
144                let mut source = e.source();
145                while let Some(err) = source {
146                    if let Some(parse_error) = err.downcast_ref::<url::ParseError>() {
147                        if *parse_error == url::ParseError::RelativeUrlWithoutBase {
148                            presumed_to_have_scheme = false;
149                            break;
150                        }
151                    } else if err.downcast_ref::<crate::error::BadScheme>().is_some() {
152                        presumed_to_have_scheme = false;
153                        break;
154                    }
155                    source = err.source();
156                }
157                if presumed_to_have_scheme {
158                    return Err(crate::error::builder(e));
159                }
160                // the issue could have been caused by a missing scheme, so we try adding http://
161                let try_this = format!("http://{}", self.as_str());
162                try_this.into_url().map_err(|_| {
163                    // return the original error
164                    crate::error::builder(e)
165                })?
166            }
167        };
168        ProxyScheme::parse(url)
169    }
170}
171
172// These bounds are accidentally leaked by the blanket impl of IntoProxyScheme
173// for all types that implement IntoUrl. So, this function exists to detect
174// if we were to break those bounds for a user.
175fn _implied_bounds() {
176    fn prox<T: IntoProxyScheme>(_t: T) {}
177
178    fn url<T: IntoUrl>(t: T) {
179        prox(t);
180    }
181}
182
183impl IntoProxyScheme for ProxyScheme {
184    fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> {
185        Ok(self)
186    }
187}
188
189impl Proxy {
190    /// Proxy all HTTP traffic to the passed URL.
191    ///
192    /// # Example
193    ///
194    /// ```
195    /// # extern crate reqwest;
196    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
197    /// let client = reqwest::Client::builder()
198    ///     .proxy(reqwest::Proxy::http("https://my.prox")?)
199    ///     .build()?;
200    /// # Ok(())
201    /// # }
202    /// # fn main() {}
203    /// ```
204    pub fn http<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
205        Ok(Proxy::new(Intercept::Http(
206            proxy_scheme.into_proxy_scheme()?,
207        )))
208    }
209
210    /// Proxy all HTTPS traffic to the passed URL.
211    ///
212    /// # Example
213    ///
214    /// ```
215    /// # extern crate reqwest;
216    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
217    /// let client = reqwest::Client::builder()
218    ///     .proxy(reqwest::Proxy::https("https://example.prox:4545")?)
219    ///     .build()?;
220    /// # Ok(())
221    /// # }
222    /// # fn main() {}
223    /// ```
224    pub fn https<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
225        Ok(Proxy::new(Intercept::Https(
226            proxy_scheme.into_proxy_scheme()?,
227        )))
228    }
229
230    /// Proxy **all** traffic to the passed URL.
231    ///
232    /// # Example
233    ///
234    /// ```
235    /// # extern crate reqwest;
236    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
237    /// let client = reqwest::Client::builder()
238    ///     .proxy(reqwest::Proxy::all("http://pro.xy")?)
239    ///     .build()?;
240    /// # Ok(())
241    /// # }
242    /// # fn main() {}
243    /// ```
244    pub fn all<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
245        Ok(Proxy::new(Intercept::All(
246            proxy_scheme.into_proxy_scheme()?,
247        )))
248    }
249
250    /// Provide a custom function to determine what traffic to proxy to where.
251    ///
252    /// # Example
253    ///
254    /// ```
255    /// # extern crate reqwest;
256    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
257    /// let target = reqwest::Url::parse("https://my.prox")?;
258    /// let client = reqwest::Client::builder()
259    ///     .proxy(reqwest::Proxy::custom(move |url| {
260    ///         if url.host_str() == Some("hyper.rs") {
261    ///             Some(target.clone())
262    ///         } else {
263    ///             None
264    ///         }
265    ///     }))
266    ///     .build()?;
267    /// # Ok(())
268    /// # }
269    /// # fn main() {}
270    /// ```
271    pub fn custom<F, U: IntoProxyScheme>(fun: F) -> Proxy
272    where
273        F: Fn(&Url) -> Option<U> + Send + Sync + 'static,
274    {
275        Proxy::new(Intercept::Custom(Custom {
276            auth: None,
277            func: Arc::new(move |url| fun(url).map(IntoProxyScheme::into_proxy_scheme)),
278        }))
279    }
280
281    pub(crate) fn system() -> Proxy {
282        let mut proxy = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(
283            get_from_platform(),
284        ))));
285        proxy.no_proxy = NoProxy::from_env();
286
287        #[cfg(target_os = "windows")]
288        {
289            // Only read from windows registry proxy settings if not available from an enviroment
290            // variable. This is in line with the stated behavior of both dotnot and nuget on
291            // windows. <https://github.com/seanmonstar/reqwest/issues/2599>
292            if proxy.no_proxy.is_none() {
293                let win_exceptions: String = get_windows_proxy_exceptions();
294                proxy.no_proxy = NoProxy::from_string(&win_exceptions);
295            }
296        }
297
298        proxy
299    }
300
301    fn new(intercept: Intercept) -> Proxy {
302        Proxy {
303            intercept,
304            no_proxy: None,
305        }
306    }
307
308    /// Set the `Proxy-Authorization` header using Basic auth.
309    ///
310    /// # Example
311    ///
312    /// ```
313    /// # extern crate reqwest;
314    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
315    /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
316    ///     .basic_auth("Aladdin", "open sesame");
317    /// # Ok(())
318    /// # }
319    /// # fn main() {}
320    /// ```
321    pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy {
322        self.intercept.set_basic_auth(username, password);
323        self
324    }
325
326    /// Set the `Proxy-Authorization` header to a specified value.
327    ///
328    /// # Example
329    ///
330    /// ```
331    /// # extern crate reqwest;
332    /// # use reqwest::header::*;
333    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
334    /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
335    ///     .custom_http_auth(HeaderValue::from_static("justletmeinalreadyplease"));
336    /// # Ok(())
337    /// # }
338    /// # fn main() {}
339    /// ```
340    pub fn custom_http_auth(mut self, header_value: HeaderValue) -> Proxy {
341        self.intercept.set_custom_http_auth(header_value);
342        self
343    }
344
345    /// Adds a `No Proxy` exclusion list to this Proxy
346    ///
347    /// # Example
348    ///
349    /// ```
350    /// # extern crate reqwest;
351    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
352    /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
353    ///     .no_proxy(reqwest::NoProxy::from_string("direct.tld, sub.direct2.tld"));
354    /// # Ok(())
355    /// # }
356    /// # fn main() {}
357    /// ```
358    pub fn no_proxy(mut self, no_proxy: Option<NoProxy>) -> Proxy {
359        self.no_proxy = no_proxy;
360        self
361    }
362
363    pub(crate) fn maybe_has_http_auth(&self) -> bool {
364        match &self.intercept {
365            Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().is_some(),
366            // Custom *may* match 'http', so assume so.
367            Intercept::Custom(_) => true,
368            Intercept::System(system) => system
369                .get("http")
370                .and_then(|s| s.maybe_http_auth())
371                .is_some(),
372            Intercept::Https(_) => false,
373        }
374    }
375
376    pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
377        match &self.intercept {
378            Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().cloned(),
379            Intercept::System(system) => system
380                .get("http")
381                .and_then(|s| s.maybe_http_auth().cloned()),
382            Intercept::Custom(custom) => {
383                custom.call(uri).and_then(|s| s.maybe_http_auth().cloned())
384            }
385            Intercept::Https(_) => None,
386        }
387    }
388
389    pub(crate) fn intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
390        let in_no_proxy = self
391            .no_proxy
392            .as_ref()
393            .map_or(false, |np| np.contains(uri.host()));
394        match self.intercept {
395            Intercept::All(ref u) => {
396                if !in_no_proxy {
397                    Some(u.clone())
398                } else {
399                    None
400                }
401            }
402            Intercept::Http(ref u) => {
403                if !in_no_proxy && uri.scheme() == "http" {
404                    Some(u.clone())
405                } else {
406                    None
407                }
408            }
409            Intercept::Https(ref u) => {
410                if !in_no_proxy && uri.scheme() == "https" {
411                    Some(u.clone())
412                } else {
413                    None
414                }
415            }
416            Intercept::System(ref map) => {
417                if in_no_proxy {
418                    None
419                } else {
420                    map.get(uri.scheme()).cloned()
421                }
422            }
423            Intercept::Custom(ref custom) => {
424                if !in_no_proxy {
425                    custom.call(uri)
426                } else {
427                    None
428                }
429            }
430        }
431    }
432
433    pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool {
434        match self.intercept {
435            Intercept::All(_) => true,
436            Intercept::Http(_) => uri.scheme() == "http",
437            Intercept::Https(_) => uri.scheme() == "https",
438            Intercept::System(ref map) => map.contains_key(uri.scheme()),
439            Intercept::Custom(ref custom) => custom.call(uri).is_some(),
440        }
441    }
442}
443
444impl fmt::Debug for Proxy {
445    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
446        f.debug_tuple("Proxy")
447            .field(&self.intercept)
448            .field(&self.no_proxy)
449            .finish()
450    }
451}
452
453impl NoProxy {
454    /// Returns a new no-proxy configuration based on environment variables (or `None` if no variables are set)
455    /// see [self::NoProxy::from_string()] for the string format
456    pub fn from_env() -> Option<NoProxy> {
457        let raw = env::var("NO_PROXY")
458            .or_else(|_| env::var("no_proxy"))
459            .ok()?;
460
461        // Per the docs, this returns `None` if no environment variable is set. We can only reach
462        // here if an env var is set, so we return `Some(NoProxy::default)` if `from_string`
463        // returns None, which occurs with an empty string.
464        Some(Self::from_string(&raw).unwrap_or_default())
465    }
466
467    /// Returns a new no-proxy configuration based on a `no_proxy` string (or `None` if no variables
468    /// are set)
469    /// The rules are as follows:
470    /// * The environment variable `NO_PROXY` is checked, if it is not set, `no_proxy` is checked
471    /// * If neither environment variable is set, `None` is returned
472    /// * Entries are expected to be comma-separated (whitespace between entries is ignored)
473    /// * IP addresses (both IPv4 and IPv6) are allowed, as are optional subnet masks (by adding /size,
474    ///   for example "`192.168.1.0/24`").
475    /// * An entry "`*`" matches all hostnames (this is the only wildcard allowed)
476    /// * Any other entry is considered a domain name (and may contain a leading dot, for example `google.com`
477    ///   and `.google.com` are equivalent) and would match both that domain AND all subdomains.
478    ///
479    /// For example, if `"NO_PROXY=google.com, 192.168.1.0/24"` was set, all the following would match
480    /// (and therefore would bypass the proxy):
481    /// * `http://google.com/`
482    /// * `http://www.google.com/`
483    /// * `http://192.168.1.42/`
484    ///
485    /// The URL `http://notgoogle.com/` would not match.
486    pub fn from_string(no_proxy_list: &str) -> Option<Self> {
487        if no_proxy_list.is_empty() {
488            return None;
489        }
490        let mut ips = Vec::new();
491        let mut domains = Vec::new();
492        let parts = no_proxy_list.split(',').map(str::trim);
493        for part in parts {
494            match part.parse::<IpNet>() {
495                // If we can parse an IP net or address, then use it, otherwise, assume it is a domain
496                Ok(ip) => ips.push(Ip::Network(ip)),
497                Err(_) => match part.parse::<IpAddr>() {
498                    Ok(addr) => ips.push(Ip::Address(addr)),
499                    Err(_) => domains.push(part.to_owned()),
500                },
501            }
502        }
503        Some(NoProxy {
504            ips: IpMatcher(ips),
505            domains: DomainMatcher(domains),
506        })
507    }
508
509    fn contains(&self, host: &str) -> bool {
510        // According to RFC3986, raw IPv6 hosts will be wrapped in []. So we need to strip those off
511        // the end in order to parse correctly
512        let host = if host.starts_with('[') {
513            let x: &[_] = &['[', ']'];
514            host.trim_matches(x)
515        } else {
516            host
517        };
518        match host.parse::<IpAddr>() {
519            // If we can parse an IP addr, then use it, otherwise, assume it is a domain
520            Ok(ip) => self.ips.contains(ip),
521            Err(_) => self.domains.contains(host),
522        }
523    }
524}
525
526impl IpMatcher {
527    fn contains(&self, addr: IpAddr) -> bool {
528        for ip in &self.0 {
529            match ip {
530                Ip::Address(address) => {
531                    if &addr == address {
532                        return true;
533                    }
534                }
535                Ip::Network(net) => {
536                    if net.contains(&addr) {
537                        return true;
538                    }
539                }
540            }
541        }
542        false
543    }
544}
545
546impl DomainMatcher {
547    // The following links may be useful to understand the origin of these rules:
548    // * https://curl.se/libcurl/c/CURLOPT_NOPROXY.html
549    // * https://github.com/curl/curl/issues/1208
550    fn contains(&self, domain: &str) -> bool {
551        let domain_len = domain.len();
552        for d in &self.0 {
553            if d == domain || d.strip_prefix('.') == Some(domain) {
554                return true;
555            } else if domain.ends_with(d) {
556                if d.starts_with('.') {
557                    // If the first character of d is a dot, that means the first character of domain
558                    // must also be a dot, so we are looking at a subdomain of d and that matches
559                    return true;
560                } else if domain.as_bytes().get(domain_len - d.len() - 1) == Some(&b'.') {
561                    // Given that d is a prefix of domain, if the prior character in domain is a dot
562                    // then that means we must be matching a subdomain of d, and that matches
563                    return true;
564                }
565            } else if d == "*" {
566                return true;
567            }
568        }
569        false
570    }
571}
572
573impl ProxyScheme {
574    // To start conservative, keep builders private for now.
575
576    /// Proxy traffic via the specified URL over HTTP
577    fn http(host: &str) -> crate::Result<Self> {
578        Ok(ProxyScheme::Http {
579            auth: None,
580            host: host.parse().map_err(crate::error::builder)?,
581        })
582    }
583
584    /// Proxy traffic via the specified URL over HTTPS
585    fn https(host: &str) -> crate::Result<Self> {
586        Ok(ProxyScheme::Https {
587            auth: None,
588            host: host.parse().map_err(crate::error::builder)?,
589        })
590    }
591
592    /// Proxy traffic via the specified socket address over SOCKS4
593    ///
594    /// # Note
595    ///
596    /// Current SOCKS4 support is provided via blocking IO.
597    #[cfg(feature = "socks")]
598    fn socks4(addr: SocketAddr) -> crate::Result<Self> {
599        Ok(ProxyScheme::Socks4 {
600            addr,
601            remote_dns: false,
602        })
603    }
604
605    /// Proxy traffic via the specified socket address over SOCKS4A
606    ///
607    /// This differs from SOCKS4 in that DNS resolution is also performed via the proxy.
608    ///
609    /// # Note
610    ///
611    /// Current SOCKS4 support is provided via blocking IO.
612    #[cfg(feature = "socks")]
613    fn socks4a(addr: SocketAddr) -> crate::Result<Self> {
614        Ok(ProxyScheme::Socks4 {
615            addr,
616            remote_dns: true,
617        })
618    }
619
620    /// Proxy traffic via the specified socket address over SOCKS5
621    ///
622    /// # Note
623    ///
624    /// Current SOCKS5 support is provided via blocking IO.
625    #[cfg(feature = "socks")]
626    fn socks5(addr: SocketAddr) -> crate::Result<Self> {
627        Ok(ProxyScheme::Socks5 {
628            addr,
629            auth: None,
630            remote_dns: false,
631        })
632    }
633
634    /// Proxy traffic via the specified socket address over SOCKS5H
635    ///
636    /// This differs from SOCKS5 in that DNS resolution is also performed via the proxy.
637    ///
638    /// # Note
639    ///
640    /// Current SOCKS5 support is provided via blocking IO.
641    #[cfg(feature = "socks")]
642    fn socks5h(addr: SocketAddr) -> crate::Result<Self> {
643        Ok(ProxyScheme::Socks5 {
644            addr,
645            auth: None,
646            remote_dns: true,
647        })
648    }
649
650    /// Use a username and password when connecting to the proxy server
651    fn with_basic_auth<T: Into<String>, U: Into<String>>(
652        mut self,
653        username: T,
654        password: U,
655    ) -> Self {
656        self.set_basic_auth(username, password);
657        self
658    }
659
660    fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
661        match *self {
662            ProxyScheme::Http { ref mut auth, .. } => {
663                let header = encode_basic_auth(&username.into(), &password.into());
664                *auth = Some(header);
665            }
666            ProxyScheme::Https { ref mut auth, .. } => {
667                let header = encode_basic_auth(&username.into(), &password.into());
668                *auth = Some(header);
669            }
670            #[cfg(feature = "socks")]
671            ProxyScheme::Socks4 { .. } => {
672                panic!("Socks4 is not supported for this method")
673            }
674            #[cfg(feature = "socks")]
675            ProxyScheme::Socks5 { ref mut auth, .. } => {
676                *auth = Some((username.into(), password.into()));
677            }
678        }
679    }
680
681    fn set_custom_http_auth(&mut self, header_value: HeaderValue) {
682        match *self {
683            ProxyScheme::Http { ref mut auth, .. } => {
684                *auth = Some(header_value);
685            }
686            ProxyScheme::Https { ref mut auth, .. } => {
687                *auth = Some(header_value);
688            }
689            #[cfg(feature = "socks")]
690            ProxyScheme::Socks4 { .. } => {
691                panic!("Socks4 is not supported for this method")
692            }
693            #[cfg(feature = "socks")]
694            ProxyScheme::Socks5 { .. } => {
695                panic!("Socks5 is not supported for this method")
696            }
697        }
698    }
699
700    fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
701        match self {
702            ProxyScheme::Http { ref mut auth, .. } => {
703                if auth.is_none() {
704                    *auth = update.clone();
705                }
706            }
707            ProxyScheme::Https { ref mut auth, .. } => {
708                if auth.is_none() {
709                    *auth = update.clone();
710                }
711            }
712            #[cfg(feature = "socks")]
713            ProxyScheme::Socks4 { .. } => {}
714            #[cfg(feature = "socks")]
715            ProxyScheme::Socks5 { .. } => {}
716        }
717
718        self
719    }
720
721    /// Convert a URL into a proxy scheme
722    ///
723    /// Supported schemes: HTTP, HTTPS, (SOCKS4, SOCKS5, SOCKS5H if `socks` feature is enabled).
724    // Private for now...
725    fn parse(url: Url) -> crate::Result<Self> {
726        use url::Position;
727
728        // Resolve URL to a host and port
729        #[cfg(feature = "socks")]
730        let to_addr = || {
731            let addrs = url
732                .socket_addrs(|| match url.scheme() {
733                    "socks4" | "socks4a" | "socks5" | "socks5h" => Some(1080),
734                    _ => None,
735                })
736                .map_err(crate::error::builder)?;
737            addrs
738                .into_iter()
739                .next()
740                .ok_or_else(|| crate::error::builder("unknown proxy scheme"))
741        };
742
743        let mut scheme = match url.scheme() {
744            "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
745            "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
746            #[cfg(feature = "socks")]
747            "socks4" => Self::socks4(to_addr()?)?,
748            #[cfg(feature = "socks")]
749            "socks4a" => Self::socks4a(to_addr()?)?,
750            #[cfg(feature = "socks")]
751            "socks5" => Self::socks5(to_addr()?)?,
752            #[cfg(feature = "socks")]
753            "socks5h" => Self::socks5h(to_addr()?)?,
754            _ => return Err(crate::error::builder("unknown proxy scheme")),
755        };
756
757        if let Some(pwd) = url.password() {
758            let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
759            let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
760            scheme = scheme.with_basic_auth(decoded_username, decoded_password);
761        }
762
763        Ok(scheme)
764    }
765
766    #[cfg(test)]
767    fn scheme(&self) -> &str {
768        match self {
769            ProxyScheme::Http { .. } => "http",
770            ProxyScheme::Https { .. } => "https",
771            #[cfg(feature = "socks")]
772            ProxyScheme::Socks4 { .. } => "socks4",
773            #[cfg(feature = "socks")]
774            ProxyScheme::Socks5 { .. } => "socks5",
775        }
776    }
777
778    #[cfg(test)]
779    fn host(&self) -> &str {
780        match self {
781            ProxyScheme::Http { host, .. } => host.as_str(),
782            ProxyScheme::Https { host, .. } => host.as_str(),
783            #[cfg(feature = "socks")]
784            ProxyScheme::Socks4 { .. } => panic!("socks4"),
785            #[cfg(feature = "socks")]
786            ProxyScheme::Socks5 { .. } => panic!("socks5"),
787        }
788    }
789}
790
791impl fmt::Debug for ProxyScheme {
792    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
793        match self {
794            ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{host}"),
795            ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{host}"),
796            #[cfg(feature = "socks")]
797            ProxyScheme::Socks4 { addr, remote_dns } => {
798                let h = if *remote_dns { "a" } else { "" };
799                write!(f, "socks4{}://{}", h, addr)
800            }
801            #[cfg(feature = "socks")]
802            ProxyScheme::Socks5 {
803                addr,
804                auth: _auth,
805                remote_dns,
806            } => {
807                let h = if *remote_dns { "h" } else { "" };
808                write!(f, "socks5{h}://{addr}")
809            }
810        }
811    }
812}
813
814type SystemProxyMap = HashMap<String, ProxyScheme>;
815
816#[derive(Clone, Debug)]
817enum Intercept {
818    All(ProxyScheme),
819    Http(ProxyScheme),
820    Https(ProxyScheme),
821    System(Arc<SystemProxyMap>),
822    Custom(Custom),
823}
824
825impl Intercept {
826    fn set_basic_auth(&mut self, username: &str, password: &str) {
827        match self {
828            Intercept::All(ref mut s)
829            | Intercept::Http(ref mut s)
830            | Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
831            Intercept::System(_) => unimplemented!(),
832            Intercept::Custom(ref mut custom) => {
833                let header = encode_basic_auth(username, password);
834                custom.auth = Some(header);
835            }
836        }
837    }
838
839    fn set_custom_http_auth(&mut self, header_value: HeaderValue) {
840        match self {
841            Intercept::All(ref mut s)
842            | Intercept::Http(ref mut s)
843            | Intercept::Https(ref mut s) => s.set_custom_http_auth(header_value),
844            Intercept::System(_) => unimplemented!(),
845            Intercept::Custom(ref mut custom) => {
846                custom.auth = Some(header_value);
847            }
848        }
849    }
850}
851
852#[derive(Clone)]
853struct Custom {
854    // This auth only applies if the returned ProxyScheme doesn't have an auth...
855    auth: Option<HeaderValue>,
856    func: Arc<dyn Fn(&Url) -> Option<crate::Result<ProxyScheme>> + Send + Sync + 'static>,
857}
858
859impl Custom {
860    fn call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
861        let url = format!(
862            "{}://{}{}{}",
863            uri.scheme(),
864            uri.host(),
865            uri.port().map_or("", |_| ":"),
866            uri.port().map_or(String::new(), |p| p.to_string())
867        )
868        .parse()
869        .expect("should be valid Url");
870
871        (self.func)(&url)
872            .and_then(|result| result.ok())
873            .map(|scheme| scheme.if_no_auth(&self.auth))
874    }
875}
876
877impl fmt::Debug for Custom {
878    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
879        f.write_str("_")
880    }
881}
882
883pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
884    crate::util::basic_auth(username, Some(password))
885}
886
887/// A helper trait to allow testing `Proxy::intercept` without having to
888/// construct `hyper::client::connect::Destination`s.
889pub(crate) trait Dst {
890    fn scheme(&self) -> &str;
891    fn host(&self) -> &str;
892    fn port(&self) -> Option<u16>;
893}
894
895#[doc(hidden)]
896impl Dst for Uri {
897    fn scheme(&self) -> &str {
898        self.scheme().expect("Uri should have a scheme").as_str()
899    }
900
901    fn host(&self) -> &str {
902        Uri::host(self).expect("<Uri as Dst>::host should have a str")
903    }
904
905    fn port(&self) -> Option<u16> {
906        self.port().map(|p| p.as_u16())
907    }
908}
909
910/// Get system proxies information.
911///
912/// All platforms will check for proxy settings via environment variables.
913/// If those aren't set, platform-wide proxy settings will be looked up on
914/// Windows and macOS platforms instead. Errors encountered while discovering
915/// these settings are ignored.
916///
917/// Returns:
918///     System proxies information as a hashmap like
919///     {"http": Url::parse("http://127.0.0.1:80"), "https": Url::parse("https://127.0.0.1:80")}
920fn get_sys_proxies(
921    #[cfg_attr(
922        not(any(target_os = "windows", target_os = "macos")),
923        allow(unused_variables)
924    )]
925    platform_proxies: Option<String>,
926) -> SystemProxyMap {
927    let proxies = get_from_environment();
928
929    #[cfg(any(target_os = "windows", target_os = "macos"))]
930    if proxies.is_empty() {
931        // if there are errors in acquiring the platform proxies,
932        // we'll just return an empty HashMap
933        if let Some(platform_proxies) = platform_proxies {
934            return parse_platform_values(platform_proxies);
935        }
936    }
937
938    proxies
939}
940
941fn insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool {
942    if addr.trim().is_empty() {
943        // do not accept empty or whitespace proxy address
944        false
945    } else if let Ok(valid_addr) = addr.into_proxy_scheme() {
946        proxies.insert(scheme.into(), valid_addr);
947        true
948    } else {
949        false
950    }
951}
952
953fn get_from_environment() -> SystemProxyMap {
954    let mut proxies = HashMap::new();
955
956    if !(insert_from_env(&mut proxies, "http", "ALL_PROXY")
957        && insert_from_env(&mut proxies, "https", "ALL_PROXY"))
958    {
959        insert_from_env(&mut proxies, "http", "all_proxy");
960        insert_from_env(&mut proxies, "https", "all_proxy");
961    }
962
963    if is_cgi() {
964        if log::log_enabled!(log::Level::Warn) && env::var_os("HTTP_PROXY").is_some() {
965            log::warn!("HTTP_PROXY environment variable ignored in CGI");
966        }
967    } else if !insert_from_env(&mut proxies, "http", "HTTP_PROXY") {
968        insert_from_env(&mut proxies, "http", "http_proxy");
969    }
970
971    if !insert_from_env(&mut proxies, "https", "HTTPS_PROXY") {
972        insert_from_env(&mut proxies, "https", "https_proxy");
973    }
974
975    proxies
976}
977
978fn insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool {
979    if let Ok(val) = env::var(var) {
980        insert_proxy(proxies, scheme, val)
981    } else {
982        false
983    }
984}
985
986/// Check if we are being executed in a CGI context.
987///
988/// If so, a malicious client can send the `Proxy:` header, and it will
989/// be in the `HTTP_PROXY` env var. So we don't use it :)
990fn is_cgi() -> bool {
991    env::var_os("REQUEST_METHOD").is_some()
992}
993
994#[cfg(target_os = "windows")]
995fn get_from_platform_impl() -> Result<Option<String>, Box<dyn Error>> {
996    let internet_setting = windows_registry::CURRENT_USER
997        .open("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
998    // ensure the proxy is enabled, if the value doesn't exist, an error will be returned.
999    let proxy_enable = internet_setting.get_u32("ProxyEnable")?;
1000    let proxy_server = internet_setting.get_string("ProxyServer")?;
1001
1002    Ok((proxy_enable == 1).then_some(proxy_server))
1003}
1004
1005#[cfg(all(target_os = "macos", feature = "macos-system-configuration"))]
1006fn parse_setting_from_dynamic_store(
1007    proxies_map: &CFDictionary<CFString, CFType>,
1008    enabled_key: CFStringRef,
1009    host_key: CFStringRef,
1010    port_key: CFStringRef,
1011    scheme: &str,
1012) -> Option<String> {
1013    let proxy_enabled = proxies_map
1014        .find(enabled_key)
1015        .and_then(|flag| flag.downcast::<CFNumber>())
1016        .and_then(|flag| flag.to_i32())
1017        .unwrap_or(0)
1018        == 1;
1019
1020    if proxy_enabled {
1021        let proxy_host = proxies_map
1022            .find(host_key)
1023            .and_then(|host| host.downcast::<CFString>())
1024            .map(|host| host.to_string());
1025        let proxy_port = proxies_map
1026            .find(port_key)
1027            .and_then(|port| port.downcast::<CFNumber>())
1028            .and_then(|port| port.to_i32());
1029
1030        return match (proxy_host, proxy_port) {
1031            (Some(proxy_host), Some(proxy_port)) => {
1032                Some(format!("{scheme}={proxy_host}:{proxy_port}"))
1033            }
1034            (Some(proxy_host), None) => Some(format!("{scheme}={proxy_host}")),
1035            (None, Some(_)) => None,
1036            (None, None) => None,
1037        };
1038    }
1039
1040    None
1041}
1042
1043#[cfg(all(target_os = "macos", feature = "macos-system-configuration"))]
1044fn get_from_platform_impl() -> Result<Option<String>, Box<dyn Error>> {
1045    let store = SCDynamicStoreBuilder::new("reqwest").build();
1046
1047    let proxies_map = if let Some(proxies_map) = store.get_proxies() {
1048        proxies_map
1049    } else {
1050        return Ok(None);
1051    };
1052
1053    let http_proxy_config = parse_setting_from_dynamic_store(
1054        &proxies_map,
1055        unsafe { kSCPropNetProxiesHTTPEnable },
1056        unsafe { kSCPropNetProxiesHTTPProxy },
1057        unsafe { kSCPropNetProxiesHTTPPort },
1058        "http",
1059    );
1060    let https_proxy_config = parse_setting_from_dynamic_store(
1061        &proxies_map,
1062        unsafe { kSCPropNetProxiesHTTPSEnable },
1063        unsafe { kSCPropNetProxiesHTTPSProxy },
1064        unsafe { kSCPropNetProxiesHTTPSPort },
1065        "https",
1066    );
1067
1068    match http_proxy_config.as_ref().zip(https_proxy_config.as_ref()) {
1069        Some((http_config, https_config)) => Ok(Some(format!("{http_config};{https_config}"))),
1070        None => Ok(http_proxy_config.or(https_proxy_config)),
1071    }
1072}
1073
1074#[cfg(any(
1075    target_os = "windows",
1076    all(target_os = "macos", feature = "macos-system-configuration")
1077))]
1078fn get_from_platform() -> Option<String> {
1079    get_from_platform_impl().ok().flatten()
1080}
1081
1082#[cfg(not(any(
1083    target_os = "windows",
1084    all(target_os = "macos", feature = "macos-system-configuration")
1085)))]
1086fn get_from_platform() -> Option<String> {
1087    None
1088}
1089
1090#[cfg(any(target_os = "windows", target_os = "macos"))]
1091fn parse_platform_values_impl(platform_values: String) -> SystemProxyMap {
1092    let mut proxies = HashMap::new();
1093    if platform_values.contains("=") {
1094        // per-protocol settings.
1095        for p in platform_values.split(";") {
1096            let protocol_parts: Vec<&str> = p.split("=").collect();
1097            match protocol_parts.as_slice() {
1098                [protocol, address] => {
1099                    // If address doesn't specify an explicit protocol as protocol://address
1100                    // then default to HTTP
1101                    let address = if extract_type_prefix(*address).is_some() {
1102                        String::from(*address)
1103                    } else {
1104                        format!("http://{address}")
1105                    };
1106
1107                    insert_proxy(&mut proxies, *protocol, address);
1108                }
1109                _ => {
1110                    // Contains invalid protocol setting, just break the loop
1111                    // And make proxies to be empty.
1112                    proxies.clear();
1113                    break;
1114                }
1115            }
1116        }
1117    } else {
1118        if let Some(scheme) = extract_type_prefix(&platform_values) {
1119            // Explicit protocol has been specified
1120            insert_proxy(&mut proxies, scheme, platform_values.to_owned());
1121        } else {
1122            // No explicit protocol has been specified, default to HTTP
1123            insert_proxy(&mut proxies, "http", format!("http://{platform_values}"));
1124            insert_proxy(&mut proxies, "https", format!("http://{platform_values}"));
1125        }
1126    }
1127    proxies
1128}
1129
1130/// Extract the protocol from the given address, if present
1131/// For example, "https://example.com" will return Some("https")
1132#[cfg(any(target_os = "windows", target_os = "macos"))]
1133fn extract_type_prefix(address: &str) -> Option<&str> {
1134    if let Some(indice) = address.find("://") {
1135        if indice == 0 {
1136            None
1137        } else {
1138            let prefix = &address[..indice];
1139            let contains_banned = prefix.contains(|c| c == ':' || c == '/');
1140
1141            if !contains_banned {
1142                Some(prefix)
1143            } else {
1144                None
1145            }
1146        }
1147    } else {
1148        None
1149    }
1150}
1151
1152#[cfg(any(target_os = "windows", target_os = "macos"))]
1153fn parse_platform_values(platform_values: String) -> SystemProxyMap {
1154    parse_platform_values_impl(platform_values)
1155}
1156
1157#[cfg(target_os = "windows")]
1158fn get_windows_proxy_exceptions() -> String {
1159    let mut exceptions = String::new();
1160    if let Ok(key) =
1161        CURRENT_USER.create(r"Software\Microsoft\Windows\CurrentVersion\Internet Settings")
1162    {
1163        if let Ok(value) = key.get_string("ProxyOverride") {
1164            exceptions = value
1165                .split(';')
1166                .map(|s| s.trim())
1167                .collect::<Vec<&str>>()
1168                .join(",")
1169                .replace("*.", "");
1170        }
1171    }
1172    exceptions
1173}
1174
1175#[cfg(test)]
1176mod tests {
1177    use super::*;
1178    use once_cell::sync::Lazy;
1179    use std::sync::Mutex;
1180
1181    impl Dst for Url {
1182        fn scheme(&self) -> &str {
1183            Url::scheme(self)
1184        }
1185
1186        fn host(&self) -> &str {
1187            Url::host_str(self).expect("<Url as Dst>::host should have a str")
1188        }
1189
1190        fn port(&self) -> Option<u16> {
1191            Url::port(self)
1192        }
1193    }
1194
1195    fn url(s: &str) -> Url {
1196        s.parse().unwrap()
1197    }
1198
1199    fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
1200        let (scheme, host) = match p.intercept(&url(s)).unwrap() {
1201            ProxyScheme::Http { host, .. } => ("http", host),
1202            ProxyScheme::Https { host, .. } => ("https", host),
1203            #[cfg(feature = "socks")]
1204            _ => panic!("intercepted as socks"),
1205        };
1206        http::Uri::builder()
1207            .scheme(scheme)
1208            .authority(host)
1209            .path_and_query("/")
1210            .build()
1211            .expect("intercepted_uri")
1212    }
1213
1214    #[test]
1215    fn test_http() {
1216        let target = "http://example.domain/";
1217        let p = Proxy::http(target).unwrap();
1218
1219        let http = "http://hyper.rs";
1220        let other = "https://hyper.rs";
1221
1222        assert_eq!(intercepted_uri(&p, http), target);
1223        assert!(p.intercept(&url(other)).is_none());
1224    }
1225
1226    #[test]
1227    fn test_https() {
1228        let target = "http://example.domain/";
1229        let p = Proxy::https(target).unwrap();
1230
1231        let http = "http://hyper.rs";
1232        let other = "https://hyper.rs";
1233
1234        assert!(p.intercept(&url(http)).is_none());
1235        assert_eq!(intercepted_uri(&p, other), target);
1236    }
1237
1238    #[test]
1239    fn test_all() {
1240        let target = "http://example.domain/";
1241        let p = Proxy::all(target).unwrap();
1242
1243        let http = "http://hyper.rs";
1244        let https = "https://hyper.rs";
1245        let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
1246
1247        assert_eq!(intercepted_uri(&p, http), target);
1248        assert_eq!(intercepted_uri(&p, https), target);
1249        assert_eq!(intercepted_uri(&p, other), target);
1250    }
1251
1252    #[test]
1253    fn test_custom() {
1254        let target1 = "http://example.domain/";
1255        let target2 = "https://example.domain/";
1256        let p = Proxy::custom(move |url| {
1257            if url.host_str() == Some("hyper.rs") {
1258                target1.parse().ok()
1259            } else if url.scheme() == "http" {
1260                target2.parse().ok()
1261            } else {
1262                None::<Url>
1263            }
1264        });
1265
1266        let http = "http://seanmonstar.com";
1267        let https = "https://hyper.rs";
1268        let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
1269
1270        assert_eq!(intercepted_uri(&p, http), target2);
1271        assert_eq!(intercepted_uri(&p, https), target1);
1272        assert!(p.intercept(&url(other)).is_none());
1273    }
1274
1275    #[test]
1276    fn test_proxy_scheme_parse() {
1277        let ps = "http://foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1278
1279        match ps {
1280            ProxyScheme::Http { auth, host } => {
1281                assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1282                assert_eq!(host, "localhost:1239");
1283            }
1284            other => panic!("unexpected: {other:?}"),
1285        }
1286    }
1287
1288    #[test]
1289    fn test_proxy_scheme_ip_address_default_http() {
1290        let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap();
1291
1292        match ps {
1293            ProxyScheme::Http { auth, host } => {
1294                assert!(auth.is_none());
1295                assert_eq!(host, "192.168.1.1:8888");
1296            }
1297            other => panic!("unexpected: {other:?}"),
1298        }
1299    }
1300
1301    #[test]
1302    fn test_proxy_scheme_parse_default_http_with_auth() {
1303        // this should fail because `foo` is interpreted as the scheme and no host can be found
1304        let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1305
1306        match ps {
1307            ProxyScheme::Http { auth, host } => {
1308                assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1309                assert_eq!(host, "localhost:1239");
1310            }
1311            other => panic!("unexpected: {other:?}"),
1312        }
1313    }
1314
1315    #[test]
1316    fn test_domain_matcher() {
1317        let domains = vec![".foo.bar".into(), "bar.foo".into()];
1318        let matcher = DomainMatcher(domains);
1319
1320        // domains match with leading `.`
1321        assert!(matcher.contains("foo.bar"));
1322        // subdomains match with leading `.`
1323        assert!(matcher.contains("www.foo.bar"));
1324
1325        // domains match with no leading `.`
1326        assert!(matcher.contains("bar.foo"));
1327        // subdomains match with no leading `.`
1328        assert!(matcher.contains("www.bar.foo"));
1329
1330        // non-subdomain string prefixes don't match
1331        assert!(!matcher.contains("notfoo.bar"));
1332        assert!(!matcher.contains("notbar.foo"));
1333    }
1334
1335    // Smallest possible content for a mutex
1336    struct MutexInner;
1337
1338    static ENVLOCK: Lazy<Mutex<MutexInner>> = Lazy::new(|| Mutex::new(MutexInner));
1339
1340    #[test]
1341    fn test_get_sys_proxies_parsing() {
1342        // Stop other threads from modifying process-global ENV while we are.
1343        let _lock = ENVLOCK.lock();
1344        // save system setting first.
1345        let _g1 = env_guard("HTTP_PROXY");
1346        let _g2 = env_guard("http_proxy");
1347        let _g3 = env_guard("ALL_PROXY");
1348
1349        // Mock ENV, get the results, before doing assertions
1350        // to avoid assert! -> panic! -> Mutex Poisoned.
1351        let baseline_proxies = get_sys_proxies(None);
1352        // the system proxy setting url is invalid.
1353        env::set_var("http_proxy", "file://123465");
1354        let invalid_proxies = get_sys_proxies(None);
1355        // set valid proxy
1356        env::set_var("http_proxy", "127.0.0.1/");
1357        let valid_proxies = get_sys_proxies(None);
1358        // set valid ALL_PROXY
1359        env::set_var("ALL_PROXY", "127.0.0.2/");
1360        let all_proxies = get_sys_proxies(None);
1361
1362        // reset user setting when guards drop
1363        drop(_g1);
1364        drop(_g2);
1365        // Let other threads run now
1366        drop(_lock);
1367
1368        assert!(!baseline_proxies.contains_key("http"));
1369        assert!(!invalid_proxies.contains_key("http"));
1370
1371        let p = &valid_proxies["http"];
1372        assert_eq!(p.scheme(), "http");
1373        assert_eq!(p.host(), "127.0.0.1");
1374
1375        assert_eq!(all_proxies.len(), 2);
1376        // Set by ALL_PROXY
1377        assert_eq!(all_proxies["https"].host(), "127.0.0.2");
1378        // Overwritten by the more specific HTTP_PROXY
1379        assert_eq!(all_proxies["http"].host(), "127.0.0.1");
1380    }
1381
1382    #[cfg(any(target_os = "windows", target_os = "macos"))]
1383    #[test]
1384    fn test_get_sys_proxies_registry_parsing() {
1385        // Stop other threads from modifying process-global ENV while we are.
1386        let _lock = ENVLOCK.lock();
1387        // save system setting first.
1388        let _g1 = env_guard("HTTP_PROXY");
1389        let _g2 = env_guard("http_proxy");
1390
1391        // Mock ENV, get the results, before doing assertions
1392        // to avoid assert! -> panic! -> Mutex Poisoned.
1393        let baseline_proxies = get_sys_proxies(None);
1394        // set valid proxy
1395        let valid_proxies = get_sys_proxies(Some(String::from("http://127.0.0.1/")));
1396        let valid_proxies_no_scheme = get_sys_proxies(Some(String::from("127.0.0.1")));
1397        let valid_proxies_explicit_https =
1398            get_sys_proxies(Some(String::from("https://127.0.0.1/")));
1399        let multiple_proxies = get_sys_proxies(Some(String::from(
1400            "http=127.0.0.1:8888;https=127.0.0.2:8888",
1401        )));
1402        let multiple_proxies_explicit_scheme = get_sys_proxies(Some(String::from(
1403            "http=http://127.0.0.1:8888;https=https://127.0.0.2:8888",
1404        )));
1405
1406        // reset user setting when guards drop
1407        drop(_g1);
1408        drop(_g2);
1409        // Let other threads run now
1410        drop(_lock);
1411
1412        assert_eq!(baseline_proxies.contains_key("http"), false);
1413
1414        let p = &valid_proxies["http"];
1415        assert_eq!(p.scheme(), "http");
1416        assert_eq!(p.host(), "127.0.0.1");
1417
1418        let p = &valid_proxies_no_scheme["http"];
1419        assert_eq!(p.scheme(), "http");
1420        assert_eq!(p.host(), "127.0.0.1");
1421
1422        let p = &valid_proxies_no_scheme["https"];
1423        assert_eq!(p.scheme(), "http");
1424        assert_eq!(p.host(), "127.0.0.1");
1425
1426        let p = &valid_proxies_explicit_https["https"];
1427        assert_eq!(p.scheme(), "https");
1428        assert_eq!(p.host(), "127.0.0.1");
1429
1430        let p = &multiple_proxies["http"];
1431        assert_eq!(p.scheme(), "http");
1432        assert_eq!(p.host(), "127.0.0.1:8888");
1433
1434        let p = &multiple_proxies["https"];
1435        assert_eq!(p.scheme(), "http");
1436        assert_eq!(p.host(), "127.0.0.2:8888");
1437
1438        let p = &multiple_proxies_explicit_scheme["http"];
1439        assert_eq!(p.scheme(), "http");
1440        assert_eq!(p.host(), "127.0.0.1:8888");
1441
1442        let p = &multiple_proxies_explicit_scheme["https"];
1443        assert_eq!(p.scheme(), "https");
1444        assert_eq!(p.host(), "127.0.0.2:8888");
1445    }
1446
1447    #[test]
1448    fn test_get_sys_proxies_in_cgi() {
1449        // Stop other threads from modifying process-global ENV while we are.
1450        let _lock = ENVLOCK.lock();
1451        // save system setting first.
1452        let _g1 = env_guard("REQUEST_METHOD");
1453        let _g2 = env_guard("HTTP_PROXY");
1454
1455        // Mock ENV, get the results, before doing assertions
1456        // to avoid assert! -> panic! -> Mutex Poisoned.
1457        env::set_var("HTTP_PROXY", "http://evil/");
1458
1459        let baseline_proxies = get_sys_proxies(None);
1460        // set like we're in CGI
1461        env::set_var("REQUEST_METHOD", "GET");
1462
1463        let cgi_proxies = get_sys_proxies(None);
1464
1465        // reset user setting when guards drop
1466        drop(_g1);
1467        drop(_g2);
1468        // Let other threads run now
1469        drop(_lock);
1470
1471        // not in CGI yet
1472        assert_eq!(baseline_proxies["http"].host(), "evil");
1473        // In CGI
1474        assert!(!cgi_proxies.contains_key("http"));
1475    }
1476
1477    #[test]
1478    fn test_sys_no_proxy() {
1479        // Stop other threads from modifying process-global ENV while we are.
1480        let _lock = ENVLOCK.lock();
1481        // save system setting first.
1482        let _g1 = env_guard("HTTP_PROXY");
1483        let _g2 = env_guard("NO_PROXY");
1484
1485        let target = "http://example.domain/";
1486        env::set_var("HTTP_PROXY", target);
1487
1488        env::set_var(
1489            "NO_PROXY",
1490            ".foo.bar, bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17",
1491        );
1492
1493        // Manually construct this so we aren't use the cache
1494        let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1495        p.no_proxy = NoProxy::from_env();
1496
1497        // random url, not in no_proxy
1498        assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1499        // make sure that random non-subdomain string prefixes don't match
1500        assert_eq!(intercepted_uri(&p, "http://notfoo.bar"), target);
1501        // make sure that random non-subdomain string prefixes don't match
1502        assert_eq!(intercepted_uri(&p, "http://notbar.baz"), target);
1503        // ipv4 address out of range
1504        assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target);
1505        // ipv4 address out of range
1506        assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target);
1507        // ipv6 address out of range
1508        assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target);
1509        // ipv6 address out of range
1510        assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target);
1511
1512        // make sure subdomains (with leading .) match
1513        assert!(p.intercept(&url("http://hello.foo.bar")).is_none());
1514        // make sure exact matches (without leading .) match (also makes sure spaces between entries work)
1515        assert!(p.intercept(&url("http://bar.baz")).is_none());
1516        // check case sensitivity
1517        assert!(p.intercept(&url("http://BAR.baz")).is_none());
1518        // make sure subdomains (without leading . in no_proxy) match
1519        assert!(p.intercept(&url("http://foo.bar.baz")).is_none());
1520        // make sure subdomains (without leading . in no_proxy) match - this differs from cURL
1521        assert!(p.intercept(&url("http://foo.bar")).is_none());
1522        // ipv4 address match within range
1523        assert!(p.intercept(&url("http://10.42.1.100")).is_none());
1524        // ipv6 address exact match
1525        assert!(p.intercept(&url("http://[::1]")).is_none());
1526        // ipv6 address match within range
1527        assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none());
1528        // ipv4 address exact match
1529        assert!(p.intercept(&url("http://10.124.7.8")).is_none());
1530
1531        // reset user setting when guards drop
1532        drop(_g1);
1533        drop(_g2);
1534        // Let other threads run now
1535        drop(_lock);
1536    }
1537
1538    #[test]
1539    fn test_proxy_no_proxy_interception_for_proxy_types() {
1540        let proxy_url = "http://example.domain/";
1541        let no_proxy = ".no.proxy.tld";
1542
1543        // test all proxy interception
1544        let p = Proxy::all(proxy_url)
1545            .unwrap()
1546            .no_proxy(NoProxy::from_string(no_proxy));
1547
1548        // random url, not in no_proxy
1549        assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
1550
1551        // positive match for no proxy
1552        assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1553
1554        // test http proxy interception
1555        let p = Proxy::http(proxy_url)
1556            .unwrap()
1557            .no_proxy(NoProxy::from_string(no_proxy));
1558
1559        // random url, not in no_proxy
1560        assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
1561
1562        // positive match for no proxy
1563        assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
1564
1565        // should not be intercepted due to scheme
1566        assert!(p.intercept(&url("https://hyper.rs")).is_none());
1567
1568        // test https proxy interception
1569        let p = Proxy::https(proxy_url)
1570            .unwrap()
1571            .no_proxy(NoProxy::from_string(no_proxy));
1572
1573        // random url, not in no_proxy
1574        assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
1575
1576        // positive match for no proxy
1577        assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1578
1579        // should not be intercepted due to scheme
1580        assert!(p.intercept(&url("http://hyper.rs")).is_none());
1581
1582        // test custom proxy interception
1583        let p = Proxy::custom(move |_url| Some(proxy_url)).no_proxy(NoProxy::from_string(no_proxy));
1584
1585        // random url, not in no_proxy
1586        assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
1587
1588        // positive match for no proxy
1589        assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
1590        assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
1591    }
1592
1593    #[test]
1594    fn test_wildcard_sys_no_proxy() {
1595        // Stop other threads from modifying process-global ENV while we are.
1596        let _lock = ENVLOCK.lock();
1597        // save system setting first.
1598        let _g1 = env_guard("HTTP_PROXY");
1599        let _g2 = env_guard("NO_PROXY");
1600
1601        let target = "http://example.domain/";
1602        env::set_var("HTTP_PROXY", target);
1603
1604        env::set_var("NO_PROXY", "*");
1605
1606        // Manually construct this so we aren't use the cache
1607        let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1608        p.no_proxy = NoProxy::from_env();
1609
1610        assert!(p.intercept(&url("http://foo.bar")).is_none());
1611
1612        // reset user setting when guards drop
1613        drop(_g1);
1614        drop(_g2);
1615        // Let other threads run now
1616        drop(_lock);
1617    }
1618
1619    #[test]
1620    fn test_empty_sys_no_proxy() {
1621        // Stop other threads from modifying process-global ENV while we are.
1622        let _lock = ENVLOCK.lock();
1623        // save system setting first.
1624        let _g1 = env_guard("HTTP_PROXY");
1625        let _g2 = env_guard("NO_PROXY");
1626
1627        let target = "http://example.domain/";
1628        env::set_var("HTTP_PROXY", target);
1629
1630        env::set_var("NO_PROXY", ",");
1631
1632        // Manually construct this so we aren't use the cache
1633        let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1634        p.no_proxy = NoProxy::from_env();
1635
1636        // everything should go through proxy, "effectively" nothing is in no_proxy
1637        assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1638
1639        // Also test the behavior of `NO_PROXY` being an empty string.
1640        env::set_var("NO_PROXY", "");
1641
1642        // Manually construct this so we aren't use the cache
1643        let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1644        p.no_proxy = NoProxy::from_env();
1645        // In the case of an empty string `NoProxy::from_env()` should return `Some(..)`
1646        assert!(p.no_proxy.is_some());
1647
1648        // everything should go through proxy, "effectively" nothing is in no_proxy
1649        assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1650
1651        // reset user setting when guards drop
1652        drop(_g1);
1653        drop(_g2);
1654        // Let other threads run now
1655        drop(_lock);
1656    }
1657
1658    #[test]
1659    fn test_no_proxy_load() {
1660        // Stop other threads from modifying process-global ENV while we are.
1661        let _lock = ENVLOCK.lock();
1662
1663        let _g1 = env_guard("no_proxy");
1664        let domain = "lower.case";
1665        env::set_var("no_proxy", domain);
1666        // Manually construct this so we aren't use the cache
1667        let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1668        p.no_proxy = NoProxy::from_env();
1669        assert_eq!(
1670            p.no_proxy.expect("should have a no proxy set").domains.0[0],
1671            domain
1672        );
1673
1674        env::remove_var("no_proxy");
1675        let _g2 = env_guard("NO_PROXY");
1676        let domain = "upper.case";
1677        env::set_var("NO_PROXY", domain);
1678        // Manually construct this so we aren't use the cache
1679        let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1680        p.no_proxy = NoProxy::from_env();
1681        assert_eq!(
1682            p.no_proxy.expect("should have a no proxy set").domains.0[0],
1683            domain
1684        );
1685
1686        let _g3 = env_guard("HTTP_PROXY");
1687        env::remove_var("NO_PROXY");
1688        env::remove_var("no_proxy");
1689        let target = "http://example.domain/";
1690        env::set_var("HTTP_PROXY", target);
1691
1692        // Manually construct this so we aren't use the cache
1693        let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1694        p.no_proxy = NoProxy::from_env();
1695        assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
1696
1697        assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1698
1699        // reset user setting when guards drop
1700        drop(_g1);
1701        drop(_g2);
1702        drop(_g3);
1703        // Let other threads run now
1704        drop(_lock);
1705    }
1706
1707    #[cfg(any(target_os = "windows", target_os = "macos"))]
1708    #[test]
1709    fn test_type_prefix_extraction() {
1710        assert!(extract_type_prefix("test").is_none());
1711        assert!(extract_type_prefix("://test").is_none());
1712        assert!(extract_type_prefix("some:prefix://test").is_none());
1713        assert!(extract_type_prefix("some/prefix://test").is_none());
1714
1715        assert_eq!(extract_type_prefix("http://test").unwrap(), "http");
1716        assert_eq!(extract_type_prefix("a://test").unwrap(), "a");
1717    }
1718
1719    /// Guard an environment variable, resetting it to the original value
1720    /// when dropped.
1721    fn env_guard(name: impl Into<String>) -> EnvGuard {
1722        let name = name.into();
1723        let orig_val = env::var(&name).ok();
1724        env::remove_var(&name);
1725        EnvGuard { name, orig_val }
1726    }
1727
1728    struct EnvGuard {
1729        name: String,
1730        orig_val: Option<String>,
1731    }
1732
1733    impl Drop for EnvGuard {
1734        fn drop(&mut self) {
1735            if let Some(val) = self.orig_val.take() {
1736                env::set_var(&self.name, val);
1737            } else {
1738                env::remove_var(&self.name);
1739            }
1740        }
1741    }
1742
1743    #[test]
1744    fn test_has_http_auth() {
1745        let http_proxy_with_auth = Proxy {
1746            intercept: Intercept::Http(ProxyScheme::Http {
1747                auth: Some(HeaderValue::from_static("auth1")),
1748                host: http::uri::Authority::from_static("authority"),
1749            }),
1750            no_proxy: None,
1751        };
1752        assert!(http_proxy_with_auth.maybe_has_http_auth());
1753        assert_eq!(
1754            http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1755            Some(HeaderValue::from_static("auth1"))
1756        );
1757
1758        let http_proxy_without_auth = Proxy {
1759            intercept: Intercept::Http(ProxyScheme::Http {
1760                auth: None,
1761                host: http::uri::Authority::from_static("authority"),
1762            }),
1763            no_proxy: None,
1764        };
1765        assert!(!http_proxy_without_auth.maybe_has_http_auth());
1766        assert_eq!(
1767            http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1768            None
1769        );
1770
1771        let https_proxy_with_auth = Proxy {
1772            intercept: Intercept::Http(ProxyScheme::Https {
1773                auth: Some(HeaderValue::from_static("auth2")),
1774                host: http::uri::Authority::from_static("authority"),
1775            }),
1776            no_proxy: None,
1777        };
1778        assert!(https_proxy_with_auth.maybe_has_http_auth());
1779        assert_eq!(
1780            https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1781            Some(HeaderValue::from_static("auth2"))
1782        );
1783
1784        let all_http_proxy_with_auth = Proxy {
1785            intercept: Intercept::All(ProxyScheme::Http {
1786                auth: Some(HeaderValue::from_static("auth3")),
1787                host: http::uri::Authority::from_static("authority"),
1788            }),
1789            no_proxy: None,
1790        };
1791        assert!(all_http_proxy_with_auth.maybe_has_http_auth());
1792        assert_eq!(
1793            all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1794            Some(HeaderValue::from_static("auth3"))
1795        );
1796
1797        let all_https_proxy_with_auth = Proxy {
1798            intercept: Intercept::All(ProxyScheme::Https {
1799                auth: Some(HeaderValue::from_static("auth4")),
1800                host: http::uri::Authority::from_static("authority"),
1801            }),
1802            no_proxy: None,
1803        };
1804        assert!(all_https_proxy_with_auth.maybe_has_http_auth());
1805        assert_eq!(
1806            all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1807            Some(HeaderValue::from_static("auth4"))
1808        );
1809
1810        let all_https_proxy_without_auth = Proxy {
1811            intercept: Intercept::All(ProxyScheme::Https {
1812                auth: None,
1813                host: http::uri::Authority::from_static("authority"),
1814            }),
1815            no_proxy: None,
1816        };
1817        assert!(!all_https_proxy_without_auth.maybe_has_http_auth());
1818        assert_eq!(
1819            all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1820            None
1821        );
1822
1823        let system_http_proxy_with_auth = Proxy {
1824            intercept: Intercept::System(Arc::new({
1825                let mut m = HashMap::new();
1826                m.insert(
1827                    "http".into(),
1828                    ProxyScheme::Http {
1829                        auth: Some(HeaderValue::from_static("auth5")),
1830                        host: http::uri::Authority::from_static("authority"),
1831                    },
1832                );
1833                m
1834            })),
1835            no_proxy: None,
1836        };
1837        assert!(system_http_proxy_with_auth.maybe_has_http_auth());
1838        assert_eq!(
1839            system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1840            Some(HeaderValue::from_static("auth5"))
1841        );
1842
1843        let system_https_proxy_with_auth = Proxy {
1844            intercept: Intercept::System(Arc::new({
1845                let mut m = HashMap::new();
1846                m.insert(
1847                    "https".into(),
1848                    ProxyScheme::Https {
1849                        auth: Some(HeaderValue::from_static("auth6")),
1850                        host: http::uri::Authority::from_static("authority"),
1851                    },
1852                );
1853                m
1854            })),
1855            no_proxy: None,
1856        };
1857        assert!(!system_https_proxy_with_auth.maybe_has_http_auth());
1858        assert_eq!(
1859            system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1860            None
1861        );
1862    }
1863}
1864
1865#[cfg(test)]
1866mod test {
1867    mod into_proxy_scheme {
1868        use crate::Proxy;
1869        use std::error::Error;
1870        use std::mem::discriminant;
1871
1872        fn includes(haystack: &crate::error::Error, needle: url::ParseError) -> bool {
1873            let mut source = haystack.source();
1874            while let Some(error) = source {
1875                if let Some(parse_error) = error.downcast_ref::<url::ParseError>() {
1876                    if discriminant(parse_error) == discriminant(&needle) {
1877                        return true;
1878                    }
1879                }
1880                source = error.source();
1881            }
1882            false
1883        }
1884
1885        fn check_parse_error(url: &str, needle: url::ParseError) {
1886            let error = Proxy::http(url).unwrap_err();
1887            if !includes(&error, needle) {
1888                panic!("{needle:?} expected; {error:?}, {error} found");
1889            }
1890        }
1891
1892        mod when_scheme_missing {
1893            mod and_url_is_valid {
1894                use crate::Proxy;
1895
1896                #[test]
1897                fn lookback_works() {
1898                    let _ = Proxy::http("127.0.0.1").unwrap();
1899                }
1900
1901                #[test]
1902                fn loopback_port_works() {
1903                    let _ = Proxy::http("127.0.0.1:8080").unwrap();
1904                }
1905
1906                #[test]
1907                fn loopback_username_works() {
1908                    let _ = Proxy::http("username@127.0.0.1").unwrap();
1909                }
1910
1911                #[test]
1912                fn loopback_username_password_works() {
1913                    let _ = Proxy::http("username:password@127.0.0.1").unwrap();
1914                }
1915
1916                #[test]
1917                fn loopback_username_password_port_works() {
1918                    let _ = Proxy::http("ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
1919                }
1920
1921                #[test]
1922                fn domain_works() {
1923                    let _ = Proxy::http("proxy.example.com").unwrap();
1924                }
1925
1926                #[test]
1927                fn domain_port_works() {
1928                    let _ = Proxy::http("proxy.example.com:8080").unwrap();
1929                }
1930
1931                #[test]
1932                fn domain_username_works() {
1933                    let _ = Proxy::http("username@proxy.example.com").unwrap();
1934                }
1935
1936                #[test]
1937                fn domain_username_password_works() {
1938                    let _ = Proxy::http("username:password@proxy.example.com").unwrap();
1939                }
1940
1941                #[test]
1942                fn domain_username_password_port_works() {
1943                    let _ =
1944                        Proxy::http("ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080").unwrap();
1945                }
1946            }
1947            mod and_url_has_bad {
1948                use super::super::check_parse_error;
1949
1950                #[test]
1951                fn host() {
1952                    check_parse_error("username@", url::ParseError::RelativeUrlWithoutBase);
1953                }
1954
1955                #[test]
1956                fn idna_encoding() {
1957                    check_parse_error("xn---", url::ParseError::RelativeUrlWithoutBase);
1958                }
1959
1960                #[test]
1961                fn port() {
1962                    check_parse_error("127.0.0.1:808080", url::ParseError::RelativeUrlWithoutBase);
1963                }
1964
1965                #[test]
1966                fn ip_v4_address() {
1967                    check_parse_error("421.627.718.469", url::ParseError::RelativeUrlWithoutBase);
1968                }
1969
1970                #[test]
1971                fn ip_v6_address() {
1972                    check_parse_error(
1973                        "[56FE::2159:5BBC::6594]",
1974                        url::ParseError::RelativeUrlWithoutBase,
1975                    );
1976                }
1977
1978                #[test]
1979                fn invalid_domain_character() {
1980                    check_parse_error("abc 123", url::ParseError::RelativeUrlWithoutBase);
1981                }
1982            }
1983        }
1984
1985        mod when_scheme_present {
1986            mod and_url_is_valid {
1987                use crate::Proxy;
1988
1989                #[test]
1990                fn loopback_works() {
1991                    let _ = Proxy::http("http://127.0.0.1").unwrap();
1992                }
1993
1994                #[test]
1995                fn loopback_port_works() {
1996                    let _ = Proxy::http("https://127.0.0.1:8080").unwrap();
1997                }
1998
1999                #[test]
2000                fn loopback_username_works() {
2001                    let _ = Proxy::http("http://username@127.0.0.1").unwrap();
2002                }
2003
2004                #[test]
2005                fn loopback_username_password_works() {
2006                    let _ = Proxy::http("https://username:password@127.0.0.1").unwrap();
2007                }
2008
2009                #[test]
2010                fn loopback_username_password_port_works() {
2011                    let _ =
2012                        Proxy::http("http://ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
2013                }
2014
2015                #[test]
2016                fn domain_works() {
2017                    let _ = Proxy::http("https://proxy.example.com").unwrap();
2018                }
2019
2020                #[test]
2021                fn domain_port_works() {
2022                    let _ = Proxy::http("http://proxy.example.com:8080").unwrap();
2023                }
2024
2025                #[test]
2026                fn domain_username_works() {
2027                    let _ = Proxy::http("https://username@proxy.example.com").unwrap();
2028                }
2029
2030                #[test]
2031                fn domain_username_password_works() {
2032                    let _ = Proxy::http("http://username:password@proxy.example.com").unwrap();
2033                }
2034
2035                #[test]
2036                fn domain_username_password_port_works() {
2037                    let _ =
2038                        Proxy::http("https://ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080")
2039                            .unwrap();
2040                }
2041            }
2042            mod and_url_has_bad {
2043                use super::super::check_parse_error;
2044
2045                #[test]
2046                fn host() {
2047                    check_parse_error("http://username@", url::ParseError::EmptyHost);
2048                }
2049
2050                #[test]
2051                fn idna_encoding() {
2052                    check_parse_error("http://xn---", url::ParseError::IdnaError);
2053                }
2054
2055                #[test]
2056                fn port() {
2057                    check_parse_error("http://127.0.0.1:808080", url::ParseError::InvalidPort);
2058                }
2059
2060                #[test]
2061                fn ip_v4_address() {
2062                    check_parse_error(
2063                        "http://421.627.718.469",
2064                        url::ParseError::InvalidIpv4Address,
2065                    );
2066                }
2067
2068                #[test]
2069                fn ip_v6_address() {
2070                    check_parse_error(
2071                        "http://[56FE::2159:5BBC::6594]",
2072                        url::ParseError::InvalidIpv6Address,
2073                    );
2074                }
2075            }
2076        }
2077    }
2078}