asimov_http/proxy/
hyper.rs1use super::{Error, OriginProxy, bypasses_all};
4use hyper_util::client::proxy::matcher::Matcher;
5use url::Url;
6
7impl OriginProxy {
8 pub fn proxy_for_hyper(&self) -> Result<Option<&Url>, Error> {
21 let Some(proxy) = self.proxy_url() else {
22 return Ok(None);
23 };
24 if bypasses_all(self.no_proxy()) {
25 return Ok(None);
26 }
27 let uri = self
28 .origin()
29 .ascii_serialization()
30 .parse::<http::Uri>()
31 .map_err(|_| Error::InvalidOrigin("origin cannot be represented as an HTTP URI"))?;
32 let matcher = Matcher::builder()
35 .all("http://proxy.invalid")
36 .no(self.no_proxy())
37 .build();
38 Ok(matcher.intercept(&uri).is_some().then_some(proxy))
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::proxy::{ProxyMode, ProxyOptions};
46
47 #[test]
48 fn bypass_rules_apply_before_exposing_an_endpoint() {
49 for (target, bypass, direct) in [
50 ("https://example.com/", ".EXAMPLE.COM", true),
51 ("https://api.example.com/", "example.com", true),
52 ("https://notexample.com/", "example.com", false),
53 ("https://127.0.0.1/", "127.0.0.0/8", true),
54 ("https://[::1]/", "::1", true),
55 ("https://[2001:db8::1]/", "2001:db8::/32", true),
56 ("https://127.0.0.1/", " * ", true),
57 ("https://[::1]/", "*", true),
58 ("https://localhost/", "127.0.0.0/8", false),
59 ("https://example.com:8443/", "example.com:8443", false),
60 ("https://example.com/", "", false),
61 ] {
62 for endpoint in ["http://user:pass@proxy.invalid", "socks5h://proxy.invalid"] {
63 let target = Url::parse(target).unwrap();
64 let policy = OriginProxy::resolve(
65 &target,
66 &ProxyOptions {
67 mode: ProxyMode::Explicit(endpoint.into()),
68 no_proxy: Some(bypass.into()),
69 },
70 )
71 .unwrap();
72 assert_eq!(
73 policy.proxy_for_hyper().unwrap().is_none(),
74 direct,
75 "{target}, {bypass}"
76 );
77 if !direct {
78 assert_eq!(policy.proxy_for_hyper().unwrap(), policy.proxy_url());
79 }
80 }
81 }
82 }
83}