#[cfg(test)]
use lazy_static::lazy_static;
use log::warn;
use std::env::var_os;
use url::Url;
macro_rules! env_var_pair {
($lc_var:expr, $uc_var:expr) => {
var_os($lc_var).or_else(|| var_os($uc_var))
.map(|v| v.to_str()
.map(str::to_string)
.or_else(|| {
warn!("non UTF-8 content in {}/{}", $lc_var, $uc_var);
None
}))
.unwrap_or_else(|| None)
};
}
fn matches_no_proxy(url: &Url) -> bool {
if let Some(no_proxy) = env_var_pair!("no_proxy", "NO_PROXY") {
if no_proxy == "*" {
return true;
}
if let Some(host) = url.host_str() {
'elems: for elem in no_proxy.split(|c| c == ',' || c == ' ') {
if elem == "" || elem == "." {
continue;
}
let ch1 = elem.chars().next().unwrap();
let mut elem_iter = elem.chars();
if ch1 == '.' {
elem_iter.next();
}
let mut elem_iter = elem_iter.rev();
let mut host_iter = host.chars().rev();
while let Some(elem_ch) = elem_iter.next() {
if let Some(host_ch) = host_iter.next() {
let host_ch = host_ch as u32;
let elem_ch = match elem_ch as u32 {
uppercase @ 0x41 ..= 0x5a => uppercase + 0x20,
anything => anything
};
if elem_ch == host_ch {
continue;
}
continue 'elems;
} else {
continue 'elems;
}
}
match host_iter.next() {
None => return true,
Some(host_ch) if host_ch == '.' => return true,
_ => ()
}
}
}
}
false
}
pub struct ProxyUrl(Option<String>, Option<u16>);
impl ProxyUrl {
pub fn raw_value(self) -> Option<String> {
self.0
}
pub fn is_none(self) -> bool {
self.0.is_none()
}
pub fn with_default_port(self, port: u16) -> Self {
ProxyUrl(self.0, Some(port))
}
pub fn with_no_default_port(self) -> Self {
ProxyUrl(self.0, None)
}
pub fn to_url(self) -> Option<Url> {
let mut orig_scheme = self.0.as_ref().map(|s|
if s.starts_with("http://") {
Some("http")
} else if s.starts_with("https://") {
Some("https")
} else {
None
}
).unwrap_or(None);
if let Some(Ok(mut url)) = self.0.map(|mut s| {
if !s.contains("://") {
s.insert_str(0, "http://");
orig_scheme = Some("http");
}
if orig_scheme.is_some() {
s = s.replacen("http", "xttp", 1);
}
Url::parse(&s).map_err(|e| {
warn!("url parse error: {}", e);
e
})
}) {
if url.host_str().is_none() {
warn!("host part of the URL is empty");
return None;
}
if let Some(orig_scheme) = orig_scheme {
if url.set_scheme(orig_scheme).is_err() {
warn!("could not set URL scheme back to {}", orig_scheme);
return None;
}
}
if url.port().is_some() {
return Some(url);
} else if self.1.is_none() {
warn!("the port of the URL is unknown");
return None;
}
match url.set_port(self.1) {
Ok(_) => return Some(url),
Err(_) => warn!("could not set URL port"),
}
}
None
}
pub fn host_port(self) -> Option<(String, u16)> {
self.to_url().map(|u| (u.host_str().expect("host_str").to_string(), u.port().expect("port")))
}
pub fn to_string(self) -> Option<String> {
self.to_url().map(Url::into_string)
}
}
pub fn for_url(url: &Url) -> ProxyUrl {
if matches_no_proxy(url) {
return ProxyUrl(None, None);
}
let maybe_https_proxy = env_var_pair!("https_proxy", "HTTPS_PROXY");
let maybe_ftp_proxy = env_var_pair!("ftp_proxy", "FTP_PROXY");
let maybe_http_proxy = env_var_pair!("http_proxy", "");
let maybe_all_proxy = env_var_pair!("all_proxy", "ALL_PROXY");
let url_value = match url.scheme() {
"https" => maybe_https_proxy.or(maybe_all_proxy),
"http" => maybe_http_proxy.or(maybe_all_proxy),
"ftp" => maybe_ftp_proxy.or(maybe_all_proxy),
_ => maybe_all_proxy,
};
ProxyUrl(url_value, Some(8080))
}
pub fn for_url_str<S: AsRef<str>>(s: S) -> ProxyUrl {
let url = match Url::parse(s.as_ref()) {
Ok(url) => url,
Err(e) => {
warn!("error parsing '{}' as Url: {}", s.as_ref(), e);
return ProxyUrl(None, None);
},
};
for_url(&url)
}
#[cfg(test)]
mod tests {
use std::env::{remove_var, set_var};
use std::sync::Mutex;
use super::*;
use url::Url;
lazy_static! {
static ref LOCK: Mutex<()> = Mutex::new(());
}
fn scrub_env() {
remove_var("http_proxy");
remove_var("https_proxy");
remove_var("HTTPS_PROXY");
remove_var("ftp_proxy");
remove_var("FTP_PROXY");
remove_var("all_proxy");
remove_var("ALL_PROXY");
remove_var("no_proxy");
remove_var("NO_PROXY");
}
#[test]
fn no_proxy_simple_name() {
let _l = LOCK.lock();
scrub_env();
set_var("no_proxy", "example.org");
set_var("http_proxy", "http://proxy.example.com:8080");
let u = Url::parse("http://example.org").ok().unwrap();
assert!(for_url(&u).is_none());
assert!(for_url_str("http://example.org").is_none());
}
#[test]
fn no_proxy_global() {
let _l = LOCK.lock();
scrub_env();
set_var("no_proxy", "*");
set_var("http_proxy", "http://proxy.example.com:8080");
let u = Url::parse("http://example.org").ok().unwrap();
assert!(for_url(&u).is_none());
assert!(for_url_str("http://example.org").is_none());
}
#[test]
fn no_proxy_subdomain() {
let _l = LOCK.lock();
scrub_env();
set_var("no_proxy", "example.org");
set_var("http_proxy", "http://proxy.example.com:8080");
let u = Url::parse("http://www.example.org").ok().unwrap();
assert!(for_url(&u).is_none());
}
#[test]
fn no_proxy_subdomain_dot() {
let _l = LOCK.lock();
scrub_env();
set_var("no_proxy", ".example.org");
set_var("http_proxy", "http://proxy.example.com:8080");
let u = Url::parse("http://www.example.org").ok().unwrap();
assert!(for_url(&u).is_none());
assert!(for_url_str("http://www.example.org").is_none());
}
#[test]
fn no_proxy_multiple_list() {
let _l = LOCK.lock();
scrub_env();
set_var("no_proxy", "com, .example.org, net");
set_var("http_proxy", "http://proxy.example.com:8080");
let u = Url::parse("http://www.example.org").ok().unwrap();
assert!(for_url(&u).is_none());
assert!(for_url_str("http://www.example.org").is_none());
}
#[test]
fn http_proxy_specific() {
let _l = LOCK.lock();
scrub_env();
set_var("http_proxy", "http://proxy.example.com:8080");
set_var("all_proxy", "http://proxy.example.org:8081");
let u = Url::parse("http://www.example.org").ok().unwrap();
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 8080)));
assert_eq!(
for_url_str("http://www.example.org").to_string(),
Some("http://proxy.example.com:8080/".to_string())
);
}
#[test]
fn default_proxy_url_scheme() {
let _l = LOCK.lock();
scrub_env();
set_var("http_proxy", "proxy.example.com:8080");
let u = Url::parse("http://www.example.org").ok().unwrap();
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 8080)));
assert_eq!(
for_url_str("http://www.example.org").to_string(),
Some("http://proxy.example.com:8080/".to_string())
);
}
#[test]
fn proxy_url_with_explicit_scheme_port() {
let _l = LOCK.lock();
scrub_env();
set_var("http_proxy", "http://proxy.example.com:80");
let u = Url::parse("http://www.example.org").ok().unwrap();
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 80)));
assert_eq!(
for_url_str("http://www.example.org").to_string(),
Some("http://proxy.example.com:80/".to_string())
);
set_var("http_proxy", "https://proxy.example.com:443");
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 443)));
assert_eq!(
for_url_str("http://www.example.org").to_string(),
Some("https://proxy.example.com:443/".to_string())
);
}
#[test]
fn http_proxy_fallback() {
let _l = LOCK.lock();
scrub_env();
set_var("ALL_PROXY", "http://proxy.example.com:8080");
let u = Url::parse("http://www.example.org").ok().unwrap();
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 8080)));
assert_eq!(
for_url_str("http://www.example.org").to_string(),
Some("http://proxy.example.com:8080/".to_string())
);
set_var("all_proxy", "http://proxy.example.org:8081");
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.org".to_string(), 8081)));
assert_eq!(
for_url_str("http://www.example.org").to_string(),
Some("http://proxy.example.org:8081/".to_string())
);
}
#[test]
fn https_proxy_specific() {
let _l = LOCK.lock();
scrub_env();
set_var("HTTPS_PROXY", "http://proxy.example.com:8080");
set_var("all_proxy", "http://proxy.example.org:8081");
let u = Url::parse("https://www.example.org").ok().unwrap();
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 8080)));
assert_eq!(
for_url_str("https://www.example.org").to_string(),
Some("http://proxy.example.com:8080/".to_string())
);
set_var("https_proxy", "http://proxy.example.com:8082");
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 8082)));
assert_eq!(
for_url_str("https://www.example.org").to_string(),
Some("http://proxy.example.com:8082/".to_string())
);
}
#[test]
fn https_proxy_fallback() {
let _l = LOCK.lock();
scrub_env();
set_var("ALL_PROXY", "http://proxy.example.org:8081");
let u = Url::parse("ftp://www.example.org").ok().unwrap();
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.org".to_string(), 8081)));
assert_eq!(
for_url_str("https://www.example.org").to_string(),
Some("http://proxy.example.org:8081/".to_string())
);
set_var("all_proxy", "http://proxy.example.org:8082");
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.org".to_string(), 8082)));
assert_eq!(
for_url_str("https://www.example.org").to_string(),
Some("http://proxy.example.org:8082/".to_string())
);
}
#[test]
fn ftp_proxy_specific() {
let _l = LOCK.lock();
scrub_env();
set_var("FTP_PROXY", "http://proxy.example.com:8080");
set_var("all_proxy", "http://proxy.example.org:8081");
let u = Url::parse("ftp://www.example.org").ok().unwrap();
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 8080)));
assert_eq!(
for_url_str("ftp://www.example.org").to_string(),
Some("http://proxy.example.com:8080/".to_string())
);
set_var("ftp_proxy", "http://proxy.example.com:8082");
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 8082)));
assert_eq!(
for_url_str("ftp://www.example.org").to_string(),
Some("http://proxy.example.com:8082/".to_string())
);
}
#[test]
fn ftp_proxy_fallback() {
let _l = LOCK.lock();
scrub_env();
set_var("ALL_PROXY", "http://proxy.example.org:8081");
let u = Url::parse("ftp://www.example.org").ok().unwrap();
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.org".to_string(), 8081)));
assert_eq!(
for_url_str("ftp://www.example.org").to_string(),
Some("http://proxy.example.org:8081/".to_string())
);
set_var("all_proxy", "http://proxy.example.org:8082");
assert_eq!(for_url(&u).host_port(), Some(("proxy.example.org".to_string(), 8082)));
assert_eq!(
for_url_str("ftp://www.example.org").to_string(),
Some("http://proxy.example.org:8082/".to_string())
);
}
}