firefox_webdriver/browser/
proxy.rs

1//! Proxy configuration types.
2//!
3//! Types for configuring per-tab and per-window proxy settings.
4//!
5//! # Example
6//!
7//! ```
8//! use firefox_webdriver::ProxyConfig;
9//!
10//! // HTTP proxy without auth
11//! let proxy = ProxyConfig::http("proxy.example.com", 8080);
12//!
13//! // SOCKS5 proxy with auth
14//! let proxy = ProxyConfig::socks5("proxy.example.com", 1080)
15//!     .with_credentials("user", "pass")
16//!     .with_proxy_dns(true);
17//! ```
18
19// ============================================================================
20// Imports
21// ============================================================================
22
23use serde::{Deserialize, Serialize};
24
25// ============================================================================
26// ProxyType
27// ============================================================================
28
29/// Proxy protocol type.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
31#[serde(rename_all = "lowercase")]
32pub enum ProxyType {
33    /// HTTP proxy (or SSL CONNECT for HTTPS).
34    Http,
35
36    /// HTTP proxying over TLS connection to proxy.
37    Https,
38
39    /// SOCKS v4 proxy.
40    Socks4,
41
42    /// SOCKS v5 proxy.
43    #[serde(rename = "socks")]
44    Socks5,
45
46    /// Direct connection (no proxy).
47    #[default]
48    Direct,
49}
50
51// ============================================================================
52// ProxyType - Implementation
53// ============================================================================
54
55impl ProxyType {
56    /// Returns the string representation for the extension.
57    #[inline]
58    #[must_use]
59    pub fn as_str(&self) -> &'static str {
60        match self {
61            Self::Http => "http",
62            Self::Https => "https",
63            Self::Socks4 => "socks4",
64            Self::Socks5 => "socks",
65            Self::Direct => "direct",
66        }
67    }
68}
69
70// ============================================================================
71// ProxyConfig
72// ============================================================================
73
74/// Proxy configuration.
75///
76/// # Example
77///
78/// ```
79/// use firefox_webdriver::ProxyConfig;
80///
81/// // HTTP proxy without auth
82/// let proxy = ProxyConfig::http("proxy.example.com", 8080);
83///
84/// // SOCKS5 proxy with auth
85/// let proxy = ProxyConfig::socks5("proxy.example.com", 1080)
86///     .with_credentials("user", "pass")
87///     .with_proxy_dns(true);
88/// ```
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ProxyConfig {
91    /// Proxy type.
92    #[serde(rename = "type")]
93    pub proxy_type: ProxyType,
94
95    /// Proxy hostname.
96    pub host: String,
97
98    /// Proxy port.
99    pub port: u16,
100
101    /// Username for authentication (optional).
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub username: Option<String>,
104
105    /// Password for authentication (optional).
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub password: Option<String>,
108
109    /// Whether to proxy DNS queries (SOCKS4/SOCKS5 only).
110    #[serde(rename = "proxyDns", default)]
111    pub proxy_dns: bool,
112}
113
114// ============================================================================
115// ProxyConfig - Constructors
116// ============================================================================
117
118impl ProxyConfig {
119    /// Creates a new proxy configuration.
120    ///
121    /// # Arguments
122    ///
123    /// * `proxy_type` - Proxy protocol type
124    /// * `host` - Proxy hostname
125    /// * `port` - Proxy port
126    #[must_use]
127    pub fn new(proxy_type: ProxyType, host: impl Into<String>, port: u16) -> Self {
128        Self {
129            proxy_type,
130            host: host.into(),
131            port,
132            username: None,
133            password: None,
134            proxy_dns: false,
135        }
136    }
137
138    /// Creates an HTTP proxy configuration.
139    #[inline]
140    #[must_use]
141    pub fn http(host: impl Into<String>, port: u16) -> Self {
142        Self::new(ProxyType::Http, host, port)
143    }
144
145    /// Creates an HTTPS proxy configuration.
146    #[inline]
147    #[must_use]
148    pub fn https(host: impl Into<String>, port: u16) -> Self {
149        Self::new(ProxyType::Https, host, port)
150    }
151
152    /// Creates a SOCKS4 proxy configuration.
153    #[inline]
154    #[must_use]
155    pub fn socks4(host: impl Into<String>, port: u16) -> Self {
156        Self::new(ProxyType::Socks4, host, port)
157    }
158
159    /// Creates a SOCKS5 proxy configuration.
160    ///
161    /// Note: DNS proxying is enabled by default for SOCKS5 to prevent DNS leaks.
162    /// Use `.with_proxy_dns(false)` to disable if needed.
163    #[inline]
164    #[must_use]
165    pub fn socks5(host: impl Into<String>, port: u16) -> Self {
166        Self {
167            proxy_type: ProxyType::Socks5,
168            host: host.into(),
169            port,
170            username: None,
171            password: None,
172            proxy_dns: true, // Enable by default to prevent DNS leaks
173        }
174    }
175
176    /// Creates a direct (no proxy) configuration.
177    #[inline]
178    #[must_use]
179    pub fn direct() -> Self {
180        Self {
181            proxy_type: ProxyType::Direct,
182            host: String::new(),
183            port: 0,
184            username: None,
185            password: None,
186            proxy_dns: false,
187        }
188    }
189}
190
191// ============================================================================
192// ProxyConfig - Builder Methods
193// ============================================================================
194
195impl ProxyConfig {
196    /// Sets authentication credentials.
197    ///
198    /// # Arguments
199    ///
200    /// * `username` - Proxy username
201    /// * `password` - Proxy password
202    #[must_use]
203    pub fn with_credentials(
204        mut self,
205        username: impl Into<String>,
206        password: impl Into<String>,
207    ) -> Self {
208        self.username = Some(username.into());
209        self.password = Some(password.into());
210        self
211    }
212
213    /// Enables DNS proxying (SOCKS4/SOCKS5 only).
214    ///
215    /// When enabled, DNS queries are sent through the proxy.
216    #[must_use]
217    pub fn with_proxy_dns(mut self, proxy_dns: bool) -> Self {
218        self.proxy_dns = proxy_dns;
219        self
220    }
221}
222
223// ============================================================================
224// ProxyConfig - Predicates
225// ============================================================================
226
227impl ProxyConfig {
228    /// Returns `true` if this proxy has authentication configured.
229    #[inline]
230    #[must_use]
231    pub fn has_auth(&self) -> bool {
232        self.username.is_some() && self.password.is_some()
233    }
234
235    /// Returns `true` if this is a SOCKS proxy.
236    #[inline]
237    #[must_use]
238    pub fn is_socks(&self) -> bool {
239        matches!(self.proxy_type, ProxyType::Socks4 | ProxyType::Socks5)
240    }
241
242    /// Returns `true` if this is an HTTP/HTTPS proxy.
243    #[inline]
244    #[must_use]
245    pub fn is_http(&self) -> bool {
246        matches!(self.proxy_type, ProxyType::Http | ProxyType::Https)
247    }
248}
249
250// ============================================================================
251// Tests
252// ============================================================================
253
254#[cfg(test)]
255mod tests {
256    use super::{ProxyConfig, ProxyType};
257
258    // ------------------------------------------------------------------------
259    // ProxyType Tests
260    // ------------------------------------------------------------------------
261
262    #[test]
263    fn test_proxy_type_as_str() {
264        assert_eq!(ProxyType::Http.as_str(), "http");
265        assert_eq!(ProxyType::Https.as_str(), "https");
266        assert_eq!(ProxyType::Socks4.as_str(), "socks4");
267        assert_eq!(ProxyType::Socks5.as_str(), "socks");
268        assert_eq!(ProxyType::Direct.as_str(), "direct");
269    }
270
271    #[test]
272    fn test_proxy_type_serialization() {
273        assert_eq!(
274            serde_json::to_string(&ProxyType::Http).unwrap(),
275            r#""http""#
276        );
277        assert_eq!(
278            serde_json::to_string(&ProxyType::Https).unwrap(),
279            r#""https""#
280        );
281        assert_eq!(
282            serde_json::to_string(&ProxyType::Socks4).unwrap(),
283            r#""socks4""#
284        );
285        assert_eq!(
286            serde_json::to_string(&ProxyType::Socks5).unwrap(),
287            r#""socks""#
288        );
289        assert_eq!(
290            serde_json::to_string(&ProxyType::Direct).unwrap(),
291            r#""direct""#
292        );
293    }
294
295    #[test]
296    fn test_proxy_type_default() {
297        assert_eq!(ProxyType::default(), ProxyType::Direct);
298    }
299
300    // ------------------------------------------------------------------------
301    // ProxyConfig Tests
302    // ------------------------------------------------------------------------
303
304    #[test]
305    fn test_proxy_config_http() {
306        let proxy = ProxyConfig::http("proxy.example.com", 8080);
307        assert_eq!(proxy.proxy_type, ProxyType::Http);
308        assert_eq!(proxy.host, "proxy.example.com");
309        assert_eq!(proxy.port, 8080);
310        assert!(!proxy.has_auth());
311        assert!(proxy.is_http());
312        assert!(!proxy.is_socks());
313    }
314
315    #[test]
316    fn test_proxy_config_https() {
317        let proxy = ProxyConfig::https("proxy.example.com", 8443);
318        assert_eq!(proxy.proxy_type, ProxyType::Https);
319        assert!(proxy.is_http());
320    }
321
322    #[test]
323    fn test_proxy_config_socks4() {
324        let proxy = ProxyConfig::socks4("proxy.example.com", 1080);
325        assert_eq!(proxy.proxy_type, ProxyType::Socks4);
326        assert!(proxy.is_socks());
327        assert!(!proxy.is_http());
328    }
329
330    #[test]
331    fn test_proxy_config_socks5() {
332        let proxy = ProxyConfig::socks5("proxy.example.com", 1080);
333        assert_eq!(proxy.proxy_type, ProxyType::Socks5);
334        assert!(proxy.is_socks());
335        assert!(proxy.proxy_dns); // DNS proxying enabled by default for SOCKS5
336    }
337
338    #[test]
339    fn test_proxy_config_direct() {
340        let proxy = ProxyConfig::direct();
341        assert_eq!(proxy.proxy_type, ProxyType::Direct);
342        assert!(!proxy.is_http());
343        assert!(!proxy.is_socks());
344    }
345
346    #[test]
347    fn test_proxy_config_with_auth() {
348        let proxy = ProxyConfig::socks5("proxy.example.com", 1080)
349            .with_credentials("user", "pass")
350            .with_proxy_dns(true);
351
352        assert_eq!(proxy.proxy_type, ProxyType::Socks5);
353        assert!(proxy.has_auth());
354        assert!(proxy.is_socks());
355        assert!(proxy.proxy_dns);
356        assert_eq!(proxy.username.as_deref(), Some("user"));
357        assert_eq!(proxy.password.as_deref(), Some("pass"));
358    }
359
360    #[test]
361    fn test_proxy_config_serialization() {
362        let proxy = ProxyConfig::http("proxy.example.com", 8080).with_credentials("user", "pass");
363
364        let json = serde_json::to_string(&proxy).unwrap();
365        assert!(json.contains(r#""type":"http""#));
366        assert!(json.contains(r#""host":"proxy.example.com""#));
367        assert!(json.contains(r#""port":8080"#));
368        assert!(json.contains(r#""username":"user""#));
369    }
370
371    #[test]
372    fn test_proxy_config_clone() {
373        let proxy = ProxyConfig::http("proxy.example.com", 8080);
374        let cloned = proxy.clone();
375        assert_eq!(proxy.host, cloned.host);
376        assert_eq!(proxy.port, cloned.port);
377    }
378}