/rust/registry/src/index.crates.io-1949cf8c6b5b557f/reqwest-0.12.24/src/proxy.rs
Line | Count | Source |
1 | | use std::error::Error; |
2 | | use std::fmt; |
3 | | use std::sync::Arc; |
4 | | |
5 | | use http::{header::HeaderValue, HeaderMap, Uri}; |
6 | | use hyper_util::client::proxy::matcher; |
7 | | |
8 | | use crate::into_url::{IntoUrl, IntoUrlSealed}; |
9 | | use crate::Url; |
10 | | |
11 | | // # Internals |
12 | | // |
13 | | // This module is a couple pieces: |
14 | | // |
15 | | // - The public builder API |
16 | | // - The internal built types that our Connector knows how to use. |
17 | | // |
18 | | // The user creates a builder (`reqwest::Proxy`), and configures any extras. |
19 | | // Once that type is passed to the `ClientBuilder`, we convert it into the |
20 | | // built matcher types, making use of `hyper-util`'s matchers. |
21 | | |
22 | | /// Configuration of a proxy that a `Client` should pass requests to. |
23 | | /// |
24 | | /// A `Proxy` has a couple pieces to it: |
25 | | /// |
26 | | /// - a URL of how to talk to the proxy |
27 | | /// - rules on what `Client` requests should be directed to the proxy |
28 | | /// |
29 | | /// For instance, let's look at `Proxy::http`: |
30 | | /// |
31 | | /// ```rust |
32 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
33 | | /// let proxy = reqwest::Proxy::http("https://secure.example")?; |
34 | | /// # Ok(()) |
35 | | /// # } |
36 | | /// ``` |
37 | | /// |
38 | | /// This proxy will intercept all HTTP requests, and make use of the proxy |
39 | | /// at `https://secure.example`. A request to `http://hyper.rs` will talk |
40 | | /// to your proxy. A request to `https://hyper.rs` will not. |
41 | | /// |
42 | | /// Multiple `Proxy` rules can be configured for a `Client`. The `Client` will |
43 | | /// check each `Proxy` in the order it was added. This could mean that a |
44 | | /// `Proxy` added first with eager intercept rules, such as `Proxy::all`, |
45 | | /// would prevent a `Proxy` later in the list from ever working, so take care. |
46 | | /// |
47 | | /// By enabling the `"socks"` feature it is possible to use a socks proxy: |
48 | | /// ```rust |
49 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
50 | | /// let proxy = reqwest::Proxy::http("socks5://192.168.1.1:9000")?; |
51 | | /// # Ok(()) |
52 | | /// # } |
53 | | /// ``` |
54 | | #[derive(Clone)] |
55 | | pub struct Proxy { |
56 | | extra: Extra, |
57 | | intercept: Intercept, |
58 | | no_proxy: Option<NoProxy>, |
59 | | } |
60 | | |
61 | | /// A configuration for filtering out requests that shouldn't be proxied |
62 | | #[derive(Clone, Debug, Default)] |
63 | | pub struct NoProxy { |
64 | | inner: String, |
65 | | } |
66 | | |
67 | | #[derive(Clone)] |
68 | | struct Extra { |
69 | | auth: Option<HeaderValue>, |
70 | | misc: Option<HeaderMap>, |
71 | | } |
72 | | |
73 | | // ===== Internal ===== |
74 | | |
75 | | pub(crate) struct Matcher { |
76 | | inner: Matcher_, |
77 | | extra: Extra, |
78 | | maybe_has_http_auth: bool, |
79 | | maybe_has_http_custom_headers: bool, |
80 | | } |
81 | | |
82 | | enum Matcher_ { |
83 | | Util(matcher::Matcher), |
84 | | Custom(Custom), |
85 | | } |
86 | | |
87 | | /// Our own type, wrapping an `Intercept`, since we may have a few additional |
88 | | /// pieces attached thanks to `reqwest`s extra proxy configuration. |
89 | | pub(crate) struct Intercepted { |
90 | | inner: matcher::Intercept, |
91 | | /// This is because of `reqwest::Proxy`'s design which allows configuring |
92 | | /// an explicit auth, besides what might have been in the URL (or Custom). |
93 | | extra: Extra, |
94 | | } |
95 | | |
96 | | /* |
97 | | impl ProxyScheme { |
98 | | fn maybe_http_auth(&self) -> Option<&HeaderValue> { |
99 | | match self { |
100 | | ProxyScheme::Http { auth, .. } | ProxyScheme::Https { auth, .. } => auth.as_ref(), |
101 | | #[cfg(feature = "socks")] |
102 | | _ => None, |
103 | | } |
104 | | } |
105 | | |
106 | | fn maybe_http_custom_headers(&self) -> Option<&HeaderMap> { |
107 | | match self { |
108 | | ProxyScheme::Http { misc, .. } | ProxyScheme::Https { misc, .. } => misc.as_ref(), |
109 | | #[cfg(feature = "socks")] |
110 | | _ => None, |
111 | | } |
112 | | } |
113 | | } |
114 | | */ |
115 | | |
116 | | /// Trait used for converting into a proxy scheme. This trait supports |
117 | | /// parsing from a URL-like type, whilst also supporting proxy schemes |
118 | | /// built directly using the factory methods. |
119 | | pub trait IntoProxy { |
120 | | fn into_proxy(self) -> crate::Result<Url>; |
121 | | } |
122 | | |
123 | | impl<S: IntoUrl> IntoProxy for S { |
124 | 0 | fn into_proxy(self) -> crate::Result<Url> { |
125 | 0 | match self.as_str().into_url() { |
126 | 0 | Ok(mut url) => { |
127 | | // If the scheme is a SOCKS protocol and no port is specified, set the default |
128 | 0 | if url.port().is_none() |
129 | 0 | && matches!(url.scheme(), "socks4" | "socks4a" | "socks5" | "socks5h") |
130 | 0 | { |
131 | 0 | let _ = url.set_port(Some(1080)); |
132 | 0 | } |
133 | 0 | Ok(url) |
134 | | } |
135 | 0 | Err(e) => { |
136 | 0 | let mut presumed_to_have_scheme = true; |
137 | 0 | let mut source = e.source(); |
138 | 0 | while let Some(err) = source { |
139 | 0 | if let Some(parse_error) = err.downcast_ref::<url::ParseError>() { |
140 | 0 | if *parse_error == url::ParseError::RelativeUrlWithoutBase { |
141 | 0 | presumed_to_have_scheme = false; |
142 | 0 | break; |
143 | 0 | } |
144 | 0 | } else if err.downcast_ref::<crate::error::BadScheme>().is_some() { |
145 | 0 | presumed_to_have_scheme = false; |
146 | 0 | break; |
147 | 0 | } |
148 | 0 | source = err.source(); |
149 | | } |
150 | 0 | if presumed_to_have_scheme { |
151 | 0 | return Err(crate::error::builder(e)); |
152 | 0 | } |
153 | | // the issue could have been caused by a missing scheme, so we try adding http:// |
154 | 0 | let try_this = format!("http://{}", self.as_str()); |
155 | 0 | try_this.into_url().map_err(|_| { |
156 | | // return the original error |
157 | 0 | crate::error::builder(e) |
158 | 0 | }) |
159 | | } |
160 | | } |
161 | 0 | } |
162 | | } |
163 | | |
164 | | // These bounds are accidentally leaked by the blanket impl of IntoProxy |
165 | | // for all types that implement IntoUrl. So, this function exists to detect |
166 | | // if we were to break those bounds for a user. |
167 | 0 | fn _implied_bounds() { |
168 | 0 | fn prox<T: IntoProxy>(_t: T) {} |
169 | | |
170 | 0 | fn url<T: IntoUrl>(t: T) { |
171 | 0 | prox(t); |
172 | 0 | } |
173 | 0 | } |
174 | | |
175 | | impl Proxy { |
176 | | /// Proxy all HTTP traffic to the passed URL. |
177 | | /// |
178 | | /// # Example |
179 | | /// |
180 | | /// ``` |
181 | | /// # extern crate reqwest; |
182 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
183 | | /// let client = reqwest::Client::builder() |
184 | | /// .proxy(reqwest::Proxy::http("https://my.prox")?) |
185 | | /// .build()?; |
186 | | /// # Ok(()) |
187 | | /// # } |
188 | | /// # fn main() {} |
189 | | /// ``` |
190 | 0 | pub fn http<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> { |
191 | 0 | Ok(Proxy::new(Intercept::Http(proxy_scheme.into_proxy()?))) |
192 | 0 | } |
193 | | |
194 | | /// Proxy all HTTPS traffic to the passed URL. |
195 | | /// |
196 | | /// # Example |
197 | | /// |
198 | | /// ``` |
199 | | /// # extern crate reqwest; |
200 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
201 | | /// let client = reqwest::Client::builder() |
202 | | /// .proxy(reqwest::Proxy::https("https://example.prox:4545")?) |
203 | | /// .build()?; |
204 | | /// # Ok(()) |
205 | | /// # } |
206 | | /// # fn main() {} |
207 | | /// ``` |
208 | 0 | pub fn https<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> { |
209 | 0 | Ok(Proxy::new(Intercept::Https(proxy_scheme.into_proxy()?))) |
210 | 0 | } |
211 | | |
212 | | /// Proxy **all** traffic to the passed URL. |
213 | | /// |
214 | | /// "All" refers to `https` and `http` URLs. Other schemes are not |
215 | | /// recognized by reqwest. |
216 | | /// |
217 | | /// # Example |
218 | | /// |
219 | | /// ``` |
220 | | /// # extern crate reqwest; |
221 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
222 | | /// let client = reqwest::Client::builder() |
223 | | /// .proxy(reqwest::Proxy::all("http://pro.xy")?) |
224 | | /// .build()?; |
225 | | /// # Ok(()) |
226 | | /// # } |
227 | | /// # fn main() {} |
228 | | /// ``` |
229 | 0 | pub fn all<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> { |
230 | 0 | Ok(Proxy::new(Intercept::All(proxy_scheme.into_proxy()?))) |
231 | 0 | } |
232 | | |
233 | | /// Provide a custom function to determine what traffic to proxy to where. |
234 | | /// |
235 | | /// # Example |
236 | | /// |
237 | | /// ``` |
238 | | /// # extern crate reqwest; |
239 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
240 | | /// let target = reqwest::Url::parse("https://my.prox")?; |
241 | | /// let client = reqwest::Client::builder() |
242 | | /// .proxy(reqwest::Proxy::custom(move |url| { |
243 | | /// if url.host_str() == Some("hyper.rs") { |
244 | | /// Some(target.clone()) |
245 | | /// } else { |
246 | | /// None |
247 | | /// } |
248 | | /// })) |
249 | | /// .build()?; |
250 | | /// # Ok(()) |
251 | | /// # } |
252 | | /// # fn main() {} |
253 | | /// ``` |
254 | 0 | pub fn custom<F, U: IntoProxy>(fun: F) -> Proxy |
255 | 0 | where |
256 | 0 | F: Fn(&Url) -> Option<U> + Send + Sync + 'static, |
257 | | { |
258 | 0 | Proxy::new(Intercept::Custom(Custom { |
259 | 0 | func: Arc::new(move |url| fun(url).map(IntoProxy::into_proxy)), |
260 | 0 | no_proxy: None, |
261 | | })) |
262 | 0 | } |
263 | | |
264 | 0 | fn new(intercept: Intercept) -> Proxy { |
265 | 0 | Proxy { |
266 | 0 | extra: Extra { |
267 | 0 | auth: None, |
268 | 0 | misc: None, |
269 | 0 | }, |
270 | 0 | intercept, |
271 | 0 | no_proxy: None, |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | /// Set the `Proxy-Authorization` header using Basic auth. |
276 | | /// |
277 | | /// # Example |
278 | | /// |
279 | | /// ``` |
280 | | /// # extern crate reqwest; |
281 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
282 | | /// let proxy = reqwest::Proxy::https("http://localhost:1234")? |
283 | | /// .basic_auth("Aladdin", "open sesame"); |
284 | | /// # Ok(()) |
285 | | /// # } |
286 | | /// # fn main() {} |
287 | | /// ``` |
288 | 0 | pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy { |
289 | 0 | match self.intercept { |
290 | 0 | Intercept::All(ref mut s) |
291 | 0 | | Intercept::Http(ref mut s) |
292 | 0 | | Intercept::Https(ref mut s) => url_auth(s, username, password), |
293 | 0 | Intercept::Custom(_) => { |
294 | 0 | let header = encode_basic_auth(username, password); |
295 | 0 | self.extra.auth = Some(header); |
296 | 0 | } |
297 | | } |
298 | | |
299 | 0 | self |
300 | 0 | } |
301 | | |
302 | | /// Set the `Proxy-Authorization` header to a specified value. |
303 | | /// |
304 | | /// # Example |
305 | | /// |
306 | | /// ``` |
307 | | /// # extern crate reqwest; |
308 | | /// # use reqwest::header::*; |
309 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
310 | | /// let proxy = reqwest::Proxy::https("http://localhost:1234")? |
311 | | /// .custom_http_auth(HeaderValue::from_static("justletmeinalreadyplease")); |
312 | | /// # Ok(()) |
313 | | /// # } |
314 | | /// # fn main() {} |
315 | | /// ``` |
316 | 0 | pub fn custom_http_auth(mut self, header_value: HeaderValue) -> Proxy { |
317 | 0 | self.extra.auth = Some(header_value); |
318 | 0 | self |
319 | 0 | } |
320 | | |
321 | | /// Adds a Custom Headers to Proxy |
322 | | /// Adds custom headers to this Proxy |
323 | | /// |
324 | | /// # Example |
325 | | /// ``` |
326 | | /// # extern crate reqwest; |
327 | | /// # use reqwest::header::*; |
328 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
329 | | /// let mut headers = HeaderMap::new(); |
330 | | /// headers.insert(USER_AGENT, "reqwest".parse().unwrap()); |
331 | | /// let proxy = reqwest::Proxy::https("http://localhost:1234")? |
332 | | /// .headers(headers); |
333 | | /// # Ok(()) |
334 | | /// # } |
335 | | /// # fn main() {} |
336 | | /// ``` |
337 | 0 | pub fn headers(mut self, headers: HeaderMap) -> Proxy { |
338 | 0 | match self.intercept { |
339 | 0 | Intercept::All(_) | Intercept::Http(_) | Intercept::Https(_) | Intercept::Custom(_) => { |
340 | 0 | self.extra.misc = Some(headers); |
341 | 0 | } |
342 | | } |
343 | | |
344 | 0 | self |
345 | 0 | } |
346 | | |
347 | | /// Adds a `No Proxy` exclusion list to this Proxy |
348 | | /// |
349 | | /// # Example |
350 | | /// |
351 | | /// ``` |
352 | | /// # extern crate reqwest; |
353 | | /// # fn run() -> Result<(), Box<dyn std::error::Error>> { |
354 | | /// let proxy = reqwest::Proxy::https("http://localhost:1234")? |
355 | | /// .no_proxy(reqwest::NoProxy::from_string("direct.tld, sub.direct2.tld")); |
356 | | /// # Ok(()) |
357 | | /// # } |
358 | | /// # fn main() {} |
359 | | /// ``` |
360 | 0 | pub fn no_proxy(mut self, no_proxy: Option<NoProxy>) -> Proxy { |
361 | 0 | self.no_proxy = no_proxy; |
362 | 0 | self |
363 | 0 | } |
364 | | |
365 | 0 | pub(crate) fn into_matcher(self) -> Matcher { |
366 | | let Proxy { |
367 | 0 | intercept, |
368 | 0 | extra, |
369 | 0 | no_proxy, |
370 | 0 | } = self; |
371 | | |
372 | | let maybe_has_http_auth; |
373 | | let maybe_has_http_custom_headers; |
374 | | |
375 | 0 | let inner = match intercept { |
376 | 0 | Intercept::All(url) => { |
377 | 0 | maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth); |
378 | 0 | maybe_has_http_custom_headers = |
379 | 0 | cache_maybe_has_http_custom_headers(&url, &extra.misc); |
380 | | Matcher_::Util( |
381 | 0 | matcher::Matcher::builder() |
382 | 0 | .all(String::from(url)) |
383 | 0 | .no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or("")) |
384 | 0 | .build(), |
385 | | ) |
386 | | } |
387 | 0 | Intercept::Http(url) => { |
388 | 0 | maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth); |
389 | 0 | maybe_has_http_custom_headers = |
390 | 0 | cache_maybe_has_http_custom_headers(&url, &extra.misc); |
391 | | Matcher_::Util( |
392 | 0 | matcher::Matcher::builder() |
393 | 0 | .http(String::from(url)) |
394 | 0 | .no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or("")) |
395 | 0 | .build(), |
396 | | ) |
397 | | } |
398 | 0 | Intercept::Https(url) => { |
399 | 0 | maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth); |
400 | 0 | maybe_has_http_custom_headers = |
401 | 0 | cache_maybe_has_http_custom_headers(&url, &extra.misc); |
402 | | Matcher_::Util( |
403 | 0 | matcher::Matcher::builder() |
404 | 0 | .https(String::from(url)) |
405 | 0 | .no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or("")) |
406 | 0 | .build(), |
407 | | ) |
408 | | } |
409 | 0 | Intercept::Custom(mut custom) => { |
410 | 0 | maybe_has_http_auth = true; // never know |
411 | 0 | maybe_has_http_custom_headers = true; |
412 | 0 | custom.no_proxy = no_proxy; |
413 | 0 | Matcher_::Custom(custom) |
414 | | } |
415 | | }; |
416 | | |
417 | 0 | Matcher { |
418 | 0 | inner, |
419 | 0 | extra, |
420 | 0 | maybe_has_http_auth, |
421 | 0 | maybe_has_http_custom_headers, |
422 | 0 | } |
423 | 0 | } |
424 | | |
425 | | /* |
426 | | pub(crate) fn maybe_has_http_auth(&self) -> bool { |
427 | | match &self.intercept { |
428 | | Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().is_some(), |
429 | | // Custom *may* match 'http', so assume so. |
430 | | Intercept::Custom(_) => true, |
431 | | Intercept::System(system) => system |
432 | | .get("http") |
433 | | .and_then(|s| s.maybe_http_auth()) |
434 | | .is_some(), |
435 | | Intercept::Https(_) => false, |
436 | | } |
437 | | } |
438 | | |
439 | | pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> { |
440 | | match &self.intercept { |
441 | | Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().cloned(), |
442 | | Intercept::System(system) => system |
443 | | .get("http") |
444 | | .and_then(|s| s.maybe_http_auth().cloned()), |
445 | | Intercept::Custom(custom) => { |
446 | | custom.call(uri).and_then(|s| s.maybe_http_auth().cloned()) |
447 | | } |
448 | | Intercept::Https(_) => None, |
449 | | } |
450 | | } |
451 | | */ |
452 | | } |
453 | | |
454 | 0 | fn cache_maybe_has_http_auth(url: &Url, extra: &Option<HeaderValue>) -> bool { |
455 | 0 | url.scheme() == "http" && (url.password().is_some() || extra.is_some()) |
456 | 0 | } |
457 | | |
458 | 0 | fn cache_maybe_has_http_custom_headers(url: &Url, extra: &Option<HeaderMap>) -> bool { |
459 | 0 | url.scheme() == "http" && extra.is_some() |
460 | 0 | } |
461 | | |
462 | | impl fmt::Debug for Proxy { |
463 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
464 | 0 | f.debug_tuple("Proxy") |
465 | 0 | .field(&self.intercept) |
466 | 0 | .field(&self.no_proxy) |
467 | 0 | .finish() |
468 | 0 | } |
469 | | } |
470 | | |
471 | | impl NoProxy { |
472 | | /// Returns a new no-proxy configuration based on environment variables (or `None` if no variables are set) |
473 | | /// see [self::NoProxy::from_string()] for the string format |
474 | 0 | pub fn from_env() -> Option<NoProxy> { |
475 | 0 | let raw = std::env::var("NO_PROXY") |
476 | 0 | .or_else(|_| std::env::var("no_proxy")) |
477 | 0 | .ok()?; |
478 | | |
479 | | // Per the docs, this returns `None` if no environment variable is set. We can only reach |
480 | | // here if an env var is set, so we return `Some(NoProxy::default)` if `from_string` |
481 | | // returns None, which occurs with an empty string. |
482 | 0 | Some(Self::from_string(&raw).unwrap_or_default()) |
483 | 0 | } |
484 | | |
485 | | /// Returns a new no-proxy configuration based on a `no_proxy` string (or `None` if no variables |
486 | | /// are set) |
487 | | /// The rules are as follows: |
488 | | /// * The environment variable `NO_PROXY` is checked, if it is not set, `no_proxy` is checked |
489 | | /// * If neither environment variable is set, `None` is returned |
490 | | /// * Entries are expected to be comma-separated (whitespace between entries is ignored) |
491 | | /// * IP addresses (both IPv4 and IPv6) are allowed, as are optional subnet masks (by adding /size, |
492 | | /// for example "`192.168.1.0/24`"). |
493 | | /// * An entry "`*`" matches all hostnames (this is the only wildcard allowed) |
494 | | /// * Any other entry is considered a domain name (and may contain a leading dot, for example `google.com` |
495 | | /// and `.google.com` are equivalent) and would match both that domain AND all subdomains. |
496 | | /// |
497 | | /// For example, if `"NO_PROXY=google.com, 192.168.1.0/24"` was set, all the following would match |
498 | | /// (and therefore would bypass the proxy): |
499 | | /// * `http://google.com/` |
500 | | /// * `http://www.google.com/` |
501 | | /// * `http://192.168.1.42/` |
502 | | /// |
503 | | /// The URL `http://notgoogle.com/` would not match. |
504 | 0 | pub fn from_string(no_proxy_list: &str) -> Option<Self> { |
505 | | // lazy parsed, to not make the type public in hyper-util |
506 | 0 | Some(NoProxy { |
507 | 0 | inner: no_proxy_list.into(), |
508 | 0 | }) |
509 | 0 | } |
510 | | } |
511 | | |
512 | | impl Matcher { |
513 | 0 | pub(crate) fn system() -> Self { |
514 | 0 | Self { |
515 | 0 | inner: Matcher_::Util(matcher::Matcher::from_system()), |
516 | 0 | extra: Extra { |
517 | 0 | auth: None, |
518 | 0 | misc: None, |
519 | 0 | }, |
520 | 0 | // maybe env vars have auth! |
521 | 0 | maybe_has_http_auth: true, |
522 | 0 | maybe_has_http_custom_headers: true, |
523 | 0 | } |
524 | 0 | } |
525 | | |
526 | 0 | pub(crate) fn intercept(&self, dst: &Uri) -> Option<Intercepted> { |
527 | 0 | let inner = match self.inner { |
528 | 0 | Matcher_::Util(ref m) => m.intercept(dst), |
529 | 0 | Matcher_::Custom(ref c) => c.call(dst), |
530 | | }; |
531 | | |
532 | 0 | inner.map(|inner| Intercepted { |
533 | 0 | inner, |
534 | 0 | extra: self.extra.clone(), |
535 | 0 | }) |
536 | 0 | } |
537 | | |
538 | | /// Return whether this matcher might provide HTTP (not s) auth. |
539 | | /// |
540 | | /// This is very specific. If this proxy needs auth to be part of a Forward |
541 | | /// request (instead of a tunnel), this should return true. |
542 | | /// |
543 | | /// If it's not sure, this should return true. |
544 | | /// |
545 | | /// This is meant as a hint to allow skipping a more expensive check |
546 | | /// (calling `intercept()`) if it will never need auth when Forwarding. |
547 | 0 | pub(crate) fn maybe_has_http_auth(&self) -> bool { |
548 | 0 | self.maybe_has_http_auth |
549 | 0 | } |
550 | | |
551 | 0 | pub(crate) fn http_non_tunnel_basic_auth(&self, dst: &Uri) -> Option<HeaderValue> { |
552 | 0 | if let Some(proxy) = self.intercept(dst) { |
553 | 0 | if proxy.uri().scheme_str() == Some("http") { |
554 | 0 | return proxy.basic_auth().cloned(); |
555 | 0 | } |
556 | 0 | } |
557 | | |
558 | 0 | None |
559 | 0 | } |
560 | | |
561 | 0 | pub(crate) fn maybe_has_http_custom_headers(&self) -> bool { |
562 | 0 | self.maybe_has_http_custom_headers |
563 | 0 | } |
564 | | |
565 | 0 | pub(crate) fn http_non_tunnel_custom_headers(&self, dst: &Uri) -> Option<HeaderMap> { |
566 | 0 | if let Some(proxy) = self.intercept(dst) { |
567 | 0 | if proxy.uri().scheme_str() == Some("http") { |
568 | 0 | return proxy.custom_headers().cloned(); |
569 | 0 | } |
570 | 0 | } |
571 | | |
572 | 0 | None |
573 | 0 | } |
574 | | } |
575 | | |
576 | | impl fmt::Debug for Matcher { |
577 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
578 | 0 | match self.inner { |
579 | 0 | Matcher_::Util(ref m) => m.fmt(f), |
580 | 0 | Matcher_::Custom(ref m) => m.fmt(f), |
581 | | } |
582 | 0 | } |
583 | | } |
584 | | |
585 | | impl Intercepted { |
586 | 0 | pub(crate) fn uri(&self) -> &http::Uri { |
587 | 0 | self.inner.uri() |
588 | 0 | } |
589 | | |
590 | 0 | pub(crate) fn basic_auth(&self) -> Option<&HeaderValue> { |
591 | 0 | if let Some(ref val) = self.extra.auth { |
592 | 0 | return Some(val); |
593 | 0 | } |
594 | 0 | self.inner.basic_auth() |
595 | 0 | } |
596 | | |
597 | 0 | pub(crate) fn custom_headers(&self) -> Option<&HeaderMap> { |
598 | 0 | if let Some(ref val) = self.extra.misc { |
599 | 0 | return Some(val); |
600 | 0 | } |
601 | 0 | None |
602 | 0 | } |
603 | | |
604 | | #[cfg(feature = "socks")] |
605 | | pub(crate) fn raw_auth(&self) -> Option<(&str, &str)> { |
606 | | self.inner.raw_auth() |
607 | | } |
608 | | } |
609 | | |
610 | | impl fmt::Debug for Intercepted { |
611 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
612 | 0 | self.inner.uri().fmt(f) |
613 | 0 | } |
614 | | } |
615 | | |
616 | | /* |
617 | | impl ProxyScheme { |
618 | | /// Use a username and password when connecting to the proxy server |
619 | | fn with_basic_auth<T: Into<String>, U: Into<String>>( |
620 | | mut self, |
621 | | username: T, |
622 | | password: U, |
623 | | ) -> Self { |
624 | | self.set_basic_auth(username, password); |
625 | | self |
626 | | } |
627 | | |
628 | | fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) { |
629 | | match *self { |
630 | | ProxyScheme::Http { ref mut auth, .. } => { |
631 | | let header = encode_basic_auth(&username.into(), &password.into()); |
632 | | *auth = Some(header); |
633 | | } |
634 | | ProxyScheme::Https { ref mut auth, .. } => { |
635 | | let header = encode_basic_auth(&username.into(), &password.into()); |
636 | | *auth = Some(header); |
637 | | } |
638 | | #[cfg(feature = "socks")] |
639 | | ProxyScheme::Socks4 { .. } => { |
640 | | panic!("Socks4 is not supported for this method") |
641 | | } |
642 | | #[cfg(feature = "socks")] |
643 | | ProxyScheme::Socks5 { ref mut auth, .. } => { |
644 | | *auth = Some((username.into(), password.into())); |
645 | | } |
646 | | } |
647 | | } |
648 | | |
649 | | fn set_custom_http_auth(&mut self, header_value: HeaderValue) { |
650 | | match *self { |
651 | | ProxyScheme::Http { ref mut auth, .. } => { |
652 | | *auth = Some(header_value); |
653 | | } |
654 | | ProxyScheme::Https { ref mut auth, .. } => { |
655 | | *auth = Some(header_value); |
656 | | } |
657 | | #[cfg(feature = "socks")] |
658 | | ProxyScheme::Socks4 { .. } => { |
659 | | panic!("Socks4 is not supported for this method") |
660 | | } |
661 | | #[cfg(feature = "socks")] |
662 | | ProxyScheme::Socks5 { .. } => { |
663 | | panic!("Socks5 is not supported for this method") |
664 | | } |
665 | | } |
666 | | } |
667 | | |
668 | | fn set_custom_headers(&mut self, headers: HeaderMap) { |
669 | | match *self { |
670 | | ProxyScheme::Http { ref mut misc, .. } => { |
671 | | misc.get_or_insert_with(HeaderMap::new).extend(headers) |
672 | | } |
673 | | ProxyScheme::Https { ref mut misc, .. } => { |
674 | | misc.get_or_insert_with(HeaderMap::new).extend(headers) |
675 | | } |
676 | | #[cfg(feature = "socks")] |
677 | | ProxyScheme::Socks4 { .. } => { |
678 | | panic!("Socks4 is not supported for this method") |
679 | | } |
680 | | #[cfg(feature = "socks")] |
681 | | ProxyScheme::Socks5 { .. } => { |
682 | | panic!("Socks5 is not supported for this method") |
683 | | } |
684 | | } |
685 | | } |
686 | | |
687 | | fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self { |
688 | | match self { |
689 | | ProxyScheme::Http { ref mut auth, .. } => { |
690 | | if auth.is_none() { |
691 | | *auth = update.clone(); |
692 | | } |
693 | | } |
694 | | ProxyScheme::Https { ref mut auth, .. } => { |
695 | | if auth.is_none() { |
696 | | *auth = update.clone(); |
697 | | } |
698 | | } |
699 | | #[cfg(feature = "socks")] |
700 | | ProxyScheme::Socks4 { .. } => {} |
701 | | #[cfg(feature = "socks")] |
702 | | ProxyScheme::Socks5 { .. } => {} |
703 | | } |
704 | | |
705 | | self |
706 | | } |
707 | | |
708 | | /// Convert a URL into a proxy scheme |
709 | | /// |
710 | | /// Supported schemes: HTTP, HTTPS, (SOCKS4, SOCKS5, SOCKS5H if `socks` feature is enabled). |
711 | | // Private for now... |
712 | | fn parse(url: Url) -> crate::Result<Self> { |
713 | | use url::Position; |
714 | | |
715 | | // Resolve URL to a host and port |
716 | | #[cfg(feature = "socks")] |
717 | | let to_addr = || { |
718 | | let addrs = url |
719 | | .socket_addrs(|| match url.scheme() { |
720 | | "socks4" | "socks4a" | "socks5" | "socks5h" => Some(1080), |
721 | | _ => None, |
722 | | }) |
723 | | .map_err(crate::error::builder)?; |
724 | | addrs |
725 | | .into_iter() |
726 | | .next() |
727 | | .ok_or_else(|| crate::error::builder("unknown proxy scheme")) |
728 | | }; |
729 | | |
730 | | let mut scheme = match url.scheme() { |
731 | | "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?, |
732 | | "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?, |
733 | | #[cfg(feature = "socks")] |
734 | | "socks4" => Self::socks4(to_addr()?)?, |
735 | | #[cfg(feature = "socks")] |
736 | | "socks4a" => Self::socks4a(to_addr()?)?, |
737 | | #[cfg(feature = "socks")] |
738 | | "socks5" => Self::socks5(to_addr()?)?, |
739 | | #[cfg(feature = "socks")] |
740 | | "socks5h" => Self::socks5h(to_addr()?)?, |
741 | | _ => return Err(crate::error::builder("unknown proxy scheme")), |
742 | | }; |
743 | | |
744 | | if let Some(pwd) = url.password() { |
745 | | let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy(); |
746 | | let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy(); |
747 | | scheme = scheme.with_basic_auth(decoded_username, decoded_password); |
748 | | } |
749 | | |
750 | | Ok(scheme) |
751 | | } |
752 | | } |
753 | | */ |
754 | | |
755 | | #[derive(Clone, Debug)] |
756 | | enum Intercept { |
757 | | All(Url), |
758 | | Http(Url), |
759 | | Https(Url), |
760 | | Custom(Custom), |
761 | | } |
762 | | |
763 | 0 | fn url_auth(url: &mut Url, username: &str, password: &str) { |
764 | 0 | url.set_username(username).expect("is a base"); |
765 | 0 | url.set_password(Some(password)).expect("is a base"); |
766 | 0 | } |
767 | | |
768 | | #[derive(Clone)] |
769 | | struct Custom { |
770 | | func: Arc<dyn Fn(&Url) -> Option<crate::Result<Url>> + Send + Sync + 'static>, |
771 | | no_proxy: Option<NoProxy>, |
772 | | } |
773 | | |
774 | | impl Custom { |
775 | 0 | fn call(&self, uri: &http::Uri) -> Option<matcher::Intercept> { |
776 | 0 | let url = format!( |
777 | 0 | "{}://{}{}{}", |
778 | 0 | uri.scheme()?, |
779 | 0 | uri.host()?, |
780 | 0 | uri.port().map_or("", |_| ":"), |
781 | 0 | uri.port().map_or(String::new(), |p| p.to_string()) |
782 | | ) |
783 | 0 | .parse() |
784 | 0 | .expect("should be valid Url"); |
785 | | |
786 | 0 | (self.func)(&url) |
787 | 0 | .and_then(|result| result.ok()) |
788 | 0 | .and_then(|target| { |
789 | 0 | let m = matcher::Matcher::builder() |
790 | 0 | .all(String::from(target)) |
791 | 0 | .build(); |
792 | | |
793 | 0 | m.intercept(uri) |
794 | 0 | }) |
795 | | //.map(|scheme| scheme.if_no_auth(&self.auth)) |
796 | 0 | } |
797 | | } |
798 | | |
799 | | impl fmt::Debug for Custom { |
800 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
801 | 0 | f.write_str("_") |
802 | 0 | } |
803 | | } |
804 | | |
805 | 0 | pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue { |
806 | 0 | crate::util::basic_auth(username, Some(password)) |
807 | 0 | } |
808 | | |
809 | | #[cfg(test)] |
810 | | mod tests { |
811 | | use super::*; |
812 | | |
813 | | fn url(s: &str) -> http::Uri { |
814 | | s.parse().unwrap() |
815 | | } |
816 | | |
817 | | fn intercepted_uri(p: &Matcher, s: &str) -> Uri { |
818 | | p.intercept(&s.parse().unwrap()).unwrap().uri().clone() |
819 | | } |
820 | | |
821 | | #[test] |
822 | | fn test_http() { |
823 | | let target = "http://example.domain/"; |
824 | | let p = Proxy::http(target).unwrap().into_matcher(); |
825 | | |
826 | | let http = "http://hyper.rs"; |
827 | | let other = "https://hyper.rs"; |
828 | | |
829 | | assert_eq!(intercepted_uri(&p, http), target); |
830 | | assert!(p.intercept(&url(other)).is_none()); |
831 | | } |
832 | | |
833 | | #[test] |
834 | | fn test_https() { |
835 | | let target = "http://example.domain/"; |
836 | | let p = Proxy::https(target).unwrap().into_matcher(); |
837 | | |
838 | | let http = "http://hyper.rs"; |
839 | | let other = "https://hyper.rs"; |
840 | | |
841 | | assert!(p.intercept(&url(http)).is_none()); |
842 | | assert_eq!(intercepted_uri(&p, other), target); |
843 | | } |
844 | | |
845 | | #[test] |
846 | | fn test_all() { |
847 | | let target = "http://example.domain/"; |
848 | | let p = Proxy::all(target).unwrap().into_matcher(); |
849 | | |
850 | | let http = "http://hyper.rs"; |
851 | | let https = "https://hyper.rs"; |
852 | | // no longer supported |
853 | | //let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs"; |
854 | | |
855 | | assert_eq!(intercepted_uri(&p, http), target); |
856 | | assert_eq!(intercepted_uri(&p, https), target); |
857 | | //assert_eq!(intercepted_uri(&p, other), target); |
858 | | } |
859 | | |
860 | | #[test] |
861 | | fn test_custom() { |
862 | | let target1 = "http://example.domain/"; |
863 | | let target2 = "https://example.domain/"; |
864 | | let p = Proxy::custom(move |url| { |
865 | | if url.host_str() == Some("hyper.rs") { |
866 | | target1.parse().ok() |
867 | | } else if url.scheme() == "http" { |
868 | | target2.parse().ok() |
869 | | } else { |
870 | | None::<Url> |
871 | | } |
872 | | }) |
873 | | .into_matcher(); |
874 | | |
875 | | let http = "http://seanmonstar.com"; |
876 | | let https = "https://hyper.rs"; |
877 | | let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com"; |
878 | | |
879 | | assert_eq!(intercepted_uri(&p, http), target2); |
880 | | assert_eq!(intercepted_uri(&p, https), target1); |
881 | | assert!(p.intercept(&url(other)).is_none()); |
882 | | } |
883 | | |
884 | | #[test] |
885 | | fn test_standard_with_custom_auth_header() { |
886 | | let target = "http://example.domain/"; |
887 | | let p = Proxy::all(target) |
888 | | .unwrap() |
889 | | .custom_http_auth(http::HeaderValue::from_static("testme")) |
890 | | .into_matcher(); |
891 | | |
892 | | let got = p.intercept(&url("http://anywhere.local")).unwrap(); |
893 | | let auth = got.basic_auth().unwrap(); |
894 | | assert_eq!(auth, "testme"); |
895 | | } |
896 | | |
897 | | #[test] |
898 | | fn test_custom_with_custom_auth_header() { |
899 | | let target = "http://example.domain/"; |
900 | | let p = Proxy::custom(move |_| target.parse::<Url>().ok()) |
901 | | .custom_http_auth(http::HeaderValue::from_static("testme")) |
902 | | .into_matcher(); |
903 | | |
904 | | let got = p.intercept(&url("http://anywhere.local")).unwrap(); |
905 | | let auth = got.basic_auth().unwrap(); |
906 | | assert_eq!(auth, "testme"); |
907 | | } |
908 | | |
909 | | #[test] |
910 | | fn test_maybe_has_http_auth() { |
911 | | let m = Proxy::all("https://letme:in@yo.local") |
912 | | .unwrap() |
913 | | .into_matcher(); |
914 | | assert!(!m.maybe_has_http_auth(), "https always tunnels"); |
915 | | |
916 | | let m = Proxy::all("http://letme:in@yo.local") |
917 | | .unwrap() |
918 | | .into_matcher(); |
919 | | assert!(m.maybe_has_http_auth(), "http forwards"); |
920 | | } |
921 | | |
922 | | #[test] |
923 | | fn test_socks_proxy_default_port() { |
924 | | { |
925 | | let m = Proxy::all("socks5://example.com").unwrap().into_matcher(); |
926 | | |
927 | | let http = "http://hyper.rs"; |
928 | | let https = "https://hyper.rs"; |
929 | | |
930 | | assert_eq!(intercepted_uri(&m, http).port_u16(), Some(1080)); |
931 | | assert_eq!(intercepted_uri(&m, https).port_u16(), Some(1080)); |
932 | | |
933 | | // custom port |
934 | | let m = Proxy::all("socks5://example.com:1234") |
935 | | .unwrap() |
936 | | .into_matcher(); |
937 | | |
938 | | assert_eq!(intercepted_uri(&m, http).port_u16(), Some(1234)); |
939 | | assert_eq!(intercepted_uri(&m, https).port_u16(), Some(1234)); |
940 | | } |
941 | | } |
942 | | } |