Skip to main content

capmonstercloud_client/
proxy.rs

1//! Proxy descriptions: the proxy a *worker* uses to open the target site (`ProxyInfo`),
2//! and the proxy the *SDK itself* uses to reach the CapMonster API (`ClientProxy`).
3
4use serde::Serialize;
5
6/// Protocol of a proxy server.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
8#[serde(rename_all = "lowercase")]
9pub enum ProxyType {
10    Http,
11    Https,
12    Socks4,
13    Socks5,
14}
15
16impl ProxyType {
17    pub fn as_str(&self) -> &'static str {
18        match self {
19            ProxyType::Http => "http",
20            ProxyType::Https => "https",
21            ProxyType::Socks4 => "socks4",
22            ProxyType::Socks5 => "socks5",
23        }
24    }
25}
26
27/// Proxy used by CapMonster workers to reach the captcha page.
28///
29/// It is flattened into the task JSON as `proxyType`, `proxyAddress`, `proxyPort`,
30/// `proxyLogin`, `proxyPassword`.
31#[derive(Clone, PartialEq, Eq, Serialize)]
32#[serde(rename_all = "camelCase")]
33pub struct ProxyInfo {
34    pub proxy_type: ProxyType,
35    /// IPv4/IPv6 address or hostname.
36    pub proxy_address: String,
37    pub proxy_port: u16,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub proxy_login: Option<String>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub proxy_password: Option<String>,
42}
43
44impl ProxyInfo {
45    pub fn new(proxy_type: ProxyType, address: impl Into<String>, port: u16) -> Self {
46        Self {
47            proxy_type,
48            proxy_address: address.into(),
49            proxy_port: port,
50            proxy_login: None,
51            proxy_password: None,
52        }
53    }
54
55    /// Adds login/password authentication.
56    pub fn with_auth(mut self, login: impl Into<String>, password: impl Into<String>) -> Self {
57        self.proxy_login = Some(login.into());
58        self.proxy_password = Some(password.into());
59        self
60    }
61}
62
63/// Proxy through which the SDK sends its own HTTP requests to the CapMonster Cloud API.
64/// Only `Http` and `Https` are supported.
65#[derive(Clone, PartialEq, Eq)]
66pub struct ClientProxy {
67    pub proxy_type: ProxyType,
68    pub address: String,
69    pub port: u16,
70    pub login: Option<String>,
71    pub password: Option<String>,
72}
73
74impl ClientProxy {
75    pub fn new(proxy_type: ProxyType, address: impl Into<String>, port: u16) -> Self {
76        Self {
77            proxy_type,
78            address: address.into(),
79            port,
80            login: None,
81            password: None,
82        }
83    }
84
85    pub fn with_auth(mut self, login: impl Into<String>, password: impl Into<String>) -> Self {
86        self.login = Some(login.into());
87        self.password = Some(password.into());
88        self
89    }
90}
91
92fn redact(secret: &Option<String>) -> &'static str {
93    if secret.is_some() {
94        "Some(\"***\")"
95    } else {
96        "None"
97    }
98}
99
100// Debug is written by hand so that passwords never end up in logs.
101impl std::fmt::Debug for ProxyInfo {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        f.debug_struct("ProxyInfo")
104            .field("proxy_type", &self.proxy_type)
105            .field("proxy_address", &self.proxy_address)
106            .field("proxy_port", &self.proxy_port)
107            .field("proxy_login", &self.proxy_login)
108            .field(
109                "proxy_password",
110                &format_args!("{}", redact(&self.proxy_password)),
111            )
112            .finish()
113    }
114}
115
116impl std::fmt::Debug for ClientProxy {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        f.debug_struct("ClientProxy")
119            .field("proxy_type", &self.proxy_type)
120            .field("address", &self.address)
121            .field("port", &self.port)
122            .field("login", &self.login)
123            .field("password", &format_args!("{}", redact(&self.password)))
124            .finish()
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn debug_does_not_leak_passwords() {
134        let p = ProxyInfo::new(ProxyType::Http, "h", 1).with_auth("u", "SECRET");
135        let c = ClientProxy::new(ProxyType::Http, "h", 1).with_auth("u", "SECRET");
136        assert!(!format!("{p:?}").contains("SECRET"));
137        assert!(!format!("{c:?}").contains("SECRET"));
138    }
139}