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