1mod types;
4
5pub use types::{
6 IeProxyConfig, ProxyConfig, ProxyResolution, parse_proxy_bypass, parse_proxy_server,
7};
8
9use crate::error::{
10 InvalidParameterError, ProxyConfigError, ProxyError, ProxyResolutionError, RegistryError,
11 WindowsApiError,
12};
13use crate::registry::{self, Hive};
14use crate::{Error, Result};
15use windows::Win32::Foundation::{GetLastError, GlobalFree, HGLOBAL};
16use windows::Win32::Networking::WinHttp::{
17 WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_AUTO_DETECT_TYPE_DHCP,
18 WINHTTP_AUTO_DETECT_TYPE_DNS_A, WINHTTP_AUTOPROXY_AUTO_DETECT, WINHTTP_AUTOPROXY_CONFIG_URL,
19 WINHTTP_AUTOPROXY_OPTIONS, WINHTTP_CURRENT_USER_IE_PROXY_CONFIG, WINHTTP_PROXY_INFO,
20 WinHttpCloseHandle, WinHttpGetIEProxyConfigForCurrentUser, WinHttpGetProxyForUrl, WinHttpOpen,
21};
22use windows::core::{HSTRING, PCWSTR, PWSTR};
23
24const INTERNET_SETTINGS_PATH: &str = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings";
25const POLICIES_PATH: &str = r"SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings";
26
27pub fn get_system_proxy() -> Result<Option<ProxyConfig>> {
29 get_proxy_from_hive(Hive::LocalMachine)
30}
31
32pub fn get_user_proxy() -> Result<Option<ProxyConfig>> {
34 get_proxy_from_hive(Hive::CurrentUser)
35}
36
37pub fn get_effective_proxy() -> Result<Option<ProxyConfig>> {
39 if use_user_proxy_settings()? {
40 return get_user_proxy();
41 }
42 get_system_proxy()
43}
44
45pub fn get_ie_proxy_config() -> Result<IeProxyConfig> {
47 unsafe {
48 let mut config = WINHTTP_CURRENT_USER_IE_PROXY_CONFIG::default();
49 WinHttpGetIEProxyConfigForCurrentUser(&mut config).map_err(|err| {
50 Error::WindowsApi(WindowsApiError::with_context(
51 err,
52 "WinHttpGetIEProxyConfigForCurrentUser",
53 ))
54 })?;
55
56 let auto_config_url = take_optional_pwstr(config.lpszAutoConfigUrl)?;
57 let proxy = take_optional_pwstr(config.lpszProxy)?;
58 let proxy_bypass = take_optional_pwstr(config.lpszProxyBypass)?;
59
60 Ok(IeProxyConfig {
61 auto_detect: config.fAutoDetect.as_bool(),
62 auto_config_url,
63 proxy,
64 proxy_bypass,
65 })
66 }
67}
68
69pub fn get_proxy_for_url(url: &str) -> Result<Option<ProxyResolution>> {
71 if url.trim().is_empty() {
72 return Err(Error::InvalidParameter(InvalidParameterError::new(
73 "url",
74 "url cannot be empty",
75 )));
76 }
77
78 let ie_config = get_ie_proxy_config()?;
79
80 unsafe {
81 let session = WinHttpSession::new()?;
82 let mut proxy_info = WINHTTP_PROXY_INFO::default();
83
84 let mut options = WINHTTP_AUTOPROXY_OPTIONS::default();
85 if ie_config.auto_detect {
86 options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT;
87 options.dwAutoDetectFlags =
88 WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
89 }
90
91 if let Some(auto_url) = &ie_config.auto_config_url {
92 options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL;
93 let auto_url_wide = HSTRING::from(auto_url);
94 options.lpszAutoConfigUrl = PCWSTR(auto_url_wide.as_ptr());
95
96 let url_wide = HSTRING::from(url);
97 let result = WinHttpGetProxyForUrl(
98 session.handle,
99 PCWSTR(url_wide.as_ptr()),
100 &mut options,
101 &mut proxy_info,
102 );
103
104 if result.is_err() {
105 return Ok(None);
106 }
107 } else if ie_config.auto_detect {
108 let url_wide = HSTRING::from(url);
109 let result = WinHttpGetProxyForUrl(
110 session.handle,
111 PCWSTR(url_wide.as_ptr()),
112 &mut options,
113 &mut proxy_info,
114 );
115
116 if result.is_err() {
117 return Ok(None);
118 }
119 } else if let Some(proxy) = ie_config.proxy {
120 return Ok(Some(ProxyResolution::from_winhttp(
121 url,
122 Some(proxy),
123 ie_config.proxy_bypass,
124 false,
125 false,
126 )));
127 } else {
128 return Ok(None);
129 }
130
131 let proxy = take_optional_pwstr(proxy_info.lpszProxy)?;
132 let bypass = take_optional_pwstr(proxy_info.lpszProxyBypass)?;
133
134 Ok(Some(ProxyResolution::from_winhttp(
135 url,
136 proxy,
137 bypass,
138 ie_config.auto_detect,
139 ie_config.auto_config_url.is_some(),
140 )))
141 }
142}
143
144fn get_proxy_from_hive(hive: Hive) -> Result<Option<ProxyConfig>> {
145 let proxy_enabled =
146 read_u32_optional(hive, INTERNET_SETTINGS_PATH, "ProxyEnable")?.unwrap_or(0) == 1;
147
148 let server = read_string_optional(hive, INTERNET_SETTINGS_PATH, "ProxyServer")?
149 .filter(|value| !value.trim().is_empty());
150
151 let bypass = read_string_optional(hive, INTERNET_SETTINGS_PATH, "ProxyOverride")?;
152 let auto_detect =
153 read_u32_optional(hive, INTERNET_SETTINGS_PATH, "AutoDetect")?.unwrap_or(0) == 1;
154 let auto_config_url = read_string_optional(hive, INTERNET_SETTINGS_PATH, "AutoConfigURL")?;
155
156 if !proxy_enabled && !auto_detect && auto_config_url.is_none() {
157 return Ok(None);
158 }
159
160 if proxy_enabled {
161 if let Some(server) = server {
162 return Ok(Some(ProxyConfig::from_registry_values(
163 server,
164 bypass,
165 auto_detect,
166 auto_config_url,
167 )));
168 }
169
170 return Err(Error::Proxy(ProxyError::InvalidConfig(
171 ProxyConfigError::new("ProxyServer", "ProxyEnable is 1 but ProxyServer is missing"),
172 )));
173 }
174
175 Ok(Some(ProxyConfig {
176 server: String::new(),
177 bypass: parse_proxy_bypass(bypass.as_deref()),
178 by_scheme: Default::default(),
179 auto_detect,
180 auto_config_url,
181 }))
182}
183
184fn use_user_proxy_settings() -> Result<bool> {
185 Ok(
186 read_u32_optional(Hive::CurrentUser, POLICIES_PATH, "ProxySettingsPerUser")?.unwrap_or(0)
187 == 1,
188 )
189}
190
191fn read_string_optional(hive: Hive, path: &str, value_name: &str) -> Result<Option<String>> {
192 match registry::read_string(hive, path, value_name) {
193 Ok(value) => Ok(Some(value)),
194 Err(Error::Registry(RegistryError::KeyNotFound(_))) => Ok(None),
195 Err(Error::Registry(RegistryError::ValueNotFound(_))) => Ok(None),
196 Err(err) => Err(err),
197 }
198}
199
200fn read_u32_optional(hive: Hive, path: &str, value_name: &str) -> Result<Option<u32>> {
201 match registry::read_u32(hive, path, value_name) {
202 Ok(value) => Ok(Some(value)),
203 Err(Error::Registry(RegistryError::KeyNotFound(_))) => Ok(None),
204 Err(Error::Registry(RegistryError::ValueNotFound(_))) => Ok(None),
205 Err(err) => Err(err),
206 }
207}
208
209fn take_optional_pwstr(pwstr: PWSTR) -> Result<Option<String>> {
210 if pwstr.is_null() {
211 return Ok(None);
212 }
213
214 let mut len = 0usize;
215 unsafe {
216 while *pwstr.0.add(len) != 0 {
217 len += 1;
218 }
219 }
220
221 let data = unsafe { std::slice::from_raw_parts(pwstr.0, len) };
222 let value = String::from_utf16_lossy(data);
223
224 unsafe {
225 GlobalFree(HGLOBAL(pwstr.0 as _)).map_err(|err| {
226 Error::WindowsApi(WindowsApiError::with_context(err, "GlobalFree(PWSTR)"))
227 })?;
228 }
229
230 Ok(Some(value))
231}
232
233struct WinHttpSession {
234 handle: *mut core::ffi::c_void,
235}
236
237impl WinHttpSession {
238 fn new() -> Result<Self> {
239 unsafe {
240 let agent = HSTRING::from("windows-erg/proxy");
241 let handle = WinHttpOpen(
242 PCWSTR(agent.as_ptr()),
243 WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
244 PCWSTR::null(),
245 PCWSTR::null(),
246 0,
247 );
248
249 if handle.is_null() {
250 let code = GetLastError();
251 return Err(Error::Proxy(ProxyError::ResolutionFailed(
252 ProxyResolutionError::new(
253 "WinHttpOpen",
254 format!("WinHttpOpen returned null (error: 0x{:08X})", code.0),
255 ),
256 )));
257 }
258
259 Ok(WinHttpSession { handle })
260 }
261 }
262}
263
264impl Drop for WinHttpSession {
265 fn drop(&mut self) {
266 unsafe {
267 let _ = WinHttpCloseHandle(self.handle);
268 }
269 }
270}