1use super::{Authority, Host};
2use crate::{Protocol, proto::try_to_extract_protocol_from_uri_scheme, user::ProxyCredential};
3use rama_core::error::{ErrorContext, OpaqueError};
4use std::{fmt::Display, str::FromStr};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct ProxyAddress {
9 pub protocol: Option<Protocol>,
11
12 pub authority: Authority,
14
15 pub credential: Option<ProxyCredential>,
17}
18
19impl TryFrom<&str> for ProxyAddress {
20 type Error = OpaqueError;
21
22 fn try_from(value: &str) -> Result<Self, Self::Error> {
23 let slice = value.as_bytes();
24
25 let (protocol, size) = try_to_extract_protocol_from_uri_scheme(slice)
26 .context("extract protocol from proxy address scheme")?;
27 let slice = &slice[size..];
28
29 for i in 0..slice.len() {
30 if slice[i] == b'@' {
31 let credential = ProxyCredential::try_from_clear_str(
32 std::str::from_utf8(&slice[..i])
33 .context("parse proxy address: view credential as utf-8")?
34 .to_owned(),
35 )
36 .context("parse proxy credential from address")?;
37
38 let authority: Authority = slice[i + 1..]
39 .try_into()
40 .or_else(|_| {
41 Host::try_from(&slice[i + 1..]).map(|h| {
42 (
43 h,
44 protocol
45 .as_ref()
46 .and_then(|proto| proto.default_port())
47 .unwrap_or(80),
48 )
49 .into()
50 })
51 })
52 .context("parse proxy authority from address")?;
53
54 return Ok(ProxyAddress {
55 protocol,
56 authority,
57 credential: Some(credential),
58 });
59 }
60 }
61
62 let authority: Authority = slice
63 .try_into()
64 .or_else(|_| {
65 Host::try_from(slice).map(|h| {
66 (
67 h,
68 protocol
69 .as_ref()
70 .and_then(|proto| proto.default_port())
71 .unwrap_or(80),
72 )
73 .into()
74 })
75 })
76 .context("parse proxy authority from address")?;
77 Ok(ProxyAddress {
78 protocol,
79 authority,
80 credential: None,
81 })
82 }
83}
84
85impl TryFrom<String> for ProxyAddress {
86 type Error = OpaqueError;
87
88 fn try_from(value: String) -> Result<Self, Self::Error> {
89 value.as_str().try_into()
90 }
91}
92
93impl FromStr for ProxyAddress {
94 type Err = OpaqueError;
95
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 s.try_into()
98 }
99}
100
101impl Display for ProxyAddress {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 if let Some(protocol) = &self.protocol {
104 write!(f, "{}://", protocol.as_str())?;
105 }
106 if let Some(credential) = &self.credential {
107 write!(f, "{}@", credential.as_clear_string())?;
108 }
109 self.authority.fmt(f)
110 }
111}
112
113impl serde::Serialize for ProxyAddress {
114 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
115 where
116 S: serde::Serializer,
117 {
118 let addr = self.to_string();
119 addr.serialize(serializer)
120 }
121}
122
123impl<'de> serde::Deserialize<'de> for ProxyAddress {
124 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
125 where
126 D: serde::Deserializer<'de>,
127 {
128 let s = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
129 s.parse().map_err(serde::de::Error::custom)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::{
137 address::Host,
138 user::{Basic, Bearer},
139 };
140
141 #[test]
142 fn test_valid_proxy() {
143 let addr: ProxyAddress = "127.0.0.1:8080".try_into().unwrap();
144 assert_eq!(
145 addr,
146 ProxyAddress {
147 protocol: None,
148 authority: Authority::new(Host::Address("127.0.0.1".parse().unwrap()), 8080),
149 credential: None,
150 }
151 );
152 }
153
154 #[test]
155 fn test_valid_domain_proxy() {
156 let addr: ProxyAddress = "proxy.example.com".try_into().unwrap();
157 assert_eq!(
158 addr,
159 ProxyAddress {
160 protocol: None,
161 authority: Authority::new(Host::Name("proxy.example.com".parse().unwrap()), 80),
162 credential: None,
163 }
164 );
165 }
166
167 #[test]
168 fn test_valid_proxy_with_credential() {
169 let addr: ProxyAddress = "foo:bar@127.0.0.1:8080".try_into().unwrap();
170 assert_eq!(
171 addr,
172 ProxyAddress {
173 protocol: None,
174 authority: Authority::new(Host::Address("127.0.0.1".parse().unwrap()), 8080),
175 credential: Some(Basic::new("foo", "bar").into()),
176 }
177 );
178 }
179
180 #[test]
181 fn test_valid_http_proxy() {
182 let addr: ProxyAddress = "http://127.0.0.1:8080".try_into().unwrap();
183 assert_eq!(
184 addr,
185 ProxyAddress {
186 protocol: Some(Protocol::HTTP),
187 authority: Authority::new(Host::Address("127.0.0.1".parse().unwrap()), 8080),
188 credential: None,
189 }
190 );
191 }
192
193 #[test]
194 fn test_valid_http_proxy_with_credential() {
195 let addr: ProxyAddress = "http://foo:bar@127.0.0.1:8080".try_into().unwrap();
196 assert_eq!(
197 addr,
198 ProxyAddress {
199 protocol: Some(Protocol::HTTP),
200 authority: Authority::new(Host::Address("127.0.0.1".parse().unwrap()), 8080),
201 credential: Some(Basic::new("foo", "bar").into()),
202 }
203 );
204 }
205
206 #[test]
207 fn test_valid_https_proxy() {
208 let addr: ProxyAddress = "https://foo-cc-be:baz@my.proxy.io.:9999"
209 .try_into()
210 .unwrap();
211 assert_eq!(
212 addr,
213 ProxyAddress {
214 protocol: Some(Protocol::HTTPS),
215 authority: Authority::new(Host::Name("my.proxy.io.".parse().unwrap()), 9999),
216 credential: Some(Basic::new("foo-cc-be", "baz").into()),
217 }
218 );
219 }
220
221 #[test]
222 fn test_valid_socks5h_proxy() {
223 let addr: ProxyAddress = "socks5h://foo@[::1]:60000".try_into().unwrap();
224 assert_eq!(
225 addr,
226 ProxyAddress {
227 protocol: Some(Protocol::SOCKS5H),
228 authority: Authority::new(Host::Address("::1".parse().unwrap()), 60000),
229 credential: Some(Bearer::try_from_clear_str("foo").unwrap().into()),
230 }
231 );
232 }
233
234 #[test]
235 fn test_valid_proxy_address_symmetric() {
236 for s in [
237 "proxy.io",
238 "proxy.io:8080",
239 "127.0.0.1",
240 "127.0.0.1:8080",
241 "::1",
242 "[::1]:8080",
243 "socks5://proxy.io",
244 "socks5://proxy.io:8080",
245 "socks5://127.0.0.1",
246 "socks5://127.0.0.1:8080",
247 "socks5://::1",
248 "socks5://[::1]:8080",
249 "socks5://foo@proxy.io",
250 "socks5://foo@proxy.io:8080",
251 "socks5://foo@127.0.0.1",
252 "socks5://foo@127.0.0.1:8080",
253 "socks5://foo@::1",
254 "socks5://foo@[::1]:8080",
255 "socks5://foo:@proxy.io",
256 "socks5://foo:@proxy.io:8080",
257 "socks5://foo:@127.0.0.1",
258 "socks5://foo:@127.0.0.1:8080",
259 "socks5://foo:@::1",
260 "socks5://foo:@[::1]:8080",
261 "socks5://foo:bar@proxy.io",
262 "socks5://foo:bar@proxy.io:8080",
263 "socks5://foo:bar@127.0.0.1",
264 "socks5://foo:bar@127.0.0.1:8080",
265 "socks5://foo:bar@::1",
266 "socks5://foo:bar@[::1]:8080",
267 ] {
268 let addr: ProxyAddress = match s.try_into() {
269 Ok(addr) => addr,
270 Err(err) => panic!("invalid addr '{s}': {err}"),
271 };
272 let out = addr.to_string();
273 let mut s = s.to_owned();
274 if !s.ends_with(":8080") {
275 if s.contains("::1") {
276 let mut it = s.split("://");
277 let mut scheme = Some(it.next().unwrap());
278 let host = it.next().unwrap_or_else(|| scheme.take().unwrap());
279 if host.contains('@') {
280 let mut it = host.split('@');
281 let credential = it.next().unwrap();
282 let host = it.next().unwrap();
283 s = match scheme {
284 Some(scheme) => format!("{scheme}://{credential}@[{host}]:1080"),
285 None => format!("{credential}@[{host}]:80"),
286 };
287 } else {
288 s = match scheme {
289 Some(scheme) => format!("{scheme}://[{host}]:1080"),
290 None => format!("[{host}]:80"),
291 };
292 }
293 } else {
294 s = if s.contains("://") {
295 format!("{s}:1080")
296 } else {
297 format!("{s}:80")
298 };
299 }
300 }
301 assert_eq!(s, out, "addr: {addr}");
302 }
303 }
304}