1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// This is free and unencumbered software released into the public domain.
use super::{Error, OriginProxy, bypasses_all};
use hyper_util::client::proxy::matcher::Matcher;
use url::Url;
impl OriginProxy {
/// Returns this origin's effective endpoint for a Hyper connector (`hyper`).
///
/// Applies NO_PROXY using the same matcher as reqwest, including the shared
/// wildcard fix for IP literals. Native exclusions were resolved earlier.
/// This does not perform backend environment/system discovery. The returned
/// URL retains credentials; use it only for [`Self::origin`]. The caller
/// owns CONNECT/SOCKS support, TLS, timeouts, and connection pooling.
///
/// # Errors
///
/// Returns [`Error::InvalidOrigin`] if the origin cannot be represented as
/// an HTTP URI for matching.
pub fn proxy_for_hyper(&self) -> Result<Option<&Url>, Error> {
let Some(proxy) = self.proxy_url() else {
return Ok(None);
};
if bypasses_all(self.no_proxy()) {
return Ok(None);
}
let uri = self
.origin()
.ascii_serialization()
.parse::<http::Uri>()
.map_err(|_| Error::InvalidOrigin("origin cannot be represented as an HTTP URI"))?;
// Use only the matcher's bypass parser. The sentinel is never connected
// to; endpoint selection and credential validation belong to adapters.
let matcher = Matcher::builder()
.all("http://proxy.invalid")
.no(self.no_proxy())
.build();
Ok(matcher.intercept(&uri).is_some().then_some(proxy))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::proxy::{ProxyMode, ProxyOptions};
#[test]
fn bypass_rules_apply_before_exposing_an_endpoint() {
for (target, bypass, direct) in [
("https://example.com/", ".EXAMPLE.COM", true),
("https://api.example.com/", "example.com", true),
("https://notexample.com/", "example.com", false),
("https://127.0.0.1/", "127.0.0.0/8", true),
("https://[::1]/", "::1", true),
("https://[2001:db8::1]/", "2001:db8::/32", true),
("https://127.0.0.1/", " * ", true),
("https://[::1]/", "*", true),
("https://localhost/", "127.0.0.0/8", false),
("https://example.com:8443/", "example.com:8443", false),
("https://example.com/", "", false),
] {
for endpoint in ["http://user:pass@proxy.invalid", "socks5h://proxy.invalid"] {
let target = Url::parse(target).unwrap();
let policy = OriginProxy::resolve(
&target,
&ProxyOptions {
mode: ProxyMode::Explicit(endpoint.into()),
no_proxy: Some(bypass.into()),
},
)
.unwrap();
assert_eq!(
policy.proxy_for_hyper().unwrap().is_none(),
direct,
"{target}, {bypass}"
);
if !direct {
assert_eq!(policy.proxy_for_hyper().unwrap(), policy.proxy_url());
}
}
}
}
}