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