Skip to main content

eggress_system_proxy/
capability.rs

1use std::collections::HashMap;
2use std::fmt;
3
4/// Platform-specific capabilities for system proxy inspection and mutation.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
6pub enum SystemProxyCapability {
7    /// Read HTTP_PROXY / HTTPS_PROXY / ALL_PROXY environment variables.
8    InspectEnvironment,
9    /// Read proxy settings via macOS `networksetup` command.
10    InspectMacosNetworksetup,
11    /// Apply proxy settings via macOS `networksetup` command.
12    ApplyMacosNetworksetup,
13    /// Read proxy settings via Windows Internet Settings (registry).
14    InspectWindowsInternetSettings,
15    /// Apply proxy settings via Windows Internet Settings (registry).
16    ApplyWindowsInternetSettings,
17    /// Read proxy settings via GNOME `gsettings` or `dconf`.
18    InspectGnomeSettings,
19    /// Apply proxy settings via GNOME `gsettings`.
20    ApplyGnomeSettings,
21    /// Read proxy settings via KDE `kwriteconfig5`.
22    InspectKdeSettings,
23    /// Apply proxy settings via KDE `kwriteconfig5`.
24    ApplyKdeSettings,
25}
26
27/// Status of a system proxy capability check.
28#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
29pub enum SystemProxyStatus {
30    /// The capability is available on this system.
31    Available,
32    /// The capability exists but requires elevated privileges.
33    MissingPrivilege,
34    /// The capability is not supported on this platform.
35    UnsupportedPlatform,
36    /// The required tool is not installed.
37    ToolMissing,
38    /// The capability was disabled at compile time.
39    DisabledAtCompileTime,
40}
41
42impl fmt::Display for SystemProxyCapability {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::InspectEnvironment => write!(f, "InspectEnvironment"),
46            Self::InspectMacosNetworksetup => write!(f, "InspectMacosNetworksetup"),
47            Self::ApplyMacosNetworksetup => write!(f, "ApplyMacosNetworksetup"),
48            Self::InspectWindowsInternetSettings => write!(f, "InspectWindowsInternetSettings"),
49            Self::ApplyWindowsInternetSettings => write!(f, "ApplyWindowsInternetSettings"),
50            Self::InspectGnomeSettings => write!(f, "InspectGnomeSettings"),
51            Self::ApplyGnomeSettings => write!(f, "ApplyGnomeSettings"),
52            Self::InspectKdeSettings => write!(f, "InspectKdeSettings"),
53            Self::ApplyKdeSettings => write!(f, "ApplyKdeSettings"),
54        }
55    }
56}
57
58impl fmt::Display for SystemProxyStatus {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            Self::Available => write!(f, "available"),
62            Self::MissingPrivilege => write!(f, "missing privilege"),
63            Self::UnsupportedPlatform => write!(f, "unsupported platform"),
64            Self::ToolMissing => write!(f, "tool missing"),
65            Self::DisabledAtCompileTime => write!(f, "disabled at compile time"),
66        }
67    }
68}
69
70/// A single capability check result.
71#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
72pub struct SystemProxyCapabilityReport {
73    pub capability: SystemProxyCapability,
74    pub status: SystemProxyStatus,
75}
76
77impl fmt::Display for SystemProxyCapabilityReport {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        write!(f, "{}: {}", self.capability, self.status)
80    }
81}
82
83/// Check the status of a specific system proxy capability by probing the system.
84pub fn check_system_proxy_capability(cap: SystemProxyCapability) -> SystemProxyStatus {
85    match cap {
86        SystemProxyCapability::InspectEnvironment => check_env_inspection(),
87        SystemProxyCapability::InspectMacosNetworksetup => check_tool_available("networksetup"),
88        SystemProxyCapability::ApplyMacosNetworksetup => check_tool_available("networksetup"),
89        SystemProxyCapability::InspectWindowsInternetSettings => check_windows_internet_settings(),
90        SystemProxyCapability::ApplyWindowsInternetSettings => check_windows_internet_settings(),
91        SystemProxyCapability::InspectGnomeSettings => check_tool_available("gsettings"),
92        SystemProxyCapability::ApplyGnomeSettings => check_tool_available("gsettings"),
93        SystemProxyCapability::InspectKdeSettings => check_tool_available("kwriteconfig5"),
94        SystemProxyCapability::ApplyKdeSettings => check_tool_available("kwriteconfig5"),
95    }
96}
97
98/// Check with overrides for testing.
99pub fn check_system_proxy_capability_with_overrides(
100    cap: SystemProxyCapability,
101    overrides: Option<&HashMap<SystemProxyCapability, SystemProxyStatus>>,
102) -> SystemProxyStatus {
103    if let Some(overrides) = overrides {
104        if let Some(status) = overrides.get(&cap) {
105            return status.clone();
106        }
107    }
108    check_system_proxy_capability(cap)
109}
110
111/// Return all system proxy capabilities and their statuses.
112pub fn system_proxy_platform_info() -> Vec<SystemProxyCapabilityReport> {
113    ALL_SYSTEM_PROXY_CAPABILITIES
114        .iter()
115        .map(|&cap| SystemProxyCapabilityReport {
116            capability: cap,
117            status: check_system_proxy_capability(cap),
118        })
119        .collect()
120}
121
122/// All known system proxy capabilities.
123const ALL_SYSTEM_PROXY_CAPABILITIES: &[SystemProxyCapability] = &[
124    SystemProxyCapability::InspectEnvironment,
125    SystemProxyCapability::InspectMacosNetworksetup,
126    SystemProxyCapability::ApplyMacosNetworksetup,
127    SystemProxyCapability::InspectWindowsInternetSettings,
128    SystemProxyCapability::ApplyWindowsInternetSettings,
129    SystemProxyCapability::InspectGnomeSettings,
130    SystemProxyCapability::ApplyGnomeSettings,
131    SystemProxyCapability::InspectKdeSettings,
132    SystemProxyCapability::ApplyKdeSettings,
133];
134
135// ---------------------------------------------------------------------------
136// Capability check implementations
137// ---------------------------------------------------------------------------
138
139fn check_env_inspection() -> SystemProxyStatus {
140    SystemProxyStatus::Available
141}
142
143fn check_tool_available(tool: &str) -> SystemProxyStatus {
144    #[cfg(unix)]
145    {
146        check_tool_available_unix(tool)
147    }
148    #[cfg(not(unix))]
149    {
150        let _ = tool;
151        SystemProxyStatus::UnsupportedPlatform
152    }
153}
154
155#[cfg(unix)]
156fn check_tool_available_unix(tool: &str) -> SystemProxyStatus {
157    use std::process::Command;
158
159    match Command::new("which").arg(tool).output() {
160        Ok(output) if output.status.success() => SystemProxyStatus::Available,
161        Ok(_) => SystemProxyStatus::ToolMissing,
162        Err(_) => SystemProxyStatus::ToolMissing,
163    }
164}
165
166fn check_windows_internet_settings() -> SystemProxyStatus {
167    #[cfg(target_os = "windows")]
168    {
169        SystemProxyStatus::Available
170    }
171    #[cfg(not(target_os = "windows"))]
172    {
173        SystemProxyStatus::UnsupportedPlatform
174    }
175}
176
177/// Format system proxy capability reports as a human-readable string.
178pub fn format_system_proxy_capability_report(reports: &[SystemProxyCapabilityReport]) -> String {
179    let mut out = String::from("System proxy capabilities:\n");
180    for report in reports {
181        out.push_str(&format!("  {}: {}\n", report.capability, report.status));
182    }
183    out
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn display_system_proxy_capability() {
192        assert_eq!(
193            SystemProxyCapability::InspectEnvironment.to_string(),
194            "InspectEnvironment"
195        );
196        assert_eq!(
197            SystemProxyCapability::ApplyMacosNetworksetup.to_string(),
198            "ApplyMacosNetworksetup"
199        );
200    }
201
202    #[test]
203    fn display_system_proxy_status() {
204        assert_eq!(SystemProxyStatus::Available.to_string(), "available");
205        assert_eq!(
206            SystemProxyStatus::MissingPrivilege.to_string(),
207            "missing privilege"
208        );
209        assert_eq!(
210            SystemProxyStatus::UnsupportedPlatform.to_string(),
211            "unsupported platform"
212        );
213        assert_eq!(SystemProxyStatus::ToolMissing.to_string(), "tool missing");
214        assert_eq!(
215            SystemProxyStatus::DisabledAtCompileTime.to_string(),
216            "disabled at compile time"
217        );
218    }
219
220    #[test]
221    fn capability_report_display() {
222        let report = SystemProxyCapabilityReport {
223            capability: SystemProxyCapability::InspectEnvironment,
224            status: SystemProxyStatus::Available,
225        };
226        assert_eq!(report.to_string(), "InspectEnvironment: available");
227    }
228
229    #[test]
230    fn env_inspection_always_available() {
231        assert_eq!(
232            check_system_proxy_capability(SystemProxyCapability::InspectEnvironment),
233            SystemProxyStatus::Available
234        );
235    }
236
237    #[test]
238    fn override_returns_override_value() {
239        let mut overrides = HashMap::new();
240        overrides.insert(
241            SystemProxyCapability::ApplyMacosNetworksetup,
242            SystemProxyStatus::ToolMissing,
243        );
244
245        assert_eq!(
246            check_system_proxy_capability_with_overrides(
247                SystemProxyCapability::ApplyMacosNetworksetup,
248                Some(&overrides)
249            ),
250            SystemProxyStatus::ToolMissing
251        );
252    }
253
254    #[test]
255    fn override_does_not_affect_unset_capabilities() {
256        let mut overrides = HashMap::new();
257        overrides.insert(
258            SystemProxyCapability::ApplyMacosNetworksetup,
259            SystemProxyStatus::Available,
260        );
261
262        assert_eq!(
263            check_system_proxy_capability_with_overrides(
264                SystemProxyCapability::ApplyMacosNetworksetup,
265                Some(&overrides)
266            ),
267            SystemProxyStatus::Available
268        );
269
270        let real_status = check_system_proxy_capability_with_overrides(
271            SystemProxyCapability::InspectEnvironment,
272            Some(&overrides),
273        );
274        assert_eq!(real_status, SystemProxyStatus::Available);
275    }
276
277    #[test]
278    fn platform_info_returns_all_capabilities() {
279        let info = system_proxy_platform_info();
280        assert_eq!(info.len(), 9);
281
282        let names: Vec<_> = info.iter().map(|r| r.capability.to_string()).collect();
283        assert!(names.contains(&"InspectEnvironment".to_string()));
284        assert!(names.contains(&"InspectMacosNetworksetup".to_string()));
285    }
286
287    #[test]
288    fn format_report_contains_names() {
289        let info = system_proxy_platform_info();
290        let formatted = format_system_proxy_capability_report(&info);
291        assert!(formatted.contains("System proxy capabilities:"));
292        assert!(formatted.contains("InspectEnvironment"));
293    }
294
295    #[test]
296    fn windows_internet_settings_unsupported_on_non_windows() {
297        #[cfg(not(target_os = "windows"))]
298        {
299            assert_eq!(
300                check_system_proxy_capability(
301                    SystemProxyCapability::InspectWindowsInternetSettings
302                ),
303                SystemProxyStatus::UnsupportedPlatform
304            );
305        }
306    }
307
308    #[cfg(target_os = "macos")]
309    #[test]
310    fn macos_networksetup_available_on_macos() {
311        assert_eq!(
312            check_system_proxy_capability(SystemProxyCapability::InspectMacosNetworksetup),
313            SystemProxyStatus::Available
314        );
315    }
316}