Skip to main content

socks5_impl/protocol/
proxy_parameters.rs

1use super::{Address, UserKey};
2use crate::{Error, Result};
3
4#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
5#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
6pub struct ProxyParameters {
7    pub proxy_type: ProxyType,
8    pub addr: Address,
9    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
10    pub credentials: Option<UserKey>,
11}
12
13impl Default for ProxyParameters {
14    fn default() -> Self {
15        ProxyParameters {
16            proxy_type: ProxyType::Socks5,
17            addr: "127.0.0.1:1080".parse().unwrap(),
18            credentials: None,
19        }
20    }
21}
22
23impl std::fmt::Display for ProxyParameters {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        let auth = match &self.credentials {
26            Some(creds) => format!("{creds}"),
27            None => "".to_owned(),
28        };
29        if auth.is_empty() {
30            write!(f, "{}://{}", &self.proxy_type, &self.addr)
31        } else {
32            write!(f, "{}://{}@{}", &self.proxy_type, auth, &self.addr)
33        }
34    }
35}
36
37impl TryFrom<&str> for ProxyParameters {
38    type Error = Error;
39    fn try_from(s: &str) -> Result<Self> {
40        if s == "none" {
41            return Ok(ProxyParameters {
42                proxy_type: ProxyType::None,
43                addr: "0.0.0.0:0".parse().unwrap(),
44                credentials: None,
45            });
46        }
47
48        let e = format!("`{s}` is not a valid proxy URL");
49        let url = url::Url::parse(s).map_err(|_| Error::from(&e))?;
50        let e = format!("`{s}` does not contain a host");
51        let host = url.host_str().ok_or(Error::from(e))?;
52
53        let e = format!("`{s}` does not contain a port");
54        let port = url.port_or_known_default().ok_or(Error::from(&e))?;
55
56        let addr = (host, port).into();
57
58        let credentials = if url.username() == "" && url.password().is_none() {
59            None
60        } else {
61            use percent_encoding::percent_decode;
62            let username = percent_decode(url.username().as_bytes()).decode_utf8()?;
63            let password = percent_decode(url.password().unwrap_or("").as_bytes()).decode_utf8()?;
64            Some(UserKey::new(username, password))
65        };
66
67        let proxy_type = url.scheme().to_ascii_lowercase().as_str().try_into()?;
68
69        Ok(ProxyParameters {
70            proxy_type,
71            addr,
72            credentials,
73        })
74    }
75}
76
77impl std::str::FromStr for ProxyParameters {
78    type Err = Error;
79    fn from_str(s: &str) -> Result<Self> {
80        Self::try_from(s)
81    }
82}
83
84#[repr(C)]
85#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
86#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, Hash)]
87pub enum ProxyType {
88    Http = 0,
89    Socks4,
90    #[default]
91    Socks5,
92    None,
93}
94
95impl TryFrom<&str> for ProxyType {
96    type Error = Error;
97    fn try_from(value: &str) -> Result<Self> {
98        match value.to_ascii_lowercase().as_str() {
99            "http" => Ok(ProxyType::Http),
100            "socks4" => Ok(ProxyType::Socks4),
101            "socks5" => Ok(ProxyType::Socks5),
102            "none" => Ok(ProxyType::None),
103            scheme => Err(Error::from(&format!("`{scheme}` is an invalid proxy type"))),
104        }
105    }
106}
107
108impl std::fmt::Display for ProxyType {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        match self {
111            ProxyType::Socks4 => write!(f, "socks4"),
112            ProxyType::Socks5 => write!(f, "socks5"),
113            ProxyType::Http => write!(f, "http"),
114            ProxyType::None => write!(f, "none"),
115        }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn default_proxy_parameters() {
125        let parameters = ProxyParameters::default();
126
127        assert_eq!(parameters.proxy_type, ProxyType::Socks5);
128        assert_eq!(parameters.addr, "127.0.0.1:1080".parse().unwrap());
129        assert_eq!(parameters.credentials, None);
130        assert_eq!(parameters.to_string(), "socks5://127.0.0.1:1080");
131    }
132
133    #[test]
134    fn parse_without_credentials() {
135        let parameters = "socks5://123.45.67.89:1080".parse::<ProxyParameters>().unwrap();
136        assert_eq!(parameters.proxy_type, ProxyType::Socks5);
137        assert_eq!(parameters.addr, ("123.45.67.89", 1080).into());
138        assert_eq!(parameters.addr.get_type(), crate::protocol::AddressType::IPv4);
139        assert_eq!(parameters.credentials, None);
140        assert_eq!(parameters.to_string(), "socks5://123.45.67.89:1080");
141
142        let parameters = "socks5://proxy.example.com:1080".parse::<ProxyParameters>().unwrap();
143
144        assert_eq!(parameters.proxy_type, ProxyType::Socks5);
145        assert_eq!(parameters.addr, ("proxy.example.com", 1080).into());
146        assert_eq!(parameters.credentials, None);
147        assert_eq!(parameters.to_string(), "socks5://proxy.example.com:1080");
148
149        let parameters = "http://proxy.example.com:8080".parse::<ProxyParameters>().unwrap();
150        assert_eq!(parameters.proxy_type, ProxyType::Http);
151        assert_eq!(parameters.addr, ("proxy.example.com", 8080).into());
152        assert_eq!(parameters.credentials, None);
153        assert_eq!(parameters.to_string(), "http://proxy.example.com:8080");
154
155        let parameters = "http://proxy.example.com".parse::<ProxyParameters>().unwrap();
156        assert_eq!(parameters.proxy_type, ProxyType::Http);
157        assert_eq!(parameters.addr, ("proxy.example.com", 80).into());
158        assert_eq!(parameters.credentials, None);
159        assert_eq!(parameters.to_string(), "http://proxy.example.com:80");
160
161        assert!("socks5://proxy.example.com".parse::<ProxyParameters>().is_err());
162    }
163
164    #[test]
165    fn parse_with_credentials() {
166        let parameters = "socks5://user:password@proxy.example.com:1080".parse::<ProxyParameters>().unwrap();
167        assert_eq!(parameters.proxy_type, ProxyType::Socks5);
168        assert_eq!(parameters.addr, ("proxy.example.com", 1080).into());
169        assert_eq!(parameters.credentials, Some(UserKey::new("user", "password")));
170        assert_eq!(parameters.to_string(), "socks5://user:password@proxy.example.com:1080");
171
172        let parameters = "socks5://user@123.45.67.89:1080".parse::<ProxyParameters>().unwrap();
173        assert_eq!(parameters.proxy_type, ProxyType::Socks5);
174        assert_eq!(parameters.addr, ("123.45.67.89", 1080).into());
175        assert_eq!(parameters.credentials, Some(UserKey::new("user", "")));
176        assert_eq!(parameters.to_string(), "socks5://user@123.45.67.89:1080");
177
178        let parameters = "socks5://:password@123.45.67.89:1080".parse::<ProxyParameters>().unwrap();
179        assert_eq!(parameters.proxy_type, ProxyType::Socks5);
180        assert_eq!(parameters.addr, ("123.45.67.89", 1080).into());
181        assert_eq!(parameters.credentials, Some(UserKey::new("", "password")));
182        assert_eq!(parameters.to_string(), "socks5://:password@123.45.67.89:1080");
183    }
184
185    #[test]
186    fn parse_with_percent_encoded_credentials() {
187        let parameters = "socks5://user%40name:pa%24%24@proxy.example.com:1080"
188            .parse::<ProxyParameters>()
189            .unwrap();
190
191        assert_eq!(parameters.proxy_type, ProxyType::Socks5);
192        assert_eq!(parameters.addr, ("proxy.example.com", 1080).into());
193        assert_eq!(parameters.credentials, Some(UserKey::new("user@name", "pa$$")));
194        assert_eq!(parameters.to_string(), "socks5://user%40name:pa%24%24@proxy.example.com:1080");
195    }
196
197    #[test]
198    fn parse_none_proxy() {
199        let parameters = "none".parse::<ProxyParameters>().unwrap();
200
201        assert_eq!(parameters.proxy_type, ProxyType::None);
202        assert_eq!(parameters.addr, "0.0.0.0:0".parse().unwrap());
203        assert_eq!(parameters.credentials, None);
204        assert_eq!(parameters.to_string(), "none://0.0.0.0:0");
205    }
206
207    #[test]
208    fn parse_invalid_proxy_type() {
209        let err = "ftp://proxy.example.com:21".parse::<ProxyParameters>().unwrap_err();
210        assert!(format!("{err}").contains("invalid proxy type"));
211    }
212}