Coverage Report

Created: 2026-01-30 06:08

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