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(proxy) = system.get("http") {
283 match proxy {
284 ProxyScheme::Http { auth, .. } => auth.is_some(),
285 _ => false,
286 }
287 } else {
288 false
289 }
290 }
291 _ => false,
292 }
293 }
294
http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue>295 pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
296 match self.intercept {
297 Intercept::All(ProxyScheme::Http { ref auth, .. })
298 | Intercept::Http(ProxyScheme::Http { ref auth, .. }) => auth.clone(),
299 Intercept::System(ref system) => {
300 if let Some(proxy) = system.get("http") {
301 match proxy {
302 ProxyScheme::Http { auth, .. } => auth.clone(),
303 _ => None,
304 }
305 } else {
306 None
307 }
308 }
309 Intercept::Custom(ref custom) => custom.call(uri).and_then(|scheme| match scheme {
310 ProxyScheme::Http { auth, .. } => auth,
311 ProxyScheme::Https { auth, .. } => auth,
312 #[cfg(feature = "socks")]
313 _ => None,
314 }),
315 _ => None,
316 }
317 }
318
intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme>319 pub(crate) fn intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
320 match self.intercept {
321 Intercept::All(ref u) => Some(u.clone()),
322 Intercept::Http(ref u) => {
323 if uri.scheme() == "http" {
324 Some(u.clone())
325 } else {
326 None
327 }
328 }
329 Intercept::Https(ref u) => {
330 if uri.scheme() == "https" {
331 Some(u.clone())
332 } else {
333 None
334 }
335 }
336 Intercept::System(ref map) => {
337 let in_no_proxy = self
338 .no_proxy
339 .as_ref()
340 .map_or(false, |np| np.contains(uri.host()));
341 if in_no_proxy {
342 None
343 } else {
344 map.get(uri.scheme()).cloned()
345 }
346 }
347 Intercept::Custom(ref custom) => custom.call(uri),
348 }
349 }
350
is_match<D: Dst>(&self, uri: &D) -> bool351 pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool {
352 match self.intercept {
353 Intercept::All(_) => true,
354 Intercept::Http(_) => uri.scheme() == "http",
355 Intercept::Https(_) => uri.scheme() == "https",
356 Intercept::System(ref map) => map.contains_key(uri.scheme()),
357 Intercept::Custom(ref custom) => custom.call(uri).is_some(),
358 }
359 }
360 }
361
362 impl fmt::Debug for Proxy {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result363 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
364 f.debug_tuple("Proxy")
365 .field(&self.intercept)
366 .field(&self.no_proxy)
367 .finish()
368 }
369 }
370
371 impl NoProxy {
372 /// Returns a new no proxy configration if the NO_PROXY/no_proxy environment variable is set.
373 /// Returns None otherwise
new() -> Option<Self>374 fn new() -> Option<Self> {
375 let raw = env::var("NO_PROXY")
376 .or_else(|_| env::var("no_proxy"))
377 .unwrap_or_default();
378 if raw.is_empty() {
379 return None;
380 }
381 let mut ips = Vec::new();
382 let mut domains = Vec::new();
383 let parts = raw.split(',');
384 for part in parts {
385 match part.parse::<IpNet>() {
386 // If we can parse an IP net or address, then use it, otherwise, assume it is a domain
387 Ok(ip) => ips.push(Ip::Network(ip)),
388 Err(_) => match part.parse::<IpAddr>() {
389 Ok(addr) => ips.push(Ip::Address(addr)),
390 Err(_) => domains.push(part.to_owned()),
391 },
392 }
393 }
394 Some(NoProxy {
395 ips: IpMatcher(ips),
396 domains: DomainMatcher(domains),
397 })
398 }
399
contains(&self, host: &str) -> bool400 fn contains(&self, host: &str) -> bool {
401 // According to RFC3986, raw IPv6 hosts will be wrapped in []. So we need to strip those off
402 // the end in order to parse correctly
403 let host = if host.starts_with('[') {
404 let x: &[_] = &['[', ']'];
405 host.trim_matches(x)
406 } else {
407 host
408 };
409 match host.parse::<IpAddr>() {
410 // If we can parse an IP addr, then use it, otherwise, assume it is a domain
411 Ok(ip) => self.ips.contains(ip),
412 Err(_) => self.domains.contains(host),
413 }
414 }
415 }
416
417 impl IpMatcher {
contains(&self, addr: IpAddr) -> bool418 fn contains(&self, addr: IpAddr) -> bool {
419 for ip in self.0.iter() {
420 match ip {
421 Ip::Address(address) => {
422 if &addr == address {
423 return true;
424 }
425 }
426 Ip::Network(net) => {
427 if net.contains(&addr) {
428 return true;
429 }
430 }
431 }
432 }
433 false
434 }
435 }
436
437 impl DomainMatcher {
contains(&self, domain: &str) -> bool438 fn contains(&self, domain: &str) -> bool {
439 for d in self.0.iter() {
440 // First check for a "wildcard" domain match. A single "." will match anything.
441 // Otherwise, check that the domains are equal
442 if (d.starts_with('.') && domain.ends_with(d.get(1..).unwrap_or_default()))
443 || d == domain
444 {
445 return true;
446 }
447 }
448 false
449 }
450 }
451
452 impl ProxyScheme {
453 // To start conservative, keep builders private for now.
454
455 /// Proxy traffic via the specified URL over HTTP
http(host: &str) -> crate::Result<Self>456 fn http(host: &str) -> crate::Result<Self> {
457 Ok(ProxyScheme::Http {
458 auth: None,
459 host: host.parse().map_err(crate::error::builder)?,
460 })
461 }
462
463 /// Proxy traffic via the specified URL over HTTPS
https(host: &str) -> crate::Result<Self>464 fn https(host: &str) -> crate::Result<Self> {
465 Ok(ProxyScheme::Https {
466 auth: None,
467 host: host.parse().map_err(crate::error::builder)?,
468 })
469 }
470
471 /// Proxy traffic via the specified socket address over SOCKS5
472 ///
473 /// # Note
474 ///
475 /// Current SOCKS5 support is provided via blocking IO.
476 #[cfg(feature = "socks")]
socks5(addr: SocketAddr) -> crate::Result<Self>477 fn socks5(addr: SocketAddr) -> crate::Result<Self> {
478 Ok(ProxyScheme::Socks5 {
479 addr,
480 auth: None,
481 remote_dns: false,
482 })
483 }
484
485 /// Proxy traffic via the specified socket address over SOCKS5H
486 ///
487 /// This differs from SOCKS5 in that DNS resolution is also performed via the proxy.
488 ///
489 /// # Note
490 ///
491 /// Current SOCKS5 support is provided via blocking IO.
492 #[cfg(feature = "socks")]
socks5h(addr: SocketAddr) -> crate::Result<Self>493 fn socks5h(addr: SocketAddr) -> crate::Result<Self> {
494 Ok(ProxyScheme::Socks5 {
495 addr,
496 auth: None,
497 remote_dns: true,
498 })
499 }
500
501 /// 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, ) -> Self502 fn with_basic_auth<T: Into<String>, U: Into<String>>(
503 mut self,
504 username: T,
505 password: U,
506 ) -> Self {
507 self.set_basic_auth(username, password);
508 self
509 }
510
set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U)511 fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
512 match *self {
513 ProxyScheme::Http { ref mut auth, .. } => {
514 let header = encode_basic_auth(&username.into(), &password.into());
515 *auth = Some(header);
516 }
517 ProxyScheme::Https { ref mut auth, .. } => {
518 let header = encode_basic_auth(&username.into(), &password.into());
519 *auth = Some(header);
520 }
521 #[cfg(feature = "socks")]
522 ProxyScheme::Socks5 { ref mut auth, .. } => {
523 *auth = Some((username.into(), password.into()));
524 }
525 }
526 }
527
if_no_auth(mut self, update: &Option<HeaderValue>) -> Self528 fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
529 match self {
530 ProxyScheme::Http { ref mut auth, .. } => {
531 if auth.is_none() {
532 *auth = update.clone();
533 }
534 }
535 ProxyScheme::Https { ref mut auth, .. } => {
536 if auth.is_none() {
537 *auth = update.clone();
538 }
539 }
540 #[cfg(feature = "socks")]
541 ProxyScheme::Socks5 { .. } => {}
542 }
543
544 self
545 }
546
547 /// Convert a URL into a proxy scheme
548 ///
549 /// Supported schemes: HTTP, HTTPS, (SOCKS5, SOCKS5H if `socks` feature is enabled).
550 // Private for now...
parse(url: Url) -> crate::Result<Self>551 fn parse(url: Url) -> crate::Result<Self> {
552 use url::Position;
553
554 // Resolve URL to a host and port
555 #[cfg(feature = "socks")]
556 let to_addr = || {
557 let addrs = url
558 .socket_addrs(|| match url.scheme() {
559 "socks5" | "socks5h" => Some(1080),
560 _ => None,
561 })
562 .map_err(crate::error::builder)?;
563 addrs
564 .into_iter()
565 .next()
566 .ok_or_else(|| crate::error::builder("unknown proxy scheme"))
567 };
568
569 let mut scheme = match url.scheme() {
570 "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
571 "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
572 #[cfg(feature = "socks")]
573 "socks5" => Self::socks5(to_addr()?)?,
574 #[cfg(feature = "socks")]
575 "socks5h" => Self::socks5h(to_addr()?)?,
576 _ => return Err(crate::error::builder("unknown proxy scheme")),
577 };
578
579 if let Some(pwd) = url.password() {
580 let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
581 let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
582 scheme = scheme.with_basic_auth(decoded_username, decoded_password);
583 }
584
585 Ok(scheme)
586 }
587
588 #[cfg(test)]
scheme(&self) -> &str589 fn scheme(&self) -> &str {
590 match self {
591 ProxyScheme::Http { .. } => "http",
592 ProxyScheme::Https { .. } => "https",
593 #[cfg(feature = "socks")]
594 ProxyScheme::Socks5 { .. } => "socks5",
595 }
596 }
597
598 #[cfg(test)]
host(&self) -> &str599 fn host(&self) -> &str {
600 match self {
601 ProxyScheme::Http { host, .. } => host.as_str(),
602 ProxyScheme::Https { host, .. } => host.as_str(),
603 #[cfg(feature = "socks")]
604 ProxyScheme::Socks5 { .. } => panic!("socks5"),
605 }
606 }
607 }
608
609 impl fmt::Debug for ProxyScheme {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result610 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
611 match self {
612 ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{}", host),
613 ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{}", host),
614 #[cfg(feature = "socks")]
615 ProxyScheme::Socks5 {
616 addr,
617 auth: _auth,
618 remote_dns,
619 } => {
620 let h = if *remote_dns { "h" } else { "" };
621 write!(f, "socks5{}://{}", h, addr)
622 }
623 }
624 }
625 }
626
627 type SystemProxyMap = HashMap<String, ProxyScheme>;
628 type RegistryProxyValues = (u32, String);
629
630 #[derive(Clone, Debug)]
631 enum Intercept {
632 All(ProxyScheme),
633 Http(ProxyScheme),
634 Https(ProxyScheme),
635 System(Arc<SystemProxyMap>),
636 Custom(Custom),
637 }
638
639 impl Intercept {
set_basic_auth(&mut self, username: &str, password: &str)640 fn set_basic_auth(&mut self, username: &str, password: &str) {
641 match self {
642 Intercept::All(ref mut s)
643 | Intercept::Http(ref mut s)
644 | Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
645 Intercept::System(_) => unimplemented!(),
646 Intercept::Custom(ref mut custom) => {
647 let header = encode_basic_auth(username, password);
648 custom.auth = Some(header);
649 }
650 }
651 }
652 }
653
654 #[derive(Clone)]
655 struct Custom {
656 // This auth only applies if the returned ProxyScheme doesn't have an auth...
657 auth: Option<HeaderValue>,
658 func: Arc<dyn Fn(&Url) -> Option<crate::Result<ProxyScheme>> + Send + Sync + 'static>,
659 }
660
661 impl Custom {
call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme>662 fn call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
663 let url = format!(
664 "{}://{}{}{}",
665 uri.scheme(),
666 uri.host(),
667 uri.port().map(|_| ":").unwrap_or(""),
668 uri.port().map(|p| p.to_string()).unwrap_or_default()
669 )
670 .parse()
671 .expect("should be valid Url");
672
673 (self.func)(&url)
674 .and_then(|result| result.ok())
675 .map(|scheme| scheme.if_no_auth(&self.auth))
676 }
677 }
678
679 impl fmt::Debug for Custom {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result680 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
681 f.write_str("_")
682 }
683 }
684
encode_basic_auth(username: &str, password: &str) -> HeaderValue685 pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
686 let val = format!("{}:{}", username, password);
687 let mut header = format!("Basic {}", base64::encode(&val))
688 .parse::<HeaderValue>()
689 .expect("base64 is always valid HeaderValue");
690 header.set_sensitive(true);
691 header
692 }
693
694 /// A helper trait to allow testing `Proxy::intercept` without having to
695 /// construct `hyper::client::connect::Destination`s.
696 pub(crate) trait Dst {
scheme(&self) -> &str697 fn scheme(&self) -> &str;
host(&self) -> &str698 fn host(&self) -> &str;
port(&self) -> Option<u16>699 fn port(&self) -> Option<u16>;
700 }
701
702 #[doc(hidden)]
703 impl Dst for Uri {
scheme(&self) -> &str704 fn scheme(&self) -> &str {
705 self.scheme().expect("Uri should have a scheme").as_str()
706 }
707
host(&self) -> &str708 fn host(&self) -> &str {
709 Uri::host(self).expect("<Uri as Dst>::host should have a str")
710 }
711
port(&self) -> Option<u16>712 fn port(&self) -> Option<u16> {
713 self.port().map(|p| p.as_u16())
714 }
715 }
716
717 lazy_static! {
718 static ref SYS_PROXIES: Arc<SystemProxyMap> = Arc::new(get_sys_proxies(get_from_registry()));
719 }
720
721 /// Get system proxies information.
722 ///
723 /// It can only support Linux, Unix like, and windows system. Note that it will always
724 /// return a HashMap, even if something runs into error when find registry information in
725 /// Windows system. Note that invalid proxy url in the system setting will be ignored.
726 ///
727 /// Returns:
728 /// System proxies information as a hashmap like
729 /// {"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, >, ) -> SystemProxyMap730 fn get_sys_proxies(
731 #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] registry_values: Option<
732 RegistryProxyValues,
733 >,
734 ) -> SystemProxyMap {
735 let proxies = get_from_environment();
736
737 // TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed
738 #[cfg(target_os = "windows")]
739 {
740 if proxies.is_empty() {
741 // don't care errors if can't get proxies from registry, just return an empty HashMap.
742 if let Some(registry_values) = registry_values {
743 return parse_registry_values(registry_values);
744 }
745 }
746 }
747 proxies
748 }
749
insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool750 fn insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool {
751 if let Ok(valid_addr) = addr.into_proxy_scheme() {
752 proxies.insert(scheme.into(), valid_addr);
753 true
754 } else {
755 false
756 }
757 }
758
get_from_environment() -> SystemProxyMap759 fn get_from_environment() -> SystemProxyMap {
760 let mut proxies = HashMap::new();
761
762 if is_cgi() {
763 if log::log_enabled!(log::Level::Warn) && env::var_os("HTTP_PROXY").is_some() {
764 log::warn!("HTTP_PROXY environment variable ignored in CGI");
765 }
766 } else if !insert_from_env(&mut proxies, "http", "HTTP_PROXY") {
767 insert_from_env(&mut proxies, "http", "http_proxy");
768 }
769
770 if !insert_from_env(&mut proxies, "https", "HTTPS_PROXY") {
771 insert_from_env(&mut proxies, "https", "https_proxy");
772 }
773
774 proxies
775 }
776
insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool777 fn insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool {
778 if let Ok(val) = env::var(var) {
779 insert_proxy(proxies, scheme, val)
780 } else {
781 false
782 }
783 }
784
785 /// Check if we are being executed in a CGI context.
786 ///
787 /// If so, a malicious client can send the `Proxy:` header, and it will
788 /// be in the `HTTP_PROXY` env var. So we don't use it :)
is_cgi() -> bool789 fn is_cgi() -> bool {
790 env::var_os("REQUEST_METHOD").is_some()
791 }
792
793 #[cfg(target_os = "windows")]
get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>>794 fn get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>> {
795 let hkcu = RegKey::predef(HKEY_CURRENT_USER);
796 let internet_setting: RegKey =
797 hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
798 // ensure the proxy is enable, if the value doesn't exist, an error will returned.
799 let proxy_enable: u32 = internet_setting.get_value("ProxyEnable")?;
800 let proxy_server: String = internet_setting.get_value("ProxyServer")?;
801
802 Ok((proxy_enable, proxy_server))
803 }
804
805 #[cfg(target_os = "windows")]
get_from_registry() -> Option<RegistryProxyValues>806 fn get_from_registry() -> Option<RegistryProxyValues> {
807 get_from_registry_impl().ok()
808 }
809
810 #[cfg(not(target_os = "windows"))]
get_from_registry() -> Option<RegistryProxyValues>811 fn get_from_registry() -> Option<RegistryProxyValues> {
812 None
813 }
814
815 #[cfg(target_os = "windows")]
parse_registry_values_impl( registry_values: RegistryProxyValues, ) -> Result<SystemProxyMap, Box<dyn Error>>816 fn parse_registry_values_impl(
817 registry_values: RegistryProxyValues,
818 ) -> Result<SystemProxyMap, Box<dyn Error>> {
819 let (proxy_enable, proxy_server) = registry_values;
820
821 if proxy_enable == 0 {
822 return Ok(HashMap::new());
823 }
824
825 let mut proxies = HashMap::new();
826 if proxy_server.contains("=") {
827 // per-protocol settings.
828 for p in proxy_server.split(";") {
829 let protocol_parts: Vec<&str> = p.split("=").collect();
830 match protocol_parts.as_slice() {
831 [protocol, address] => {
832 // If address doesn't specify an explicit protocol as protocol://address
833 // then default to HTTP
834 let address = if extract_type_prefix(*address).is_some() {
835 String::from(*address)
836 } else {
837 format!("http://{}", address)
838 };
839
840 insert_proxy(&mut proxies, *protocol, address);
841 }
842 _ => {
843 // Contains invalid protocol setting, just break the loop
844 // And make proxies to be empty.
845 proxies.clear();
846 break;
847 }
848 }
849 }
850 } else {
851 if let Some(scheme) = extract_type_prefix(&proxy_server) {
852 // Explicit protocol has been specified
853 insert_proxy(&mut proxies, scheme, proxy_server.to_owned());
854 } else {
855 // No explicit protocol has been specified, default to HTTP
856 insert_proxy(&mut proxies, "http", format!("http://{}", proxy_server));
857 insert_proxy(&mut proxies, "https", format!("http://{}", proxy_server));
858 }
859 }
860 Ok(proxies)
861 }
862
863 /// Extract the protocol from the given address, if present
864 /// For example, "https://example.com" will return Some("https")
865 #[cfg(target_os = "windows")]
extract_type_prefix(address: &str) -> Option<&str>866 fn extract_type_prefix(address: &str) -> Option<&str> {
867 if let Some(indice) = address.find("://") {
868 if indice == 0 {
869 None
870 } else {
871 let prefix = &address[..indice];
872 let contains_banned = prefix.contains(|c| c == ':' || c == '/');
873
874 if !contains_banned {
875 Some(prefix)
876 } else {
877 None
878 }
879 }
880 } else {
881 None
882 }
883 }
884
885 #[cfg(target_os = "windows")]
parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap886 fn parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap {
887 parse_registry_values_impl(registry_values).unwrap_or(HashMap::new())
888 }
889
890 #[cfg(test)]
891 mod tests {
892 use super::*;
893 use lazy_static::lazy_static;
894 use std::sync::Mutex;
895
896 impl Dst for Url {
scheme(&self) -> &str897 fn scheme(&self) -> &str {
898 Url::scheme(self)
899 }
900
host(&self) -> &str901 fn host(&self) -> &str {
902 Url::host_str(self).expect("<Url as Dst>::host should have a str")
903 }
904
port(&self) -> Option<u16>905 fn port(&self) -> Option<u16> {
906 Url::port(self)
907 }
908 }
909
url(s: &str) -> Url910 fn url(s: &str) -> Url {
911 s.parse().unwrap()
912 }
913
intercepted_uri(p: &Proxy, s: &str) -> Uri914 fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
915 let (scheme, host) = match p.intercept(&url(s)).unwrap() {
916 ProxyScheme::Http { host, .. } => ("http", host),
917 ProxyScheme::Https { host, .. } => ("https", host),
918 #[cfg(feature = "socks")]
919 _ => panic!("intercepted as socks"),
920 };
921 http::Uri::builder()
922 .scheme(scheme)
923 .authority(host)
924 .path_and_query("/")
925 .build()
926 .expect("intercepted_uri")
927 }
928
929 #[test]
test_http()930 fn test_http() {
931 let target = "http://example.domain/";
932 let p = Proxy::http(target).unwrap();
933
934 let http = "http://hyper.rs";
935 let other = "https://hyper.rs";
936
937 assert_eq!(intercepted_uri(&p, http), target);
938 assert!(p.intercept(&url(other)).is_none());
939 }
940
941 #[test]
test_https()942 fn test_https() {
943 let target = "http://example.domain/";
944 let p = Proxy::https(target).unwrap();
945
946 let http = "http://hyper.rs";
947 let other = "https://hyper.rs";
948
949 assert!(p.intercept(&url(http)).is_none());
950 assert_eq!(intercepted_uri(&p, other), target);
951 }
952
953 #[test]
test_all()954 fn test_all() {
955 let target = "http://example.domain/";
956 let p = Proxy::all(target).unwrap();
957
958 let http = "http://hyper.rs";
959 let https = "https://hyper.rs";
960 let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
961
962 assert_eq!(intercepted_uri(&p, http), target);
963 assert_eq!(intercepted_uri(&p, https), target);
964 assert_eq!(intercepted_uri(&p, other), target);
965 }
966
967 #[test]
test_custom()968 fn test_custom() {
969 let target1 = "http://example.domain/";
970 let target2 = "https://example.domain/";
971 let p = Proxy::custom(move |url| {
972 if url.host_str() == Some("hyper.rs") {
973 target1.parse().ok()
974 } else if url.scheme() == "http" {
975 target2.parse().ok()
976 } else {
977 None::<Url>
978 }
979 });
980
981 let http = "http://seanmonstar.com";
982 let https = "https://hyper.rs";
983 let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
984
985 assert_eq!(intercepted_uri(&p, http), target2);
986 assert_eq!(intercepted_uri(&p, https), target1);
987 assert!(p.intercept(&url(other)).is_none());
988 }
989
990 #[test]
test_proxy_scheme_parse()991 fn test_proxy_scheme_parse() {
992 let ps = "http://foo:bar@localhost:1239".into_proxy_scheme().unwrap();
993
994 match ps {
995 ProxyScheme::Http { auth, host } => {
996 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
997 assert_eq!(host, "localhost:1239");
998 }
999 other => panic!("unexpected: {:?}", other),
1000 }
1001 }
1002
1003 #[test]
test_proxy_scheme_ip_address_default_http()1004 fn test_proxy_scheme_ip_address_default_http() {
1005 let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap();
1006
1007 match ps {
1008 ProxyScheme::Http { auth, host } => {
1009 assert!(auth.is_none());
1010 assert_eq!(host, "192.168.1.1:8888");
1011 }
1012 other => panic!("unexpected: {:?}", other),
1013 }
1014 }
1015
1016 #[test]
test_proxy_scheme_parse_default_http_with_auth()1017 fn test_proxy_scheme_parse_default_http_with_auth() {
1018 // this should fail because `foo` is interpreted as the scheme and no host can be found
1019 let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap();
1020
1021 match ps {
1022 ProxyScheme::Http { auth, host } => {
1023 assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
1024 assert_eq!(host, "localhost:1239");
1025 }
1026 other => panic!("unexpected: {:?}", other),
1027 }
1028 }
1029
1030 // Smallest possible content for a mutex
1031 struct MutexInner;
1032
1033 lazy_static! {
1034 static ref ENVLOCK: Mutex<MutexInner> = Mutex::new(MutexInner);
1035 }
1036
1037 #[test]
test_get_sys_proxies_parsing()1038 fn test_get_sys_proxies_parsing() {
1039 // Stop other threads from modifying process-global ENV while we are.
1040 let _lock = ENVLOCK.lock();
1041 // save system setting first.
1042 let _g1 = env_guard("HTTP_PROXY");
1043 let _g2 = env_guard("http_proxy");
1044
1045 // Mock ENV, get the results, before doing assertions
1046 // to avoid assert! -> panic! -> Mutex Poisoned.
1047 let baseline_proxies = get_sys_proxies(None);
1048 // the system proxy setting url is invalid.
1049 env::set_var("http_proxy", "file://123465");
1050 let invalid_proxies = get_sys_proxies(None);
1051 // set valid proxy
1052 env::set_var("http_proxy", "127.0.0.1/");
1053 let valid_proxies = get_sys_proxies(None);
1054
1055 // reset user setting when guards drop
1056 drop(_g1);
1057 drop(_g2);
1058 // Let other threads run now
1059 drop(_lock);
1060
1061 assert_eq!(baseline_proxies.contains_key("http"), false);
1062 assert_eq!(invalid_proxies.contains_key("http"), false);
1063
1064 let p = &valid_proxies["http"];
1065 assert_eq!(p.scheme(), "http");
1066 assert_eq!(p.host(), "127.0.0.1");
1067 }
1068
1069 #[cfg(target_os = "windows")]
1070 #[test]
test_get_sys_proxies_registry_parsing()1071 fn test_get_sys_proxies_registry_parsing() {
1072 // Stop other threads from modifying process-global ENV while we are.
1073 let _lock = ENVLOCK.lock();
1074 // save system setting first.
1075 let _g1 = env_guard("HTTP_PROXY");
1076 let _g2 = env_guard("http_proxy");
1077
1078 // Mock ENV, get the results, before doing assertions
1079 // to avoid assert! -> panic! -> Mutex Poisoned.
1080 let baseline_proxies = get_sys_proxies(None);
1081 // the system proxy in the registry has been disabled
1082 let disabled_proxies = get_sys_proxies(Some((0, String::from("http://127.0.0.1/"))));
1083 // set valid proxy
1084 let valid_proxies = get_sys_proxies(Some((1, String::from("http://127.0.0.1/"))));
1085 let valid_proxies_no_schema = get_sys_proxies(Some((1, String::from("127.0.0.1"))));
1086 let valid_proxies_explicit_https =
1087 get_sys_proxies(Some((1, String::from("https://127.0.0.1/"))));
1088 let multiple_proxies = get_sys_proxies(Some((
1089 1,
1090 String::from("http=127.0.0.1:8888;https=127.0.0.2:8888"),
1091 )));
1092 let multiple_proxies_explicit_schema = get_sys_proxies(Some((
1093 1,
1094 String::from("http=http://127.0.0.1:8888;https=https://127.0.0.2:8888"),
1095 )));
1096
1097 // reset user setting when guards drop
1098 drop(_g1);
1099 drop(_g2);
1100 // Let other threads run now
1101 drop(_lock);
1102
1103 assert_eq!(baseline_proxies.contains_key("http"), false);
1104 assert_eq!(disabled_proxies.contains_key("http"), false);
1105
1106 let p = &valid_proxies["http"];
1107 assert_eq!(p.scheme(), "http");
1108 assert_eq!(p.host(), "127.0.0.1");
1109
1110 let p = &valid_proxies_no_schema["http"];
1111 assert_eq!(p.scheme(), "http");
1112 assert_eq!(p.host(), "127.0.0.1");
1113
1114 let p = &valid_proxies_no_schema["https"];
1115 assert_eq!(p.scheme(), "http");
1116 assert_eq!(p.host(), "127.0.0.1");
1117
1118 let p = &valid_proxies_explicit_https["https"];
1119 assert_eq!(p.scheme(), "https");
1120 assert_eq!(p.host(), "127.0.0.1");
1121
1122 let p = &multiple_proxies["http"];
1123 assert_eq!(p.scheme(), "http");
1124 assert_eq!(p.host(), "127.0.0.1:8888");
1125
1126 let p = &multiple_proxies["https"];
1127 assert_eq!(p.scheme(), "http");
1128 assert_eq!(p.host(), "127.0.0.2:8888");
1129
1130 let p = &multiple_proxies_explicit_schema["http"];
1131 assert_eq!(p.scheme(), "http");
1132 assert_eq!(p.host(), "127.0.0.1:8888");
1133
1134 let p = &multiple_proxies_explicit_schema["https"];
1135 assert_eq!(p.scheme(), "https");
1136 assert_eq!(p.host(), "127.0.0.2:8888");
1137 }
1138
1139 #[test]
test_get_sys_proxies_in_cgi()1140 fn test_get_sys_proxies_in_cgi() {
1141 // Stop other threads from modifying process-global ENV while we are.
1142 let _lock = ENVLOCK.lock();
1143 // save system setting first.
1144 let _g1 = env_guard("REQUEST_METHOD");
1145 let _g2 = env_guard("HTTP_PROXY");
1146
1147 // Mock ENV, get the results, before doing assertions
1148 // to avoid assert! -> panic! -> Mutex Poisoned.
1149 env::set_var("HTTP_PROXY", "http://evil/");
1150
1151 let baseline_proxies = get_sys_proxies(None);
1152 // set like we're in CGI
1153 env::set_var("REQUEST_METHOD", "GET");
1154
1155 let cgi_proxies = get_sys_proxies(None);
1156
1157 // reset user setting when guards drop
1158 drop(_g1);
1159 drop(_g2);
1160 // Let other threads run now
1161 drop(_lock);
1162
1163 // not in CGI yet
1164 assert_eq!(baseline_proxies["http"].host(), "evil");
1165 // In CGI
1166 assert!(!cgi_proxies.contains_key("http"));
1167 }
1168
1169 #[test]
test_sys_no_proxy()1170 fn test_sys_no_proxy() {
1171 // Stop other threads from modifying process-global ENV while we are.
1172 let _lock = ENVLOCK.lock();
1173 // save system setting first.
1174 let _g1 = env_guard("HTTP_PROXY");
1175 let _g2 = env_guard("NO_PROXY");
1176
1177 let target = "http://example.domain/";
1178 env::set_var("HTTP_PROXY", target);
1179
1180 env::set_var(
1181 "NO_PROXY",
1182 ".foo.bar,bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17",
1183 );
1184
1185 // Manually construct this so we aren't use the cache
1186 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1187 p.no_proxy = NoProxy::new();
1188
1189 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1190 assert_eq!(intercepted_uri(&p, "http://foo.bar.baz"), target);
1191 assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target);
1192 assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target);
1193 assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target);
1194 assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target);
1195
1196 assert!(p.intercept(&url("http://hello.foo.bar")).is_none());
1197 assert!(p.intercept(&url("http://bar.baz")).is_none());
1198 assert!(p.intercept(&url("http://10.42.1.100")).is_none());
1199 assert!(p.intercept(&url("http://[::1]")).is_none());
1200 assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none());
1201 assert!(p.intercept(&url("http://10.124.7.8")).is_none());
1202
1203 // reset user setting when guards drop
1204 drop(_g1);
1205 drop(_g2);
1206 // Let other threads run now
1207 drop(_lock);
1208 }
1209
1210 #[test]
test_no_proxy_load()1211 fn test_no_proxy_load() {
1212 // Stop other threads from modifying process-global ENV while we are.
1213 let _lock = ENVLOCK.lock();
1214
1215 let _g1 = env_guard("no_proxy");
1216 let domain = "lower.case";
1217 env::set_var("no_proxy", domain);
1218 // Manually construct this so we aren't use the cache
1219 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1220 p.no_proxy = NoProxy::new();
1221 assert_eq!(
1222 p.no_proxy.expect("should have a no proxy set").domains.0[0],
1223 domain
1224 );
1225
1226 env::remove_var("no_proxy");
1227 let _g2 = env_guard("NO_PROXY");
1228 let domain = "upper.case";
1229 env::set_var("NO_PROXY", domain);
1230 // Manually construct this so we aren't use the cache
1231 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1232 p.no_proxy = NoProxy::new();
1233 assert_eq!(
1234 p.no_proxy.expect("should have a no proxy set").domains.0[0],
1235 domain
1236 );
1237
1238 let _g3 = env_guard("HTTP_PROXY");
1239 env::remove_var("NO_PROXY");
1240 env::remove_var("no_proxy");
1241 let target = "http://example.domain/";
1242 env::set_var("HTTP_PROXY", target);
1243
1244 // Manually construct this so we aren't use the cache
1245 let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
1246 p.no_proxy = NoProxy::new();
1247 assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
1248
1249 assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
1250
1251 // reset user setting when guards drop
1252 drop(_g1);
1253 drop(_g2);
1254 drop(_g3);
1255 // Let other threads run now
1256 drop(_lock);
1257 }
1258
1259 #[cfg(target_os = "windows")]
1260 #[test]
test_type_prefix_extraction()1261 fn test_type_prefix_extraction() {
1262 assert!(extract_type_prefix("test").is_none());
1263 assert!(extract_type_prefix("://test").is_none());
1264 assert!(extract_type_prefix("some:prefix://test").is_none());
1265 assert!(extract_type_prefix("some/prefix://test").is_none());
1266
1267 assert_eq!(extract_type_prefix("http://test").unwrap(), "http");
1268 assert_eq!(extract_type_prefix("a://test").unwrap(), "a");
1269 }
1270
1271 /// Guard an environment variable, resetting it to the original value
1272 /// when dropped.
env_guard(name: impl Into<String>) -> EnvGuard1273 fn env_guard(name: impl Into<String>) -> EnvGuard {
1274 let name = name.into();
1275 let orig_val = env::var(&name).ok();
1276 env::remove_var(&name);
1277 EnvGuard { name, orig_val }
1278 }
1279
1280 struct EnvGuard {
1281 name: String,
1282 orig_val: Option<String>,
1283 }
1284
1285 impl Drop for EnvGuard {
drop(&mut self)1286 fn drop(&mut self) {
1287 if let Some(val) = self.orig_val.take() {
1288 env::set_var(&self.name, val);
1289 } else {
1290 env::remove_var(&self.name);
1291 }
1292 }
1293 }
1294
1295 #[test]
test_has_http_auth()1296 fn test_has_http_auth() {
1297 let http_proxy_with_auth = Proxy {
1298 intercept: Intercept::Http(ProxyScheme::Http {
1299 auth: Some(HeaderValue::from_static("auth1")),
1300 host: http::uri::Authority::from_static("authority"),
1301 }),
1302 no_proxy: None,
1303 };
1304 assert_eq!(http_proxy_with_auth.maybe_has_http_auth(), true);
1305 assert_eq!(
1306 http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1307 Some(HeaderValue::from_static("auth1"))
1308 );
1309
1310 let http_proxy_without_auth = Proxy {
1311 intercept: Intercept::Http(ProxyScheme::Http {
1312 auth: None,
1313 host: http::uri::Authority::from_static("authority"),
1314 }),
1315 no_proxy: None,
1316 };
1317 assert_eq!(http_proxy_without_auth.maybe_has_http_auth(), false);
1318 assert_eq!(
1319 http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1320 None
1321 );
1322
1323 let https_proxy_with_auth = Proxy {
1324 intercept: Intercept::Http(ProxyScheme::Https {
1325 auth: Some(HeaderValue::from_static("auth2")),
1326 host: http::uri::Authority::from_static("authority"),
1327 }),
1328 no_proxy: None,
1329 };
1330 assert_eq!(https_proxy_with_auth.maybe_has_http_auth(), false);
1331 assert_eq!(
1332 https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1333 None
1334 );
1335
1336 let all_http_proxy_with_auth = Proxy {
1337 intercept: Intercept::All(ProxyScheme::Http {
1338 auth: Some(HeaderValue::from_static("auth3")),
1339 host: http::uri::Authority::from_static("authority"),
1340 }),
1341 no_proxy: None,
1342 };
1343 assert_eq!(all_http_proxy_with_auth.maybe_has_http_auth(), true);
1344 assert_eq!(
1345 all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1346 Some(HeaderValue::from_static("auth3"))
1347 );
1348
1349 let all_https_proxy_with_auth = Proxy {
1350 intercept: Intercept::All(ProxyScheme::Https {
1351 auth: Some(HeaderValue::from_static("auth4")),
1352 host: http::uri::Authority::from_static("authority"),
1353 }),
1354 no_proxy: None,
1355 };
1356 assert_eq!(all_https_proxy_with_auth.maybe_has_http_auth(), false);
1357 assert_eq!(
1358 all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1359 None
1360 );
1361
1362 let all_https_proxy_without_auth = Proxy {
1363 intercept: Intercept::All(ProxyScheme::Https {
1364 auth: None,
1365 host: http::uri::Authority::from_static("authority"),
1366 }),
1367 no_proxy: None,
1368 };
1369 assert_eq!(all_https_proxy_without_auth.maybe_has_http_auth(), false);
1370 assert_eq!(
1371 all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1372 None
1373 );
1374
1375 let system_http_proxy_with_auth = Proxy {
1376 intercept: Intercept::System(Arc::new({
1377 let mut m = HashMap::new();
1378 m.insert(
1379 "http".into(),
1380 ProxyScheme::Http {
1381 auth: Some(HeaderValue::from_static("auth5")),
1382 host: http::uri::Authority::from_static("authority"),
1383 },
1384 );
1385 m
1386 })),
1387 no_proxy: None,
1388 };
1389 assert_eq!(system_http_proxy_with_auth.maybe_has_http_auth(), true);
1390 assert_eq!(
1391 system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1392 Some(HeaderValue::from_static("auth5"))
1393 );
1394
1395 let system_https_proxy_with_auth = Proxy {
1396 intercept: Intercept::System(Arc::new({
1397 let mut m = HashMap::new();
1398 m.insert(
1399 "https".into(),
1400 ProxyScheme::Https {
1401 auth: Some(HeaderValue::from_static("auth6")),
1402 host: http::uri::Authority::from_static("authority"),
1403 },
1404 );
1405 m
1406 })),
1407 no_proxy: None,
1408 };
1409 assert_eq!(system_https_proxy_with_auth.maybe_has_http_auth(), false);
1410 assert_eq!(
1411 system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
1412 None
1413 );
1414 }
1415 }
1416