use crate::url::Url;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProxyChoice {
Direct,
Proxy(String),
}
pub trait ProxyResolver: Send + Sync + std::fmt::Debug {
fn resolve(&self, url: &Url) -> ProxyChoice;
}
#[derive(Debug, Clone, Default)]
pub struct EnvProxyResolver;
fn env_any(names: &[&str]) -> Option<String> {
names
.iter()
.find_map(|n| std::env::var(n).ok().filter(|v| !v.is_empty()))
}
impl ProxyResolver for EnvProxyResolver {
fn resolve(&self, url: &Url) -> ProxyChoice {
if let Some(no) = env_any(&["NO_PROXY", "no_proxy"]) {
let host = url.host.to_ascii_lowercase();
for entry in no.split(',').map(|e| e.trim()).filter(|e| !e.is_empty()) {
if entry == "*" {
return ProxyChoice::Direct;
}
let suffix = entry.trim_start_matches('.').to_ascii_lowercase();
if host == suffix || host.ends_with(&format!(".{suffix}")) {
return ProxyChoice::Direct;
}
}
}
let scheme_var = if url.scheme.eq_ignore_ascii_case("https") {
env_any(&["HTTPS_PROXY", "https_proxy"])
} else {
env_any(&["HTTP_PROXY", "http_proxy"])
};
match scheme_var.or_else(|| env_any(&["ALL_PROXY", "all_proxy"])) {
Some(spec) => ProxyChoice::Proxy(spec),
None => ProxyChoice::Direct,
}
}
}
pub fn from_env() -> EnvProxyResolver {
EnvProxyResolver
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct Static(ProxyChoice);
impl ProxyResolver for Static {
fn resolve(&self, _url: &Url) -> ProxyChoice {
self.0.clone()
}
}
#[test]
fn static_resolver_returns_choice() {
let u = Url::parse("http://example.com/").unwrap();
assert_eq!(Static(ProxyChoice::Direct).resolve(&u), ProxyChoice::Direct);
assert_eq!(
Static(ProxyChoice::Proxy("http://p:8080".into())).resolve(&u),
ProxyChoice::Proxy("http://p:8080".into())
);
}
}