Skip to main content

windows_erg/proxy/
types.rs

1//! Types for Windows proxy discovery and resolution.
2
3use std::collections::HashMap;
4
5/// Proxy settings discovered from Windows configuration.
6#[derive(Debug, Clone)]
7pub struct ProxyConfig {
8    /// Raw proxy server string (for example: "http=proxy:8080;https=proxy:8443").
9    pub server: String,
10    /// Bypass entries parsed from `ProxyOverride`.
11    pub bypass: Vec<String>,
12    /// Proxy endpoints parsed by scheme (for example `http -> proxy:8080`).
13    pub by_scheme: HashMap<String, String>,
14    /// WPAD/auto-detect setting.
15    pub auto_detect: bool,
16    /// PAC script URL (`AutoConfigURL`) when configured.
17    pub auto_config_url: Option<String>,
18}
19
20impl ProxyConfig {
21    /// Build a proxy config from raw registry values.
22    pub fn from_registry_values(
23        server: String,
24        bypass_raw: Option<String>,
25        auto_detect: bool,
26        auto_config_url: Option<String>,
27    ) -> Self {
28        ProxyConfig {
29            by_scheme: parse_proxy_server(&server),
30            bypass: parse_proxy_bypass(bypass_raw.as_deref()),
31            server,
32            auto_detect,
33            auto_config_url,
34        }
35    }
36}
37
38/// Effective proxy result for a specific URL.
39#[derive(Debug, Clone)]
40pub struct ProxyResolution {
41    /// URL that was resolved.
42    pub url: String,
43    /// Raw proxy string returned by WinHTTP.
44    pub proxy: Option<String>,
45    /// Parsed proxy endpoints by scheme.
46    pub by_scheme: HashMap<String, String>,
47    /// Bypass entries returned by WinHTTP.
48    pub bypass: Vec<String>,
49    /// Whether auto-detect (WPAD) was used for discovery.
50    pub used_auto_detect: bool,
51    /// Whether PAC URL was used for discovery.
52    pub used_auto_config_url: bool,
53}
54
55impl ProxyResolution {
56    /// Build a resolution from raw WinHTTP proxy output.
57    pub fn from_winhttp(
58        url: &str,
59        proxy: Option<String>,
60        bypass_raw: Option<String>,
61        used_auto_detect: bool,
62        used_auto_config_url: bool,
63    ) -> Self {
64        let by_scheme = proxy.as_deref().map(parse_proxy_server).unwrap_or_default();
65
66        ProxyResolution {
67            url: url.to_string(),
68            proxy,
69            by_scheme,
70            bypass: parse_proxy_bypass(bypass_raw.as_deref()),
71            used_auto_detect,
72            used_auto_config_url,
73        }
74    }
75}
76
77/// IE proxy settings returned from WinHTTP/IE integration APIs.
78#[derive(Debug, Clone)]
79pub struct IeProxyConfig {
80    /// WPAD auto-detect setting.
81    pub auto_detect: bool,
82    /// PAC script URL.
83    pub auto_config_url: Option<String>,
84    /// Raw static proxy server value.
85    pub proxy: Option<String>,
86    /// Raw bypass list value.
87    pub proxy_bypass: Option<String>,
88}
89
90/// Parse `ProxyServer` style value into a scheme map.
91///
92/// Supported formats:
93/// - `proxy.company.local:8080`
94/// - `http=proxy:8080;https=proxy:8443`
95pub fn parse_proxy_server(raw: &str) -> HashMap<String, String> {
96    let mut map = HashMap::new();
97    let trimmed = raw.trim();
98    if trimmed.is_empty() {
99        return map;
100    }
101
102    let entries = trimmed
103        .split(';')
104        .map(str::trim)
105        .filter(|entry| !entry.is_empty());
106
107    for entry in entries {
108        if let Some((scheme, endpoint)) = entry.split_once('=') {
109            let scheme = scheme.trim().to_ascii_lowercase();
110            let endpoint = endpoint.trim();
111            if !scheme.is_empty() && !endpoint.is_empty() {
112                map.insert(scheme, endpoint.to_string());
113            }
114        } else {
115            map.insert("all".to_string(), entry.to_string());
116        }
117    }
118
119    map
120}
121
122/// Parse `ProxyOverride` style bypass list.
123pub fn parse_proxy_bypass(raw: Option<&str>) -> Vec<String> {
124    raw.unwrap_or_default()
125        .split(';')
126        .map(str::trim)
127        .filter(|entry| !entry.is_empty())
128        .map(ToString::to_string)
129        .collect()
130}
131
132#[cfg(test)]
133mod tests {
134    use super::{parse_proxy_bypass, parse_proxy_server};
135
136    #[test]
137    fn parse_single_proxy_server() {
138        let parsed = parse_proxy_server("proxy.local:8080");
139        assert_eq!(parsed.get("all"), Some(&"proxy.local:8080".to_string()));
140    }
141
142    #[test]
143    fn parse_scheme_proxy_server() {
144        let parsed = parse_proxy_server("http=proxy:8080;https=secure:8443");
145        assert_eq!(parsed.get("http"), Some(&"proxy:8080".to_string()));
146        assert_eq!(parsed.get("https"), Some(&"secure:8443".to_string()));
147    }
148
149    #[test]
150    fn parse_bypass_list() {
151        let bypass = parse_proxy_bypass(Some("<local>;*.contoso.com;10.*"));
152        assert_eq!(bypass.len(), 3);
153        assert_eq!(bypass[0], "<local>");
154    }
155}