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