tor_interface/
proxy.rs

1// internal crates
2use crate::tor_provider::TargetAddr;
3
4#[derive(thiserror::Error, Debug)]
5/// Error type for the proxy module
6pub enum ProxyConfigError {
7    #[error("{0}")]
8    /// An error returned when constructing a proxy configuration with invalid parameters
9    Generic(String),
10}
11
12#[derive(Clone, Debug)]
13/// Configuration for a SOCKS4 proxy
14pub struct Socks4ProxyConfig {
15    pub(crate) address: TargetAddr,
16}
17
18impl Socks4ProxyConfig {
19    /// Construct a new `Socks4ProxyConfig`. The `address` argument must not be a [`crate::tor_provider::TargetAddr::OnionService`] and its port must not be 0.
20    pub fn new(address: TargetAddr) -> Result<Self, ProxyConfigError> {
21        let port = match &address {
22            TargetAddr::Socket(addr) => addr.port(),
23            TargetAddr::Domain(addr) => addr.port(),
24            TargetAddr::OnionService(_) => {
25                return Err(ProxyConfigError::Generic(
26                    "proxy address may not be onion service".to_string(),
27                ))
28            }
29        };
30        if port == 0 {
31            return Err(ProxyConfigError::Generic("proxy port not be 0".to_string()));
32        }
33
34        Ok(Self { address })
35    }
36}
37
38#[derive(Clone, Debug)]
39/// Configuration for a SOCKS5 proxy
40pub struct Socks5ProxyConfig {
41    pub(crate) address: TargetAddr,
42    pub(crate) username: Option<String>,
43    pub(crate) password: Option<String>,
44}
45
46impl Socks5ProxyConfig {
47    /// Construct a new `Socks5ProxyConfig`. The `address` argument must not be a  [`crate::tor_provider::TargetAddr::OnionService`] and its port must not be 0. The `username` and `password` arguments, if present, must each be less than 256 bytes long.
48    pub fn new(
49        address: TargetAddr,
50        username: Option<String>,
51        password: Option<String>,
52    ) -> Result<Self, ProxyConfigError> {
53        let port = match &address {
54            TargetAddr::Socket(addr) => addr.port(),
55            TargetAddr::Domain(addr) => addr.port(),
56            TargetAddr::OnionService(_) => {
57                return Err(ProxyConfigError::Generic(
58                    "proxy address may not be onion service".to_string(),
59                ))
60            }
61        };
62        if port == 0 {
63            return Err(ProxyConfigError::Generic("proxy port not be 0".to_string()));
64        }
65
66        // username must be less than 255 bytes
67        if let Some(username) = &username {
68            if username.len() > 255 {
69                return Err(ProxyConfigError::Generic(
70                    "socks5 username must be <= 255 bytes".to_string(),
71                ));
72            }
73        }
74        // password must be less than 255 bytes
75        if let Some(password) = &password {
76            if password.len() > 255 {
77                return Err(ProxyConfigError::Generic(
78                    "socks5 password must be <= 255 bytes".to_string(),
79                ));
80            }
81        }
82
83        Ok(Self {
84            address,
85            username,
86            password,
87        })
88    }
89}
90
91#[derive(Clone, Debug)]
92/// Configuration for an HTTP CONNECT proxy (`HTTPSProxy` in c-tor torrc configuration)
93pub struct HttpsProxyConfig {
94    pub(crate) address: TargetAddr,
95    pub(crate) username: Option<String>,
96    pub(crate) password: Option<String>,
97}
98
99impl HttpsProxyConfig {
100    /// Construct a new `HttpsProxyConfig`. The `address` argument must not be a [`crate::tor_provider::TargetAddr::OnionService`] and its port must not be 0. The `username` argument, if present, must not contain the `:` (colon) character.
101    pub fn new(
102        address: TargetAddr,
103        username: Option<String>,
104        password: Option<String>,
105    ) -> Result<Self, ProxyConfigError> {
106        let port = match &address {
107            TargetAddr::Socket(addr) => addr.port(),
108            TargetAddr::Domain(addr) => addr.port(),
109            TargetAddr::OnionService(_) => {
110                return Err(ProxyConfigError::Generic(
111                    "proxy address may not be onion service".to_string(),
112                ))
113            }
114        };
115        if port == 0 {
116            return Err(ProxyConfigError::Generic("proxy port not be 0".to_string()));
117        }
118
119        // username may not contain ':' character (per RFC 2617)
120        if let Some(username) = &username {
121            if username.contains(':') {
122                return Err(ProxyConfigError::Generic(
123                    "username may not contain ':' character".to_string(),
124                ));
125            }
126        }
127
128        Ok(Self {
129            address,
130            username,
131            password,
132        })
133    }
134}
135
136#[derive(Clone, Debug)]
137/// An enum representing a possible proxy server configuration with address and possible credentials.
138pub enum ProxyConfig {
139    /// A SOCKS4 proxy
140    Socks4(Socks4ProxyConfig),
141    /// A SOCKS5 proxy
142    Socks5(Socks5ProxyConfig),
143    /// An HTTP CONNECT proxy
144    Https(HttpsProxyConfig),
145}
146
147impl From<Socks4ProxyConfig> for ProxyConfig {
148    fn from(config: Socks4ProxyConfig) -> Self {
149        ProxyConfig::Socks4(config)
150    }
151}
152
153impl From<Socks5ProxyConfig> for ProxyConfig {
154    fn from(config: Socks5ProxyConfig) -> Self {
155        ProxyConfig::Socks5(config)
156    }
157}
158
159impl From<HttpsProxyConfig> for ProxyConfig {
160    fn from(config: HttpsProxyConfig) -> Self {
161        ProxyConfig::Https(config)
162    }
163}