1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// internal crates
use crate::tor_provider::TargetAddr;

#[derive(thiserror::Error, Debug)]
/// Error type for the proxy module
pub enum ProxyConfigError {
    #[error("{0}")]
    /// An error returned when constructing a proxy configuration with invalid parameters
    Generic(String),
}

#[derive(Clone, Debug)]
/// Configuration for a SOCKS4 proxy
pub struct Socks4ProxyConfig {
    pub(crate) address: TargetAddr,
}

impl Socks4ProxyConfig {
    /// Construct a new `Socks4ProxyConfig`. The `address` argument must not be a [`crate::tor_provider::TargetAddr::OnionService`] and its port must not be 0.
    pub fn new(address: TargetAddr) -> Result<Self, ProxyConfigError> {
        let port = match &address {
            TargetAddr::Socket(addr) => addr.port(),
            TargetAddr::Domain(addr) => addr.port(),
            TargetAddr::OnionService(_) => {
                return Err(ProxyConfigError::Generic(
                    "proxy address may not be onion service".to_string(),
                ))
            }
        };
        if port == 0 {
            return Err(ProxyConfigError::Generic("proxy port not be 0".to_string()));
        }

        Ok(Self { address })
    }
}

#[derive(Clone, Debug)]
/// Configuration for a SOCKS5 proxy
pub struct Socks5ProxyConfig {
    pub(crate) address: TargetAddr,
    pub(crate) username: Option<String>,
    pub(crate) password: Option<String>,
}

impl Socks5ProxyConfig {
    /// 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.
    pub fn new(
        address: TargetAddr,
        username: Option<String>,
        password: Option<String>,
    ) -> Result<Self, ProxyConfigError> {
        let port = match &address {
            TargetAddr::Socket(addr) => addr.port(),
            TargetAddr::Domain(addr) => addr.port(),
            TargetAddr::OnionService(_) => {
                return Err(ProxyConfigError::Generic(
                    "proxy address may not be onion service".to_string(),
                ))
            }
        };
        if port == 0 {
            return Err(ProxyConfigError::Generic("proxy port not be 0".to_string()));
        }

        // username must be less than 255 bytes
        if let Some(username) = &username {
            if username.len() > 255 {
                return Err(ProxyConfigError::Generic(
                    "socks5 username must be <= 255 bytes".to_string(),
                ));
            }
        }
        // password must be less than 255 bytes
        if let Some(password) = &password {
            if password.len() > 255 {
                return Err(ProxyConfigError::Generic(
                    "socks5 password must be <= 255 bytes".to_string(),
                ));
            }
        }

        Ok(Self {
            address,
            username,
            password,
        })
    }
}

#[derive(Clone, Debug)]
/// Configuration for an HTTP CONNECT proxy (`HTTPSProxy` in c-tor torrc configuration)
pub struct HttpsProxyConfig {
    pub(crate) address: TargetAddr,
    pub(crate) username: Option<String>,
    pub(crate) password: Option<String>,
}

impl HttpsProxyConfig {
    /// 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.
    pub fn new(
        address: TargetAddr,
        username: Option<String>,
        password: Option<String>,
    ) -> Result<Self, ProxyConfigError> {
        let port = match &address {
            TargetAddr::Socket(addr) => addr.port(),
            TargetAddr::Domain(addr) => addr.port(),
            TargetAddr::OnionService(_) => {
                return Err(ProxyConfigError::Generic(
                    "proxy address may not be onion service".to_string(),
                ))
            }
        };
        if port == 0 {
            return Err(ProxyConfigError::Generic("proxy port not be 0".to_string()));
        }

        // username may not contain ':' character (per RFC 2617)
        if let Some(username) = &username {
            if username.contains(':') {
                return Err(ProxyConfigError::Generic(
                    "username may not contain ':' character".to_string(),
                ));
            }
        }

        Ok(Self {
            address,
            username,
            password,
        })
    }
}

#[derive(Clone, Debug)]
/// An enum representing a possible proxy server configuration with address and possible credentials.
pub enum ProxyConfig {
    /// A SOCKS4 proxy
    Socks4(Socks4ProxyConfig),
    /// A SOCKS5 proxy
    Socks5(Socks5ProxyConfig),
    /// An HTTP CONNECT proxy
    Https(HttpsProxyConfig),
}

impl From<Socks4ProxyConfig> for ProxyConfig {
    fn from(config: Socks4ProxyConfig) -> Self {
        ProxyConfig::Socks4(config)
    }
}

impl From<Socks5ProxyConfig> for ProxyConfig {
    fn from(config: Socks5ProxyConfig) -> Self {
        ProxyConfig::Socks5(config)
    }
}

impl From<HttpsProxyConfig> for ProxyConfig {
    fn from(config: HttpsProxyConfig) -> Self {
        ProxyConfig::Https(config)
    }
}