Fix detection of system proxy from Windows registry (#1005)
This commit is contained in:
		
							
								
								
									
										134
									
								
								src/proxy.rs
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								src/proxy.rs
									
									
									
									
									
								
							| @@ -212,7 +212,7 @@ impl Proxy { | ||||
|  | ||||
|     pub(crate) fn system() -> Proxy { | ||||
|         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 { | ||||
|             Proxy::new(Intercept::System(SYS_PROXIES.clone())) | ||||
|         }; | ||||
| @@ -578,6 +578,7 @@ impl fmt::Debug for ProxyScheme { | ||||
| } | ||||
|  | ||||
| type SystemProxyMap = HashMap<String, ProxyScheme>; | ||||
| type RegistryProxyValues = (u32, String); | ||||
|  | ||||
| #[derive(Clone, Debug)] | ||||
| enum Intercept { | ||||
| @@ -667,7 +668,7 @@ impl Dst for Uri { | ||||
| } | ||||
|  | ||||
| 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. | ||||
| @@ -679,7 +680,9 @@ lazy_static! { | ||||
| /// Returns: | ||||
| ///     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")} | ||||
| 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(); | ||||
|  | ||||
|     // 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() { | ||||
|             // 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 | ||||
| @@ -737,7 +742,7 @@ fn is_cgi() -> bool { | ||||
| } | ||||
|  | ||||
| #[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 internet_setting: RegKey = | ||||
|         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_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 { | ||||
|         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(); | ||||
|             match protocol_parts.as_slice() { | ||||
|                 [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 | ||||
| @@ -779,8 +809,26 @@ fn get_from_registry_impl() -> Result<SystemProxyMap, Box<dyn Error>> { | ||||
| } | ||||
|  | ||||
| #[cfg(target_os = "windows")] | ||||
| fn get_from_registry() -> SystemProxyMap { | ||||
|     get_from_registry_impl().unwrap_or(HashMap::new()) | ||||
| fn contains_type_prefix(address: &str) -> bool { | ||||
|     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)] | ||||
| @@ -913,13 +961,13 @@ mod tests { | ||||
|  | ||||
|         // Mock ENV, get the results, before doing assertions | ||||
|         // 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. | ||||
|         env::set_var("http_proxy", "123465"); | ||||
|         let invalid_proxies = get_sys_proxies(); | ||||
|         let invalid_proxies = get_sys_proxies(None); | ||||
|         // set valid proxy | ||||
|         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 | ||||
|         drop(_g1); | ||||
| @@ -935,6 +983,46 @@ mod tests { | ||||
|         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] | ||||
|     fn test_get_sys_proxies_in_cgi() { | ||||
|         // Stop other threads from modifying process-global ENV while we are. | ||||
| @@ -947,11 +1035,11 @@ mod tests { | ||||
|         // to avoid assert! -> panic! -> Mutex Poisoned. | ||||
|         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 | ||||
|         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 | ||||
|         drop(_g1); | ||||
| @@ -982,7 +1070,7 @@ mod tests { | ||||
|         ); | ||||
|  | ||||
|         // 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(); | ||||
|  | ||||
|         assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target); | ||||
| @@ -1015,7 +1103,7 @@ mod tests { | ||||
|         let domain = "lower.case"; | ||||
|         env::set_var("no_proxy", domain); | ||||
|         // 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(); | ||||
|         assert_eq!( | ||||
|             p.no_proxy.expect("should have a no proxy set").domains.0[0], | ||||
| @@ -1027,7 +1115,7 @@ mod tests { | ||||
|         let domain = "upper.case"; | ||||
|         env::set_var("NO_PROXY", domain); | ||||
|         // 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(); | ||||
|         assert_eq!( | ||||
|             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); | ||||
|  | ||||
|         // 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(); | ||||
|         assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created"); | ||||
|  | ||||
| @@ -1055,6 +1143,18 @@ mod tests { | ||||
|         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 | ||||
|     /// when dropped. | ||||
|     fn env_guard(name: impl Into<String>) -> EnvGuard { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user