Fix detection of system proxy from Windows registry (#1005)

This commit is contained in:
fuyu
2020-08-19 11:38:21 -07:00
committed by GitHub
parent 512fb97ffc
commit 9e23103371
4 changed files with 135 additions and 19 deletions

View File

@@ -212,7 +212,7 @@ impl Proxy {
pub(crate) fn system() -> Proxy { pub(crate) fn system() -> Proxy {
let mut proxy = if cfg!(feature = "__internal_proxy_sys_no_cache") { let mut proxy = if cfg!(feature = "__internal_proxy_sys_no_cache") {
Proxy::new(Intercept::System(Arc::new(get_sys_proxies()))) Proxy::new(Intercept::System(Arc::new(get_sys_proxies(get_from_registry()))))
} else { } else {
Proxy::new(Intercept::System(SYS_PROXIES.clone())) Proxy::new(Intercept::System(SYS_PROXIES.clone()))
}; };
@@ -578,6 +578,7 @@ impl fmt::Debug for ProxyScheme {
} }
type SystemProxyMap = HashMap<String, ProxyScheme>; type SystemProxyMap = HashMap<String, ProxyScheme>;
type RegistryProxyValues = (u32, String);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum Intercept { enum Intercept {
@@ -667,7 +668,7 @@ impl Dst for Uri {
} }
lazy_static! { lazy_static! {
static ref SYS_PROXIES: Arc<SystemProxyMap> = Arc::new(get_sys_proxies()); static ref SYS_PROXIES: Arc<SystemProxyMap> = Arc::new(get_sys_proxies(get_from_registry()));
} }
/// Get system proxies information. /// Get system proxies information.
@@ -679,7 +680,9 @@ lazy_static! {
/// Returns: /// Returns:
/// System proxies information as a hashmap like /// System proxies information as a hashmap like
/// {"http": Url::parse("http://127.0.0.1:80"), "https": Url::parse("https://127.0.0.1:80")} /// {"http": Url::parse("http://127.0.0.1:80"), "https": Url::parse("https://127.0.0.1:80")}
fn get_sys_proxies() -> SystemProxyMap { fn get_sys_proxies(
#[cfg_attr(not(target_os = "windows"), allow(unused_variables))] registry_values: Option<RegistryProxyValues>,
) -> SystemProxyMap {
let proxies = get_from_environment(); let proxies = get_from_environment();
// TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed // TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed
@@ -687,7 +690,9 @@ fn get_sys_proxies() -> SystemProxyMap {
{ {
if proxies.is_empty() { if proxies.is_empty() {
// don't care errors if can't get proxies from registry, just return an empty HashMap. // don't care errors if can't get proxies from registry, just return an empty HashMap.
return get_from_registry(); if let Some(registry_values) = registry_values {
return parse_registry_values(registry_values);
}
} }
} }
proxies proxies
@@ -737,7 +742,7 @@ fn is_cgi() -> bool {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn get_from_registry_impl() -> Result<SystemProxyMap, Box<dyn Error>> { fn get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER); let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let internet_setting: RegKey = let internet_setting: RegKey =
hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?; hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
@@ -745,6 +750,23 @@ fn get_from_registry_impl() -> Result<SystemProxyMap, Box<dyn Error>> {
let proxy_enable: u32 = internet_setting.get_value("ProxyEnable")?; let proxy_enable: u32 = internet_setting.get_value("ProxyEnable")?;
let proxy_server: String = internet_setting.get_value("ProxyServer")?; let proxy_server: String = internet_setting.get_value("ProxyServer")?;
Ok((proxy_enable, proxy_server))
}
#[cfg(target_os = "windows")]
fn get_from_registry() -> Option<RegistryProxyValues> {
get_from_registry_impl().ok()
}
#[cfg(not(target_os = "windows"))]
fn get_from_registry() -> Option<RegistryProxyValues> {
None
}
#[cfg(target_os = "windows")]
fn parse_registry_values_impl(registry_values: RegistryProxyValues) -> Result<SystemProxyMap, Box<dyn Error>> {
let (proxy_enable, proxy_server) = registry_values;
if proxy_enable == 0 { if proxy_enable == 0 {
return Ok(HashMap::new()); return Ok(HashMap::new());
} }
@@ -756,7 +778,15 @@ fn get_from_registry_impl() -> Result<SystemProxyMap, Box<dyn Error>> {
let protocol_parts: Vec<&str> = p.split("=").collect(); let protocol_parts: Vec<&str> = p.split("=").collect();
match protocol_parts.as_slice() { match protocol_parts.as_slice() {
[protocol, address] => { [protocol, address] => {
insert_proxy(&mut proxies, *protocol, String::from(*address)); // See if address has a type:// prefix
let address = if !contains_type_prefix(*address) {
format!("{}://{}", protocol, address)
}
else {
String::from(*address)
};
insert_proxy(&mut proxies, *protocol, address);
} }
_ => { _ => {
// Contains invalid protocol setting, just break the loop // Contains invalid protocol setting, just break the loop
@@ -779,8 +809,26 @@ fn get_from_registry_impl() -> Result<SystemProxyMap, Box<dyn Error>> {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn get_from_registry() -> SystemProxyMap { fn contains_type_prefix(address: &str) -> bool {
get_from_registry_impl().unwrap_or(HashMap::new()) if let Some(indice) = address.find("://") {
if indice == 0 {
false
}
else {
let prefix = &address[..indice];
let contains_banned = prefix.contains(|c| c == ':' || c == '/');
!contains_banned
}
}
else {
false
}
}
#[cfg(target_os = "windows")]
fn parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap {
parse_registry_values_impl(registry_values).unwrap_or(HashMap::new())
} }
#[cfg(test)] #[cfg(test)]
@@ -913,13 +961,13 @@ mod tests {
// Mock ENV, get the results, before doing assertions // Mock ENV, get the results, before doing assertions
// to avoid assert! -> panic! -> Mutex Poisoned. // to avoid assert! -> panic! -> Mutex Poisoned.
let baseline_proxies = get_sys_proxies(); let baseline_proxies = get_sys_proxies(None);
// the system proxy setting url is invalid. // the system proxy setting url is invalid.
env::set_var("http_proxy", "123465"); env::set_var("http_proxy", "123465");
let invalid_proxies = get_sys_proxies(); let invalid_proxies = get_sys_proxies(None);
// set valid proxy // set valid proxy
env::set_var("http_proxy", "http://127.0.0.1/"); env::set_var("http_proxy", "http://127.0.0.1/");
let valid_proxies = get_sys_proxies(); let valid_proxies = get_sys_proxies(None);
// reset user setting when guards drop // reset user setting when guards drop
drop(_g1); drop(_g1);
@@ -935,6 +983,46 @@ mod tests {
assert_eq!(p.host(), "127.0.0.1"); assert_eq!(p.host(), "127.0.0.1");
} }
#[cfg(target_os = "windows")]
#[test]
fn test_get_sys_proxies_registry_parsing() {
// Stop other threads from modifying process-global ENV while we are.
let _lock = ENVLOCK.lock();
// save system setting first.
let _g1 = env_guard("HTTP_PROXY");
let _g2 = env_guard("http_proxy");
// Mock ENV, get the results, before doing assertions
// to avoid assert! -> panic! -> Mutex Poisoned.
let baseline_proxies = get_sys_proxies(None);
// the system proxy in the registry has been disabled
let disabled_proxies = get_sys_proxies(Some((0, String::from("http://127.0.0.1/"))));
// set valid proxy
let valid_proxies = get_sys_proxies(Some((1, String::from("http://127.0.0.1/"))));
let multiple_proxies = get_sys_proxies(Some((1, String::from("http=127.0.0.1:8888;https=127.0.0.2:8888"))));
// reset user setting when guards drop
drop(_g1);
drop(_g2);
// Let other threads run now
drop(_lock);
assert_eq!(baseline_proxies.contains_key("http"), false);
assert_eq!(disabled_proxies.contains_key("http"), false);
let p = &valid_proxies["http"];
assert_eq!(p.scheme(), "http");
assert_eq!(p.host(), "127.0.0.1");
let p = &multiple_proxies["http"];
assert_eq!(p.scheme(), "http");
assert_eq!(p.host(), "127.0.0.1:8888");
let p = &multiple_proxies["https"];
assert_eq!(p.scheme(), "https");
assert_eq!(p.host(), "127.0.0.2:8888");
}
#[test] #[test]
fn test_get_sys_proxies_in_cgi() { fn test_get_sys_proxies_in_cgi() {
// Stop other threads from modifying process-global ENV while we are. // Stop other threads from modifying process-global ENV while we are.
@@ -947,11 +1035,11 @@ mod tests {
// to avoid assert! -> panic! -> Mutex Poisoned. // to avoid assert! -> panic! -> Mutex Poisoned.
env::set_var("HTTP_PROXY", "http://evil/"); env::set_var("HTTP_PROXY", "http://evil/");
let baseline_proxies = get_sys_proxies(); let baseline_proxies = get_sys_proxies(None);
// set like we're in CGI // set like we're in CGI
env::set_var("REQUEST_METHOD", "GET"); env::set_var("REQUEST_METHOD", "GET");
let cgi_proxies = get_sys_proxies(); let cgi_proxies = get_sys_proxies(None);
// reset user setting when guards drop // reset user setting when guards drop
drop(_g1); drop(_g1);
@@ -982,7 +1070,7 @@ mod tests {
); );
// Manually construct this so we aren't use the cache // Manually construct this so we aren't use the cache
let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies()))); let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
p.no_proxy = NoProxy::new(); p.no_proxy = NoProxy::new();
assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target); assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
@@ -1015,7 +1103,7 @@ mod tests {
let domain = "lower.case"; let domain = "lower.case";
env::set_var("no_proxy", domain); env::set_var("no_proxy", domain);
// Manually construct this so we aren't use the cache // Manually construct this so we aren't use the cache
let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies()))); let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
p.no_proxy = NoProxy::new(); p.no_proxy = NoProxy::new();
assert_eq!( assert_eq!(
p.no_proxy.expect("should have a no proxy set").domains.0[0], p.no_proxy.expect("should have a no proxy set").domains.0[0],
@@ -1027,7 +1115,7 @@ mod tests {
let domain = "upper.case"; let domain = "upper.case";
env::set_var("NO_PROXY", domain); env::set_var("NO_PROXY", domain);
// Manually construct this so we aren't use the cache // Manually construct this so we aren't use the cache
let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies()))); let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
p.no_proxy = NoProxy::new(); p.no_proxy = NoProxy::new();
assert_eq!( assert_eq!(
p.no_proxy.expect("should have a no proxy set").domains.0[0], p.no_proxy.expect("should have a no proxy set").domains.0[0],
@@ -1041,7 +1129,7 @@ mod tests {
env::set_var("HTTP_PROXY", target); env::set_var("HTTP_PROXY", target);
// Manually construct this so we aren't use the cache // Manually construct this so we aren't use the cache
let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies()))); let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
p.no_proxy = NoProxy::new(); p.no_proxy = NoProxy::new();
assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created"); assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
@@ -1055,6 +1143,18 @@ mod tests {
drop(_lock); drop(_lock);
} }
#[cfg(target_os = "windows")]
#[test]
fn test_type_prefix_detection() {
assert!(!contains_type_prefix("test"));
assert!(!contains_type_prefix("://test"));
assert!(!contains_type_prefix("some:prefix://test"));
assert!(!contains_type_prefix("some/prefix://test"));
assert!(contains_type_prefix("http://test"));
assert!(contains_type_prefix("a://test"));
}
/// Guard an environment variable, resetting it to the original value /// Guard an environment variable, resetting it to the original value
/// when dropped. /// when dropped.
fn env_guard(name: impl Into<String>) -> EnvGuard { fn env_guard(name: impl Into<String>) -> EnvGuard {

View File

@@ -1,7 +1,12 @@
#[cfg(feature = "__tls")] #[cfg(feature = "__tls")]
#[tokio::test] #[tokio::test]
async fn test_badssl_modern() { async fn test_badssl_modern() {
let text = reqwest::get("https://mozilla-modern.badssl.com/") let text = reqwest::Client::builder()
.no_proxy()
.build()
.unwrap()
.get("https://mozilla-modern.badssl.com/")
.send()
.await .await
.unwrap() .unwrap()
.text() .text()
@@ -16,6 +21,7 @@ async fn test_badssl_modern() {
async fn test_rustls_badssl_modern() { async fn test_rustls_badssl_modern() {
let text = reqwest::Client::builder() let text = reqwest::Client::builder()
.use_rustls_tls() .use_rustls_tls()
.no_proxy()
.build() .build()
.unwrap() .unwrap()
.get("https://mozilla-modern.badssl.com/") .get("https://mozilla-modern.badssl.com/")
@@ -34,6 +40,7 @@ async fn test_rustls_badssl_modern() {
async fn test_badssl_self_signed() { async fn test_badssl_self_signed() {
let text = reqwest::Client::builder() let text = reqwest::Client::builder()
.danger_accept_invalid_certs(true) .danger_accept_invalid_certs(true)
.no_proxy()
.build() .build()
.unwrap() .unwrap()
.get("https://self-signed.badssl.com/") .get("https://self-signed.badssl.com/")
@@ -52,6 +59,7 @@ async fn test_badssl_self_signed() {
async fn test_badssl_wrong_host() { async fn test_badssl_wrong_host() {
let text = reqwest::Client::builder() let text = reqwest::Client::builder()
.danger_accept_invalid_hostnames(true) .danger_accept_invalid_hostnames(true)
.no_proxy()
.build() .build()
.unwrap() .unwrap()
.get("https://wrong.host.badssl.com/") .get("https://wrong.host.badssl.com/")

View File

@@ -28,7 +28,14 @@ async fn auto_headers() {
}); });
let url = format!("http://{}/1", server.addr()); let url = format!("http://{}/1", server.addr());
let res = reqwest::get(&url).await.unwrap(); let res = reqwest::Client::builder()
.no_proxy()
.build()
.unwrap()
.get(&url)
.send()
.await
.unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);

View File

@@ -76,6 +76,7 @@ async fn response_timeout() {
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.timeout(Duration::from_millis(500)) .timeout(Duration::from_millis(500))
.no_proxy()
.build() .build()
.unwrap(); .unwrap();