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 once_cell::sync::Lazy; |
11 | use percent_encoding::percent_decode; |
12 | use std::collections::HashMap; |
13 | use std::env; |
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 | pub 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 | impl 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. |
117 | pub trait IntoProxyScheme { |
118 | fn into_proxy_scheme(self) -> crate::Result<ProxyScheme>; |
119 | } |
120 | |
121 | impl<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. |
163 | fn _implied_bounds() { |
164 | fn prox<T: IntoProxyScheme>(_t: T) {} |
165 | |
166 | fn url<T: IntoUrl>(t: T) { |
167 | prox(t); |
168 | } |
169 | } |
170 | |
171 | impl IntoProxyScheme for ProxyScheme { |
172 | fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> { |
173 | Ok(self) |
174 | } |
175 | } |
176 | |
177 | impl 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 | |
405 | impl 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 | |
414 | impl 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 | |
484 | impl 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 | |
504 | impl 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 | |
531 | impl 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 | |
688 | impl 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 | |
706 | type SystemProxyMap = HashMap<String, ProxyScheme>; |
707 | type RegistryProxyValues = (u32, String); |
708 | |
709 | #[derive (Clone, Debug)] |
710 | enum Intercept { |
711 | All(ProxyScheme), |
712 | Http(ProxyScheme), |
713 | Https(ProxyScheme), |
714 | System(Arc<SystemProxyMap>), |
715 | Custom(Custom), |
716 | } |
717 | |
718 | impl 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)] |
734 | struct 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 | |
740 | impl 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 | |
758 | impl fmt::Debug for Custom { |
759 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
760 | f.write_str(data:"_" ) |
761 | } |
762 | } |
763 | |
764 | pub(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. |
770 | pub(crate) trait Dst { |
771 | fn scheme(&self) -> &str; |
772 | fn host(&self) -> &str; |
773 | fn port(&self) -> Option<u16>; |
774 | } |
775 | |
776 | #[doc (hidden)] |
777 | impl 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 | |
791 | static 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")} |
803 | fn 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 | |
823 | fn 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 | |
835 | fn 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 | |
853 | fn 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 :) |
865 | fn is_cgi() -> bool { |
866 | env::var_os(key:"REQUEST_METHOD" ).is_some() |
867 | } |
868 | |
869 | #[cfg (target_os = "windows" )] |
870 | fn 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" )] |
882 | fn get_from_registry() -> Option<RegistryProxyValues> { |
883 | get_from_registry_impl().ok() |
884 | } |
885 | |
886 | #[cfg (not(target_os = "windows" ))] |
887 | fn get_from_registry() -> Option<RegistryProxyValues> { |
888 | None |
889 | } |
890 | |
891 | #[cfg (target_os = "windows" )] |
892 | fn 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" )] |
942 | fn 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" )] |
962 | fn parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap { |
963 | parse_registry_values_impl(registry_values).unwrap_or(HashMap::new()) |
964 | } |
965 | |
966 | #[cfg (test)] |
967 | mod 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)] |
1640 | mod 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 | |