reqwest/
proxy.rs

1use std::error::Error;
2use std::fmt;
3use std::sync::Arc;
4
5use http::uri::Scheme;
6use http::{header::HeaderValue, HeaderMap, Uri};
7use hyper_util::client::proxy::matcher;
8
9use crate::into_url::{IntoUrl, IntoUrlSealed};
10use crate::Url;
11
12// # Internals
13//
14// This module is a couple pieces:
15//
16// - The public builder API
17// - The internal built types that our Connector knows how to use.
18//
19// The user creates a builder (`reqwest::Proxy`), and configures any extras.
20// Once that type is passed to the `ClientBuilder`, we convert it into the
21// built matcher types, making use of `hyper-util`'s matchers.
22
23/// Configuration of a proxy that a `Client` should pass requests to.
24///
25/// A `Proxy` has a couple pieces to it:
26///
27/// - a URL of how to talk to the proxy
28/// - rules on what `Client` requests should be directed to the proxy
29///
30/// For instance, let's look at `Proxy::http`:
31///
32/// ```rust
33/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
34/// let proxy = reqwest::Proxy::http("https://secure.example")?;
35/// # Ok(())
36/// # }
37/// ```
38///
39/// This proxy will intercept all HTTP requests, and make use of the proxy
40/// at `https://secure.example`. A request to `http://hyper.rs` will talk
41/// to your proxy. A request to `https://hyper.rs` will not.
42///
43/// Multiple `Proxy` rules can be configured for a `Client`. The `Client` will
44/// check each `Proxy` in the order it was added. This could mean that a
45/// `Proxy` added first with eager intercept rules, such as `Proxy::all`,
46/// would prevent a `Proxy` later in the list from ever working, so take care.
47///
48/// By enabling the `"socks"` feature it is possible to use a socks proxy:
49/// ```rust
50/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
51/// let proxy = reqwest::Proxy::http("socks5://192.168.1.1:9000")?;
52/// # Ok(())
53/// # }
54/// ```
55#[derive(Clone)]
56pub struct Proxy {
57    extra: Extra,
58    intercept: Intercept,
59    no_proxy: Option<NoProxy>,
60}
61
62/// A configuration for filtering out requests that shouldn't be proxied
63#[derive(Clone, Debug, Default)]
64pub struct NoProxy {
65    inner: String,
66}
67
68#[derive(Clone)]
69struct Extra {
70    auth: Option<HeaderValue>,
71    misc: Option<HeaderMap>,
72}
73
74// ===== Internal =====
75
76pub(crate) struct Matcher {
77    inner: Matcher_,
78    extra: Extra,
79    maybe_has_http_auth: bool,
80    maybe_has_http_custom_headers: bool,
81}
82
83enum Matcher_ {
84    Util(matcher::Matcher),
85    Custom(Custom),
86}
87
88/// Our own type, wrapping an `Intercept`, since we may have a few additional
89/// pieces attached thanks to `reqwest`s extra proxy configuration.
90pub(crate) struct Intercepted {
91    inner: matcher::Intercept,
92    /// This is because of `reqwest::Proxy`'s design which allows configuring
93    /// an explicit auth, besides what might have been in the URL (or Custom).
94    extra: Extra,
95}
96
97/*
98impl ProxyScheme {
99    fn maybe_http_auth(&self) -> Option<&HeaderValue> {
100        match self {
101            ProxyScheme::Http { auth, .. } | ProxyScheme::Https { auth, .. } => auth.as_ref(),
102            #[cfg(feature = "socks")]
103            _ => None,
104        }
105    }
106
107    fn maybe_http_custom_headers(&self) -> Option<&HeaderMap> {
108        match self {
109            ProxyScheme::Http { misc, .. } | ProxyScheme::Https { misc, .. } => misc.as_ref(),
110            #[cfg(feature = "socks")]
111            _ => None,
112        }
113    }
114}
115*/
116
117/// Trait used for converting into a proxy scheme. This trait supports
118/// parsing from a URL-like type, whilst also supporting proxy schemes
119/// built directly using the factory methods.
120pub trait IntoProxy {
121    fn into_proxy(self) -> crate::Result<Url>;
122}
123
124impl<S: IntoUrl> IntoProxy for S {
125    fn into_proxy(self) -> crate::Result<Url> {
126        match self.as_str().into_url() {
127            Ok(mut url) => {
128                // If the scheme is a SOCKS protocol and no port is specified, set the default
129                if url.port().is_none()
130                    && matches!(url.scheme(), "socks4" | "socks4a" | "socks5" | "socks5h")
131                {
132                    let _ = url.set_port(Some(1080));
133                }
134                Ok(url)
135            }
136            Err(e) => {
137                let mut presumed_to_have_scheme = true;
138                let mut source = e.source();
139                while let Some(err) = source {
140                    if let Some(parse_error) = err.downcast_ref::<url::ParseError>() {
141                        if *parse_error == url::ParseError::RelativeUrlWithoutBase {
142                            presumed_to_have_scheme = false;
143                            break;
144                        }
145                    } else if err.downcast_ref::<crate::error::BadScheme>().is_some() {
146                        presumed_to_have_scheme = false;
147                        break;
148                    }
149                    source = err.source();
150                }
151                if presumed_to_have_scheme {
152                    return Err(crate::error::builder(e));
153                }
154                // the issue could have been caused by a missing scheme, so we try adding http://
155                let try_this = format!("http://{}", self.as_str());
156                try_this.into_url().map_err(|_| {
157                    // return the original error
158                    crate::error::builder(e)
159                })
160            }
161        }
162    }
163}
164
165// These bounds are accidentally leaked by the blanket impl of IntoProxy
166// for all types that implement IntoUrl. So, this function exists to detect
167// if we were to break those bounds for a user.
168fn _implied_bounds() {
169    fn prox<T: IntoProxy>(_t: T) {}
170
171    fn url<T: IntoUrl>(t: T) {
172        prox(t);
173    }
174}
175
176impl Proxy {
177    /// Proxy all HTTP traffic to the passed URL.
178    ///
179    /// # Example
180    ///
181    /// ```
182    /// # extern crate reqwest;
183    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
184    /// let client = reqwest::Client::builder()
185    ///     .proxy(reqwest::Proxy::http("https://my.prox")?)
186    ///     .build()?;
187    /// # Ok(())
188    /// # }
189    /// # fn main() {}
190    /// ```
191    pub fn http<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> {
192        Ok(Proxy::new(Intercept::Http(proxy_scheme.into_proxy()?)))
193    }
194
195    /// Proxy all HTTPS traffic to the passed URL.
196    ///
197    /// # Example
198    ///
199    /// ```
200    /// # extern crate reqwest;
201    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
202    /// let client = reqwest::Client::builder()
203    ///     .proxy(reqwest::Proxy::https("https://example.prox:4545")?)
204    ///     .build()?;
205    /// # Ok(())
206    /// # }
207    /// # fn main() {}
208    /// ```
209    pub fn https<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> {
210        Ok(Proxy::new(Intercept::Https(proxy_scheme.into_proxy()?)))
211    }
212
213    /// Proxy **all** traffic to the passed URL.
214    ///
215    /// "All" refers to `https` and `http` URLs. Other schemes are not
216    /// recognized by reqwest.
217    ///
218    /// # Example
219    ///
220    /// ```
221    /// # extern crate reqwest;
222    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
223    /// let client = reqwest::Client::builder()
224    ///     .proxy(reqwest::Proxy::all("http://pro.xy")?)
225    ///     .build()?;
226    /// # Ok(())
227    /// # }
228    /// # fn main() {}
229    /// ```
230    pub fn all<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> {
231        Ok(Proxy::new(Intercept::All(proxy_scheme.into_proxy()?)))
232    }
233
234    /// Provide a custom function to determine what traffic to proxy to where.
235    ///
236    /// # Example
237    ///
238    /// ```
239    /// # extern crate reqwest;
240    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
241    /// let target = reqwest::Url::parse("https://my.prox")?;
242    /// let client = reqwest::Client::builder()
243    ///     .proxy(reqwest::Proxy::custom(move |url| {
244    ///         if url.host_str() == Some("hyper.rs") {
245    ///             Some(target.clone())
246    ///         } else {
247    ///             None
248    ///         }
249    ///     }))
250    ///     .build()?;
251    /// # Ok(())
252    /// # }
253    /// # fn main() {}
254    /// ```
255    pub fn custom<F, U: IntoProxy>(fun: F) -> Proxy
256    where
257        F: Fn(&Url) -> Option<U> + Send + Sync + 'static,
258    {
259        Proxy::new(Intercept::Custom(Custom {
260            func: Arc::new(move |url| fun(url).map(IntoProxy::into_proxy)),
261            no_proxy: None,
262        }))
263    }
264
265    fn new(intercept: Intercept) -> Proxy {
266        Proxy {
267            extra: Extra {
268                auth: None,
269                misc: None,
270            },
271            intercept,
272            no_proxy: None,
273        }
274    }
275
276    /// Set the `Proxy-Authorization` header using Basic auth.
277    ///
278    /// # Example
279    ///
280    /// ```
281    /// # extern crate reqwest;
282    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
283    /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
284    ///     .basic_auth("Aladdin", "open sesame");
285    /// # Ok(())
286    /// # }
287    /// # fn main() {}
288    /// ```
289    pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy {
290        match self.intercept {
291            Intercept::All(ref mut s)
292            | Intercept::Http(ref mut s)
293            | Intercept::Https(ref mut s) => url_auth(s, username, password),
294            Intercept::Custom(_) => {
295                let header = encode_basic_auth(username, password);
296                self.extra.auth = Some(header);
297            }
298        }
299
300        self
301    }
302
303    /// Set the `Proxy-Authorization` header to a specified value.
304    ///
305    /// # Example
306    ///
307    /// ```
308    /// # extern crate reqwest;
309    /// # use reqwest::header::*;
310    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
311    /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
312    ///     .custom_http_auth(HeaderValue::from_static("justletmeinalreadyplease"));
313    /// # Ok(())
314    /// # }
315    /// # fn main() {}
316    /// ```
317    pub fn custom_http_auth(mut self, header_value: HeaderValue) -> Proxy {
318        self.extra.auth = Some(header_value);
319        self
320    }
321
322    /// Adds a Custom Headers to Proxy
323    /// Adds custom headers to this Proxy
324    ///
325    /// # Example
326    /// ```
327    /// # extern crate reqwest;
328    /// # use reqwest::header::*;
329    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
330    /// let mut headers = HeaderMap::new();
331    /// headers.insert(USER_AGENT, "reqwest".parse().unwrap());
332    /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
333    ///     .headers(headers);
334    /// # Ok(())
335    /// # }
336    /// # fn main() {}
337    /// ```
338    pub fn headers(mut self, headers: HeaderMap) -> Proxy {
339        match self.intercept {
340            Intercept::All(_) | Intercept::Http(_) | Intercept::Https(_) | Intercept::Custom(_) => {
341                self.extra.misc = Some(headers);
342            }
343        }
344
345        self
346    }
347
348    /// Adds a `No Proxy` exclusion list to this Proxy
349    ///
350    /// # Example
351    ///
352    /// ```
353    /// # extern crate reqwest;
354    /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
355    /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
356    ///     .no_proxy(reqwest::NoProxy::from_string("direct.tld, sub.direct2.tld"));
357    /// # Ok(())
358    /// # }
359    /// # fn main() {}
360    /// ```
361    pub fn no_proxy(mut self, no_proxy: Option<NoProxy>) -> Proxy {
362        self.no_proxy = no_proxy;
363        self
364    }
365
366    pub(crate) fn into_matcher(self) -> Matcher {
367        let Proxy {
368            intercept,
369            extra,
370            no_proxy,
371        } = self;
372
373        let maybe_has_http_auth;
374        let maybe_has_http_custom_headers;
375
376        let inner = match intercept {
377            Intercept::All(url) => {
378                maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth);
379                maybe_has_http_custom_headers =
380                    cache_maybe_has_http_custom_headers(&url, &extra.misc);
381                Matcher_::Util(
382                    matcher::Matcher::builder()
383                        .all(String::from(url))
384                        .no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or(""))
385                        .build(),
386                )
387            }
388            Intercept::Http(url) => {
389                maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth);
390                maybe_has_http_custom_headers =
391                    cache_maybe_has_http_custom_headers(&url, &extra.misc);
392                Matcher_::Util(
393                    matcher::Matcher::builder()
394                        .http(String::from(url))
395                        .no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or(""))
396                        .build(),
397                )
398            }
399            Intercept::Https(url) => {
400                maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth);
401                maybe_has_http_custom_headers =
402                    cache_maybe_has_http_custom_headers(&url, &extra.misc);
403                Matcher_::Util(
404                    matcher::Matcher::builder()
405                        .https(String::from(url))
406                        .no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or(""))
407                        .build(),
408                )
409            }
410            Intercept::Custom(mut custom) => {
411                maybe_has_http_auth = true; // never know
412                maybe_has_http_custom_headers = true;
413                custom.no_proxy = no_proxy;
414                Matcher_::Custom(custom)
415            }
416        };
417
418        Matcher {
419            inner,
420            extra,
421            maybe_has_http_auth,
422            maybe_has_http_custom_headers,
423        }
424    }
425
426    /*
427    pub(crate) fn maybe_has_http_auth(&self) -> bool {
428        match &self.intercept {
429            Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().is_some(),
430            // Custom *may* match 'http', so assume so.
431            Intercept::Custom(_) => true,
432            Intercept::System(system) => system
433                .get("http")
434                .and_then(|s| s.maybe_http_auth())
435                .is_some(),
436            Intercept::Https(_) => false,
437        }
438    }
439
440    pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
441        match &self.intercept {
442            Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().cloned(),
443            Intercept::System(system) => system
444                .get("http")
445                .and_then(|s| s.maybe_http_auth().cloned()),
446            Intercept::Custom(custom) => {
447                custom.call(uri).and_then(|s| s.maybe_http_auth().cloned())
448            }
449            Intercept::Https(_) => None,
450        }
451    }
452    */
453}
454
455fn cache_maybe_has_http_auth(url: &Url, extra: &Option<HeaderValue>) -> bool {
456    (url.scheme() == "http" || url.scheme() == "https")
457        && (url.username().len() > 0 || url.password().is_some() || extra.is_some())
458}
459
460fn cache_maybe_has_http_custom_headers(url: &Url, extra: &Option<HeaderMap>) -> bool {
461    (url.scheme() == "http" || url.scheme() == "https") && extra.is_some()
462}
463
464impl fmt::Debug for Proxy {
465    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
466        f.debug_tuple("Proxy")
467            .field(&self.intercept)
468            .field(&self.no_proxy)
469            .finish()
470    }
471}
472
473impl NoProxy {
474    /// Returns a new no-proxy configuration based on environment variables (or `None` if no variables are set)
475    /// see [self::NoProxy::from_string()] for the string format
476    pub fn from_env() -> Option<NoProxy> {
477        let raw = std::env::var("NO_PROXY")
478            .or_else(|_| std::env::var("no_proxy"))
479            .ok()?;
480
481        // Per the docs, this returns `None` if no environment variable is set. We can only reach
482        // here if an env var is set, so we return `Some(NoProxy::default)` if `from_string`
483        // returns None, which occurs with an empty string.
484        Some(Self::from_string(&raw).unwrap_or_default())
485    }
486
487    /// Returns a new no-proxy configuration based on a `no_proxy` string (or `None` if no variables
488    /// are set)
489    /// The rules are as follows:
490    /// * The environment variable `NO_PROXY` is checked, if it is not set, `no_proxy` is checked
491    /// * If neither environment variable is set, `None` is returned
492    /// * Entries are expected to be comma-separated (whitespace between entries is ignored)
493    /// * IP addresses (both IPv4 and IPv6) are allowed, as are optional subnet masks (by adding /size,
494    ///   for example "`192.168.1.0/24`").
495    /// * An entry "`*`" matches all hostnames (this is the only wildcard allowed)
496    /// * Any other entry is considered a domain name (and may contain a leading dot, for example `google.com`
497    ///   and `.google.com` are equivalent) and would match both that domain AND all subdomains.
498    ///
499    /// For example, if `"NO_PROXY=google.com, 192.168.1.0/24"` was set, all the following would match
500    /// (and therefore would bypass the proxy):
501    /// * `http://google.com/`
502    /// * `http://www.google.com/`
503    /// * `http://192.168.1.42/`
504    ///
505    /// The URL `http://notgoogle.com/` would not match.
506    pub fn from_string(no_proxy_list: &str) -> Option<Self> {
507        // lazy parsed, to not make the type public in hyper-util
508        Some(NoProxy {
509            inner: no_proxy_list.into(),
510        })
511    }
512}
513
514impl Matcher {
515    pub(crate) fn system() -> Self {
516        Self {
517            inner: Matcher_::Util(matcher::Matcher::from_system()),
518            extra: Extra {
519                auth: None,
520                misc: None,
521            },
522            // maybe env vars have auth!
523            maybe_has_http_auth: true,
524            maybe_has_http_custom_headers: true,
525        }
526    }
527
528    pub(crate) fn intercept(&self, dst: &Uri) -> Option<Intercepted> {
529        let inner = match self.inner {
530            Matcher_::Util(ref m) => m.intercept(dst),
531            Matcher_::Custom(ref c) => c.call(dst),
532        };
533
534        inner.map(|inner| Intercepted {
535            inner,
536            extra: self.extra.clone(),
537        })
538    }
539
540    /// Return whether this matcher might provide HTTP (not s) auth.
541    ///
542    /// This is very specific. If this proxy needs auth to be part of a Forward
543    /// request (instead of a tunnel), this should return true.
544    ///
545    /// If it's not sure, this should return true.
546    ///
547    /// This is meant as a hint to allow skipping a more expensive check
548    /// (calling `intercept()`) if it will never need auth when Forwarding.
549    pub(crate) fn maybe_has_http_auth(&self) -> bool {
550        self.maybe_has_http_auth
551    }
552
553    pub(crate) fn http_non_tunnel_basic_auth(&self, dst: &Uri) -> Option<HeaderValue> {
554        if let Some(proxy) = self.intercept(dst) {
555            let scheme = proxy.uri().scheme();
556            if scheme == Some(&Scheme::HTTP) || scheme == Some(&Scheme::HTTPS) {
557                return proxy.basic_auth().cloned();
558            }
559        }
560
561        None
562    }
563
564    pub(crate) fn maybe_has_http_custom_headers(&self) -> bool {
565        self.maybe_has_http_custom_headers
566    }
567
568    pub(crate) fn http_non_tunnel_custom_headers(&self, dst: &Uri) -> Option<HeaderMap> {
569        if let Some(proxy) = self.intercept(dst) {
570            let scheme = proxy.uri().scheme();
571            if scheme == Some(&Scheme::HTTP) || scheme == Some(&Scheme::HTTPS) {
572                return proxy.custom_headers().cloned();
573            }
574        }
575
576        None
577    }
578}
579
580impl fmt::Debug for Matcher {
581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582        match self.inner {
583            Matcher_::Util(ref m) => m.fmt(f),
584            Matcher_::Custom(ref m) => m.fmt(f),
585        }
586    }
587}
588
589impl Intercepted {
590    pub(crate) fn uri(&self) -> &http::Uri {
591        self.inner.uri()
592    }
593
594    pub(crate) fn basic_auth(&self) -> Option<&HeaderValue> {
595        if let Some(ref val) = self.extra.auth {
596            return Some(val);
597        }
598        self.inner.basic_auth()
599    }
600
601    pub(crate) fn custom_headers(&self) -> Option<&HeaderMap> {
602        if let Some(ref val) = self.extra.misc {
603            return Some(val);
604        }
605        None
606    }
607
608    #[cfg(feature = "socks")]
609    pub(crate) fn raw_auth(&self) -> Option<(&str, &str)> {
610        self.inner.raw_auth()
611    }
612}
613
614impl fmt::Debug for Intercepted {
615    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616        self.inner.uri().fmt(f)
617    }
618}
619
620/*
621impl ProxyScheme {
622    /// Use a username and password when connecting to the proxy server
623    fn with_basic_auth<T: Into<String>, U: Into<String>>(
624        mut self,
625        username: T,
626        password: U,
627    ) -> Self {
628        self.set_basic_auth(username, password);
629        self
630    }
631
632    fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
633        match *self {
634            ProxyScheme::Http { ref mut auth, .. } => {
635                let header = encode_basic_auth(&username.into(), &password.into());
636                *auth = Some(header);
637            }
638            ProxyScheme::Https { ref mut auth, .. } => {
639                let header = encode_basic_auth(&username.into(), &password.into());
640                *auth = Some(header);
641            }
642            #[cfg(feature = "socks")]
643            ProxyScheme::Socks4 { .. } => {
644                panic!("Socks4 is not supported for this method")
645            }
646            #[cfg(feature = "socks")]
647            ProxyScheme::Socks5 { ref mut auth, .. } => {
648                *auth = Some((username.into(), password.into()));
649            }
650        }
651    }
652
653    fn set_custom_http_auth(&mut self, header_value: HeaderValue) {
654        match *self {
655            ProxyScheme::Http { ref mut auth, .. } => {
656                *auth = Some(header_value);
657            }
658            ProxyScheme::Https { ref mut auth, .. } => {
659                *auth = Some(header_value);
660            }
661            #[cfg(feature = "socks")]
662            ProxyScheme::Socks4 { .. } => {
663                panic!("Socks4 is not supported for this method")
664            }
665            #[cfg(feature = "socks")]
666            ProxyScheme::Socks5 { .. } => {
667                panic!("Socks5 is not supported for this method")
668            }
669        }
670    }
671
672    fn set_custom_headers(&mut self, headers: HeaderMap) {
673        match *self {
674            ProxyScheme::Http { ref mut misc, .. } => {
675                misc.get_or_insert_with(HeaderMap::new).extend(headers)
676            }
677            ProxyScheme::Https { ref mut misc, .. } => {
678                misc.get_or_insert_with(HeaderMap::new).extend(headers)
679            }
680            #[cfg(feature = "socks")]
681            ProxyScheme::Socks4 { .. } => {
682                panic!("Socks4 is not supported for this method")
683            }
684            #[cfg(feature = "socks")]
685            ProxyScheme::Socks5 { .. } => {
686                panic!("Socks5 is not supported for this method")
687            }
688        }
689    }
690
691    fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
692        match self {
693            ProxyScheme::Http { ref mut auth, .. } => {
694                if auth.is_none() {
695                    *auth = update.clone();
696                }
697            }
698            ProxyScheme::Https { ref mut auth, .. } => {
699                if auth.is_none() {
700                    *auth = update.clone();
701                }
702            }
703            #[cfg(feature = "socks")]
704            ProxyScheme::Socks4 { .. } => {}
705            #[cfg(feature = "socks")]
706            ProxyScheme::Socks5 { .. } => {}
707        }
708
709        self
710    }
711
712    /// Convert a URL into a proxy scheme
713    ///
714    /// Supported schemes: HTTP, HTTPS, (SOCKS4, SOCKS5, SOCKS5H if `socks` feature is enabled).
715    // Private for now...
716    fn parse(url: Url) -> crate::Result<Self> {
717        use url::Position;
718
719        // Resolve URL to a host and port
720        #[cfg(feature = "socks")]
721        let to_addr = || {
722            let addrs = url
723                .socket_addrs(|| match url.scheme() {
724                    "socks4" | "socks4a" | "socks5" | "socks5h" => Some(1080),
725                    _ => None,
726                })
727                .map_err(crate::error::builder)?;
728            addrs
729                .into_iter()
730                .next()
731                .ok_or_else(|| crate::error::builder("unknown proxy scheme"))
732        };
733
734        let mut scheme = match url.scheme() {
735            "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
736            "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
737            #[cfg(feature = "socks")]
738            "socks4" => Self::socks4(to_addr()?)?,
739            #[cfg(feature = "socks")]
740            "socks4a" => Self::socks4a(to_addr()?)?,
741            #[cfg(feature = "socks")]
742            "socks5" => Self::socks5(to_addr()?)?,
743            #[cfg(feature = "socks")]
744            "socks5h" => Self::socks5h(to_addr()?)?,
745            _ => return Err(crate::error::builder("unknown proxy scheme")),
746        };
747
748        if let Some(pwd) = url.password() {
749            let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
750            let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
751            scheme = scheme.with_basic_auth(decoded_username, decoded_password);
752        }
753
754        Ok(scheme)
755    }
756}
757*/
758
759#[derive(Clone, Debug)]
760enum Intercept {
761    All(Url),
762    Http(Url),
763    Https(Url),
764    Custom(Custom),
765}
766
767fn url_auth(url: &mut Url, username: &str, password: &str) {
768    url.set_username(username).expect("is a base");
769    url.set_password(Some(password)).expect("is a base");
770}
771
772#[derive(Clone)]
773struct Custom {
774    func: Arc<dyn Fn(&Url) -> Option<crate::Result<Url>> + Send + Sync + 'static>,
775    no_proxy: Option<NoProxy>,
776}
777
778impl Custom {
779    fn call(&self, uri: &http::Uri) -> Option<matcher::Intercept> {
780        let url = format!(
781            "{}://{}{}{}",
782            uri.scheme()?,
783            uri.host()?,
784            uri.port().map_or("", |_| ":"),
785            uri.port().map_or(String::new(), |p| p.to_string())
786        )
787        .parse()
788        .expect("should be valid Url");
789
790        (self.func)(&url)
791            .and_then(|result| result.ok())
792            .and_then(|target| {
793                let m = matcher::Matcher::builder()
794                    .all(String::from(target))
795                    .build();
796
797                m.intercept(uri)
798            })
799        //.map(|scheme| scheme.if_no_auth(&self.auth))
800    }
801}
802
803impl fmt::Debug for Custom {
804    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
805        f.write_str("_")
806    }
807}
808
809pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
810    crate::util::basic_auth(username, Some(password))
811}
812
813#[cfg(test)]
814mod tests {
815    use super::*;
816
817    fn url(s: &str) -> http::Uri {
818        s.parse().unwrap()
819    }
820
821    fn intercepted_uri(p: &Matcher, s: &str) -> Uri {
822        p.intercept(&s.parse().unwrap()).unwrap().uri().clone()
823    }
824
825    #[test]
826    fn test_http() {
827        let target = "http://example.domain/";
828        let p = Proxy::http(target).unwrap().into_matcher();
829
830        let http = "http://hyper.rs";
831        let other = "https://hyper.rs";
832
833        assert_eq!(intercepted_uri(&p, http), target);
834        assert!(p.intercept(&url(other)).is_none());
835    }
836
837    #[test]
838    fn test_https() {
839        let target = "http://example.domain/";
840        let p = Proxy::https(target).unwrap().into_matcher();
841
842        let http = "http://hyper.rs";
843        let other = "https://hyper.rs";
844
845        assert!(p.intercept(&url(http)).is_none());
846        assert_eq!(intercepted_uri(&p, other), target);
847    }
848
849    #[test]
850    fn test_all() {
851        let target = "http://example.domain/";
852        let p = Proxy::all(target).unwrap().into_matcher();
853
854        let http = "http://hyper.rs";
855        let https = "https://hyper.rs";
856        // no longer supported
857        //let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
858
859        assert_eq!(intercepted_uri(&p, http), target);
860        assert_eq!(intercepted_uri(&p, https), target);
861        //assert_eq!(intercepted_uri(&p, other), target);
862    }
863
864    #[test]
865    fn test_custom() {
866        let target1 = "http://example.domain/";
867        let target2 = "https://example.domain/";
868        let p = Proxy::custom(move |url| {
869            if url.host_str() == Some("hyper.rs") {
870                target1.parse().ok()
871            } else if url.scheme() == "http" {
872                target2.parse().ok()
873            } else {
874                None::<Url>
875            }
876        })
877        .into_matcher();
878
879        let http = "http://seanmonstar.com";
880        let https = "https://hyper.rs";
881        let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
882
883        assert_eq!(intercepted_uri(&p, http), target2);
884        assert_eq!(intercepted_uri(&p, https), target1);
885        assert!(p.intercept(&url(other)).is_none());
886    }
887
888    #[test]
889    fn test_standard_with_custom_auth_header() {
890        let target = "http://example.domain/";
891        let p = Proxy::all(target)
892            .unwrap()
893            .custom_http_auth(http::HeaderValue::from_static("testme"))
894            .into_matcher();
895
896        let got = p.intercept(&url("http://anywhere.local")).unwrap();
897        let auth = got.basic_auth().unwrap();
898        assert_eq!(auth, "testme");
899    }
900
901    #[test]
902    fn test_custom_with_custom_auth_header() {
903        let target = "http://example.domain/";
904        let p = Proxy::custom(move |_| target.parse::<Url>().ok())
905            .custom_http_auth(http::HeaderValue::from_static("testme"))
906            .into_matcher();
907
908        let got = p.intercept(&url("http://anywhere.local")).unwrap();
909        let auth = got.basic_auth().unwrap();
910        assert_eq!(auth, "testme");
911    }
912
913    #[test]
914    fn test_maybe_has_http_auth() {
915        let m = Proxy::all("https://letme:in@yo.local")
916            .unwrap()
917            .into_matcher();
918        assert!(m.maybe_has_http_auth(), "https forwards");
919
920        let m = Proxy::all("http://letme:in@yo.local")
921            .unwrap()
922            .into_matcher();
923        assert!(m.maybe_has_http_auth(), "http forwards");
924
925        let m = Proxy::all("http://:in@yo.local").unwrap().into_matcher();
926        assert!(m.maybe_has_http_auth(), "http forwards with empty username");
927
928        let m = Proxy::all("http://letme:@yo.local").unwrap().into_matcher();
929        assert!(m.maybe_has_http_auth(), "http forwards with empty password");
930    }
931
932    #[test]
933    fn test_socks_proxy_default_port() {
934        {
935            let m = Proxy::all("socks5://example.com").unwrap().into_matcher();
936
937            let http = "http://hyper.rs";
938            let https = "https://hyper.rs";
939
940            assert_eq!(intercepted_uri(&m, http).port_u16(), Some(1080));
941            assert_eq!(intercepted_uri(&m, https).port_u16(), Some(1080));
942
943            // custom port
944            let m = Proxy::all("socks5://example.com:1234")
945                .unwrap()
946                .into_matcher();
947
948            assert_eq!(intercepted_uri(&m, http).port_u16(), Some(1234));
949            assert_eq!(intercepted_uri(&m, https).port_u16(), Some(1234));
950        }
951    }
952}