use ipnet::IpNet;
use std::{env, net::IpAddr};
#[derive(Clone, Debug)]
enum Ip {
Address(IpAddr),
Network(IpNet),
}
#[derive(Clone, Debug, Default)]
struct IpMatcher(Vec<Ip>);
#[derive(Clone, Debug, Default)]
struct DomainMatcher(Vec<String>);
#[derive(Clone, Debug, Default)]
struct NoProxy {
ips: IpMatcher,
domains: DomainMatcher,
}
pub struct Proxy {
http_proxy: Option<String>,
https_proxy: Option<String>,
no_proxy: Option<NoProxy>,
}
impl Proxy {
pub fn new() -> Self {
let http_proxy = env::var("http_proxy")
.or_else(|_| env::var("HTTP_PROXY"))
.ok();
let https_proxy = env::var("https_proxy")
.or_else(|_| env::var("HTTPS_PROXY"))
.ok();
let no_proxy = NoProxy::new();
Self {
http_proxy,
https_proxy,
no_proxy,
}
}
pub fn http(&self, host: &str) -> Option<&str> {
if let Some(no_proxy) = &self.no_proxy {
if no_proxy.contains(host) {
return None;
}
}
self.http_proxy.as_deref()
}
pub fn https(&self, host: &str) -> Option<&str> {
if let Some(no_proxy) = &self.no_proxy {
if no_proxy.contains(host) {
return None;
}
}
self.https_proxy.as_deref()
}
}
impl NoProxy {
fn new() -> Option<Self> {
let raw = env::var("no_proxy")
.or_else(|_| env::var("NO_PROXY"))
.unwrap_or_default();
if raw.is_empty() {
return None;
}
let mut ips = Vec::new();
let mut domains = Vec::new();
let parts = raw.split(',');
for part in parts {
match part.parse::<IpNet>() {
Ok(ip) => ips.push(Ip::Network(ip)),
Err(_) => match part.parse::<IpAddr>() {
Ok(addr) => ips.push(Ip::Address(addr)),
Err(_) => domains.push(part.to_owned()),
},
}
}
Some(NoProxy {
ips: IpMatcher(ips),
domains: DomainMatcher(domains),
})
}
fn contains(&self, host: &str) -> bool {
let host = if host.starts_with('[') {
let x: &[_] = &['[', ']'];
host.trim_matches(x)
} else {
host
};
match host.parse::<IpAddr>() {
Ok(ip) => self.ips.contains(ip),
Err(_) => self.domains.contains(host),
}
}
}
impl IpMatcher {
fn contains(&self, addr: IpAddr) -> bool {
for ip in self.0.iter() {
match ip {
Ip::Address(address) => {
if &addr == address {
return true;
}
}
Ip::Network(net) => {
if net.contains(&addr) {
return true;
}
}
}
}
false
}
}
impl DomainMatcher {
fn contains(&self, domain: &str) -> bool {
for d in self.0.iter() {
if (d.starts_with('.') && domain.ends_with(d.get(1..).unwrap_or_default()))
|| d == domain
{
return true;
}
}
false
}
}