eggress_system_proxy/backends/
windows.rs1use crate::apply::Command;
2use crate::command_runner::CommandRunner;
3use crate::inspection::SystemProxySettings;
4
5pub fn inspect_windows_proxy(runner: &dyn CommandRunner) -> Result<SystemProxySettings, String> {
9 let _ = runner;
10 #[cfg(target_os = "windows")]
11 {
12 inspect_windows_registry(runner)
13 }
14 #[cfg(not(target_os = "windows"))]
15 {
16 Err("Windows Internet Settings not available on this platform".to_string())
17 }
18}
19
20#[cfg(target_os = "windows")]
21fn inspect_windows_registry(runner: &dyn CommandRunner) -> Result<SystemProxySettings, String> {
22 let mut raw = std::collections::HashMap::new();
23
24 let key = r#"Software\Microsoft\Windows\CurrentVersion\Internet Settings"#;
26
27 let proxy_enable = read_reg_value(runner, key, "ProxyEnable")?;
28 let proxy_server = read_reg_value(runner, key, "ProxyServer")?;
29 let proxy_override = read_reg_value(runner, key, "ProxyOverride")?;
30
31 raw.insert("ProxyEnable".to_string(), proxy_enable.unwrap_or_default());
32 raw.insert(
33 "ProxyServer".to_string(),
34 proxy_server.clone().unwrap_or_default(),
35 );
36 raw.insert(
37 "ProxyOverride".to_string(),
38 proxy_override.clone().unwrap_or_default(),
39 );
40
41 let enabled = raw.get("ProxyEnable").map_or(false, |v| v == "1");
42
43 let (http_proxy, https_proxy, socks_proxy) = if enabled {
44 parse_windows_proxy_server(&proxy_server.unwrap_or_default())
45 } else {
46 (None, None, None)
47 };
48
49 Ok(SystemProxySettings {
50 source: "windows:internet_settings".to_string(),
51 http_proxy,
52 https_proxy,
53 socks_proxy,
54 no_proxy: proxy_override,
55 raw,
56 })
57}
58
59#[cfg(target_os = "windows")]
60fn read_reg_value(
61 runner: &dyn CommandRunner,
62 key: &str,
63 value: &str,
64) -> Result<Option<String>, String> {
65 let output = runner
66 .run("reg", &["query", &format!("HKCU\\{key}"), "/v", value])
67 .map_err(|e| format!("failed to run reg: {e}"))?;
68
69 if !output.status.success() {
70 return Ok(None);
71 }
72
73 let stdout = String::from_utf8_lossy(&output.stdout);
74 for line in stdout.lines() {
75 if line.contains(value) {
76 if let Some(pos) = line.rfind("REG_SZ") {
77 return Ok(Some(line[pos + 6..].trim().to_string()));
78 }
79 }
80 }
81 Ok(None)
82}
83
84#[cfg(target_os = "windows")]
85fn parse_windows_proxy_server(server: &str) -> (Option<String>, Option<String>, Option<String>) {
86 let mut http = None;
87 let mut https = None;
88 let mut socks = None;
89
90 for part in server.split(';') {
91 let part = part.trim();
92 if let Some(addr) = part.strip_prefix("http=") {
93 http = Some(addr.to_string());
94 } else if let Some(addr) = part.strip_prefix("https=") {
95 https = Some(addr.to_string());
96 } else if let Some(addr) = part.strip_prefix("socks=") {
97 socks = Some(addr.to_string());
98 } else if !part.is_empty() {
99 if http.is_none() {
101 http = Some(part.to_string());
102 }
103 if https.is_none() {
104 https = Some(part.to_string());
105 }
106 }
107 }
108
109 (http, https, socks)
110}
111
112pub fn generate_windows_apply_commands(
114 http_proxy: Option<&str>,
115 https_proxy: Option<&str>,
116 socks_proxy: Option<&str>,
117 no_proxy: Option<&str>,
118) -> Vec<Command> {
119 let key = r"HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings";
120 let mut commands = Vec::new();
121
122 let mut parts = Vec::new();
124 if let Some(http) = http_proxy {
125 parts.push(format!("http={http}"));
126 }
127 if let Some(https) = https_proxy {
128 parts.push(format!("https={https}"));
129 }
130 if let Some(socks) = socks_proxy {
131 parts.push(format!("socks={socks}"));
132 }
133
134 if !parts.is_empty() {
135 let proxy_value = parts.join(";");
136 commands.push(Command::new(
137 "reg",
138 vec![
139 "add".into(),
140 key.into(),
141 "/v".into(),
142 "ProxyServer".into(),
143 "/t".into(),
144 "REG_SZ".into(),
145 "/d".into(),
146 proxy_value,
147 "/f".into(),
148 ],
149 ));
150 commands.push(Command::new(
151 "reg",
152 vec![
153 "add".into(),
154 key.into(),
155 "/v".into(),
156 "ProxyEnable".into(),
157 "/t".into(),
158 "REG_DWORD".into(),
159 "/d".into(),
160 "1".into(),
161 "/f".into(),
162 ],
163 ));
164 }
165
166 if let Some(no_proxy) = no_proxy {
167 commands.push(Command::new(
168 "reg",
169 vec![
170 "add".into(),
171 key.into(),
172 "/v".into(),
173 "ProxyOverride".into(),
174 "/t".into(),
175 "REG_SZ".into(),
176 "/d".into(),
177 no_proxy.into(),
178 "/f".into(),
179 ],
180 ));
181 }
182
183 commands
184}
185
186pub fn generate_windows_disable_commands() -> Vec<Command> {
188 let key = r"HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings";
189 vec![Command::new(
190 "reg",
191 vec![
192 "add".into(),
193 key.into(),
194 "/v".into(),
195 "ProxyEnable".into(),
196 "/t".into(),
197 "REG_DWORD".into(),
198 "/d".into(),
199 "0".into(),
200 "/f".into(),
201 ],
202 )]
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[cfg(target_os = "windows")]
210 #[test]
211 fn parse_server_bare_address() {
212 let (http, https, socks) = parse_windows_proxy_server("proxy:8080");
213 assert_eq!(http.as_deref(), Some("proxy:8080"));
214 assert_eq!(https.as_deref(), Some("proxy:8080"));
215 assert_eq!(socks, None);
216 }
217
218 #[cfg(target_os = "windows")]
219 #[test]
220 fn parse_server_explicit_protocols() {
221 let (http, https, socks) =
222 parse_windows_proxy_server("http=proxy:8080;https=proxy:8443;socks=proxy:1080");
223 assert_eq!(http.as_deref(), Some("proxy:8080"));
224 assert_eq!(https.as_deref(), Some("proxy:8443"));
225 assert_eq!(socks.as_deref(), Some("proxy:1080"));
226 }
227
228 #[cfg(target_os = "windows")]
229 #[test]
230 fn parse_server_empty() {
231 let (http, https, socks) = parse_windows_proxy_server("");
232 assert_eq!(http, None);
233 assert_eq!(https, None);
234 assert_eq!(socks, None);
235 }
236
237 #[test]
238 fn generate_apply_commands_produces_reg_commands() {
239 let commands = generate_windows_apply_commands(
240 Some("proxy:8080"),
241 Some("proxy:8443"),
242 None,
243 Some("localhost"),
244 );
245 assert!(commands
246 .iter()
247 .any(|c| c.to_string().contains("ProxyServer")));
248 assert!(commands
249 .iter()
250 .any(|c| c.to_string().contains("ProxyEnable")));
251 assert!(commands
252 .iter()
253 .any(|c| c.to_string().contains("ProxyOverride")));
254 }
255
256 #[test]
257 fn generate_disable_commands() {
258 let commands = generate_windows_disable_commands();
259 assert_eq!(commands.len(), 1);
260 assert!(commands[0].to_string().contains("ProxyEnable"));
261 assert!(commands[0].to_string().contains("/d 0"));
262 }
263}