[go: up one dir, main page]

ureq/
proxy.rs

1use base64::prelude::BASE64_STANDARD;
2use base64::Engine;
3use std::convert::{TryFrom, TryInto};
4use std::fmt;
5use std::io::Write;
6use std::sync::Arc;
7use ureq_proto::http::uri::{PathAndQuery, Scheme};
8use ureq_proto::parser::try_parse_response;
9
10use http::{StatusCode, Uri};
11
12use crate::config::DEFAULT_USER_AGENT;
13use crate::http;
14use crate::transport::{ConnectionDetails, Connector, Transport, TransportAdapter};
15use crate::util::{AuthorityExt, DebugUri, SchemeExt, UriExt};
16use crate::Error;
17
18/// Proxy protocol
19#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
20#[non_exhaustive]
21pub(crate) enum Proto {
22    Http,
23    Https,
24    Socks4,
25    Socks4A,
26    Socks5,
27}
28
29impl Proto {
30    pub fn default_port(&self) -> u16 {
31        match self {
32            Proto::Http => 80,
33            Proto::Https => 443,
34            Proto::Socks4 | Proto::Socks4A | Proto::Socks5 => 1080,
35        }
36    }
37
38    pub fn is_socks(&self) -> bool {
39        matches!(self, Self::Socks4 | Self::Socks4A | Self::Socks5)
40    }
41
42    pub(crate) fn is_connect(&self) -> bool {
43        matches!(self, Self::Http | Self::Https)
44    }
45}
46
47/// Proxy server settings
48#[derive(Clone, Eq, Hash, PartialEq)]
49pub struct Proxy {
50    inner: Arc<ProxyInner>,
51}
52
53#[derive(Eq, Hash, PartialEq)]
54struct ProxyInner {
55    proto: Proto,
56    uri: Uri,
57    from_env: bool,
58}
59
60impl Proxy {
61    /// Create a proxy from a uri.
62    ///
63    /// # Arguments:
64    ///
65    /// * `proxy` - a str of format `<protocol>://<user>:<password>@<host>:port` . All parts
66    ///    except host are optional.
67    ///
68    /// ###  Protocols
69    ///
70    /// * `http`: HTTP CONNECT proxy
71    /// * `https`: HTTPS CONNECT proxy (requires a TLS provider)
72    /// * `socks4`: SOCKS4 (requires **socks-proxy** feature)
73    /// * `socks4a`: SOCKS4A (requires **socks-proxy** feature)
74    /// * `socks5` and `socks`: SOCKS5 (requires **socks-proxy** feature)
75    ///
76    /// # Examples proxy formats
77    ///
78    /// * `http://127.0.0.1:8080`
79    /// * `socks5://john:smith@socks.google.com`
80    /// * `john:smith@socks.google.com:8000`
81    /// * `localhost`
82    pub fn new(proxy: &str) -> Result<Self, Error> {
83        Self::new_with_flag(proxy, false)
84    }
85
86    fn new_with_flag(proxy: &str, from_env: bool) -> Result<Self, Error> {
87        let mut uri = proxy.parse::<Uri>().or(Err(Error::InvalidProxyUrl))?;
88
89        // The uri must have an authority part (with the host), or
90        // it is invalid.
91        let _ = uri.authority().ok_or(Error::InvalidProxyUrl)?;
92
93        let scheme = match uri.scheme_str() {
94            Some(v) => v,
95            None => {
96                // The default protocol is Proto::HTTP, and it is missing in
97                // the uri. Let's put it in place.
98                uri = insert_default_scheme(uri);
99                "http"
100            }
101        };
102        let proto = scheme.try_into()?;
103
104        let inner = ProxyInner {
105            proto,
106            uri,
107            from_env,
108        };
109
110        Ok(Self {
111            inner: Arc::new(inner),
112        })
113    }
114
115    /// Read proxy settings from environment variables.
116    ///
117    /// The environment variable is expected to contain a proxy URI. The following
118    /// environment variables are attempted:
119    ///
120    /// * `ALL_PROXY`
121    /// * `HTTPS_PROXY`
122    /// * `HTTP_PROXY`
123    ///
124    /// Returns `None` if no environment variable is set or the URI is invalid.
125    pub fn try_from_env() -> Option<Self> {
126        if let Ok(env) = std::env::var("ALL_PROXY") {
127            if let Ok(proxy) = Self::new_with_flag(&env, true) {
128                return Some(proxy);
129            }
130        }
131
132        if let Ok(env) = std::env::var("all_proxy") {
133            if let Ok(proxy) = Self::new_with_flag(&env, true) {
134                return Some(proxy);
135            }
136        }
137
138        if let Ok(env) = std::env::var("HTTPS_PROXY") {
139            if let Ok(proxy) = Self::new_with_flag(&env, true) {
140                return Some(proxy);
141            }
142        }
143
144        if let Ok(env) = std::env::var("https_proxy") {
145            if let Ok(proxy) = Self::new_with_flag(&env, true) {
146                return Some(proxy);
147            }
148        }
149
150        if let Ok(env) = std::env::var("HTTP_PROXY") {
151            if let Ok(proxy) = Self::new_with_flag(&env, true) {
152                return Some(proxy);
153            }
154        }
155
156        if let Ok(env) = std::env::var("http_proxy") {
157            if let Ok(proxy) = Self::new_with_flag(&env, true) {
158                return Some(proxy);
159            }
160        }
161        None
162    }
163
164    pub(crate) fn proto(&self) -> Proto {
165        self.inner.proto
166    }
167
168    /// The proxy uri
169    pub fn uri(&self) -> &Uri {
170        &self.inner.uri
171    }
172
173    /// The host part of the proxy uri
174    pub fn host(&self) -> &str {
175        self.inner
176            .uri
177            .authority()
178            .map(|a| a.host())
179            .expect("constructor to ensure there is an authority")
180    }
181
182    /// The port of the proxy uri
183    pub fn port(&self) -> u16 {
184        self.inner
185            .uri
186            .authority()
187            .and_then(|a| a.port_u16())
188            .unwrap_or_else(|| self.inner.proto.default_port())
189    }
190
191    /// The username of the proxy uri
192    pub fn username(&self) -> Option<&str> {
193        self.inner.uri.authority().and_then(|a| a.username())
194    }
195
196    /// The password of the proxy uri
197    pub fn password(&self) -> Option<&str> {
198        self.inner.uri.authority().and_then(|a| a.password())
199    }
200
201    /// Whether this proxy setting was created manually or from
202    /// environment variables.
203    pub fn is_from_env(&self) -> bool {
204        self.inner.from_env
205    }
206}
207
208fn insert_default_scheme(uri: Uri) -> Uri {
209    let mut parts = uri.into_parts();
210
211    parts.scheme = Some(Scheme::HTTP);
212
213    // For some reason uri.into_parts can produce None for
214    // the path, but Uri::from_parts does not accept that.
215    parts.path_and_query = parts
216        .path_and_query
217        .or_else(|| Some(PathAndQuery::from_static("/")));
218
219    Uri::from_parts(parts).unwrap()
220}
221
222/// Connector for CONNECT proxy settings.
223///
224/// This operates on the previous chained transport typically a TcpConnector optionally
225/// wrapped in TLS.
226#[derive(Default)]
227pub struct ConnectProxyConnector(());
228
229impl<In: Transport> Connector<In> for ConnectProxyConnector {
230    type Out = In;
231
232    fn connect(
233        &self,
234        details: &ConnectionDetails,
235        chained: Option<In>,
236    ) -> Result<Option<Self::Out>, Error> {
237        let Some(transport) = chained else {
238            return Ok(None);
239        };
240
241        let is_connect_proxy = details.config.connect_proxy_uri().is_some();
242        if !is_connect_proxy {
243            return Ok(Some(transport));
244        }
245
246        // unwrap is ok because connect_proxy_uri() above checks it.
247        let proxy = details.config.proxy().unwrap();
248
249        let mut w = TransportAdapter::new(transport);
250
251        // unwrap is ok because run.rs will construct the ConnectionDetails
252        // such that CONNECT proxy _must_ have the `proxied` field set.
253        let proxied = details.proxied.unwrap();
254        proxied.ensure_valid_url()?;
255
256        let proxied_host = proxied.host().unwrap();
257        let proxied_port = proxied
258            .port_u16()
259            .unwrap_or(proxied.scheme().unwrap().default_port().unwrap());
260
261        write!(w, "CONNECT {}:{} HTTP/1.1\r\n", proxied_host, proxied_port)?;
262        write!(w, "Host: {}:{}\r\n", proxied_host, proxied_port)?;
263        if let Some(v) = details.config.user_agent().as_str(DEFAULT_USER_AGENT) {
264            write!(w, "User-Agent: {}\r\n", v)?;
265        }
266        write!(w, "Proxy-Connection: Keep-Alive\r\n")?;
267
268        let use_creds = proxy.username().is_some() || proxy.password().is_some();
269
270        if use_creds {
271            let user = proxy.username().unwrap_or_default();
272            let pass = proxy.password().unwrap_or_default();
273            let creds = BASE64_STANDARD.encode(format!("{}:{}", user, pass));
274            write!(w, "Proxy-Authorization: basic {}\r\n", creds)?;
275        }
276
277        write!(w, "\r\n")?;
278        w.flush()?;
279
280        let mut transport = w.into_inner();
281
282        let response = loop {
283            let made_progress = transport.await_input(details.timeout)?;
284            let buffers = transport.buffers();
285            let input = buffers.input();
286            let Some((used_input, response)) = try_parse_response::<20>(input)? else {
287                if !made_progress {
288                    let reason = "proxy server did not respond".to_string();
289                    return Err(Error::ConnectProxyFailed(reason));
290                }
291                continue;
292            };
293            buffers.input_consume(used_input);
294            break response;
295        };
296
297        match response.status() {
298            StatusCode::OK => {
299                trace!("CONNECT proxy connected");
300            }
301            x => {
302                let reason = format!("proxy server responded {}/{}", x.as_u16(), x.as_str());
303                return Err(Error::ConnectProxyFailed(reason));
304            }
305        }
306
307        Ok(Some(transport))
308    }
309}
310
311impl TryFrom<&str> for Proto {
312    type Error = Error;
313
314    fn try_from(scheme: &str) -> Result<Self, Self::Error> {
315        match scheme.to_ascii_lowercase().as_str() {
316            "http" => Ok(Proto::Http),
317            "https" => Ok(Proto::Https),
318            "socks4" => Ok(Proto::Socks4),
319            "socks4a" => Ok(Proto::Socks4A),
320            "socks" => Ok(Proto::Socks5),
321            "socks5" => Ok(Proto::Socks5),
322            _ => Err(Error::InvalidProxyUrl),
323        }
324    }
325}
326
327impl fmt::Debug for Proxy {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        f.debug_struct("Proxy")
330            .field("proto", &self.inner.proto)
331            .field("uri", &DebugUri(&self.inner.uri))
332            .field("from_env", &self.inner.from_env)
333            .finish()
334    }
335}
336
337impl fmt::Display for Proto {
338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339        match self {
340            Proto::Http => write!(f, "HTTP"),
341            Proto::Https => write!(f, "HTTPS"),
342            Proto::Socks4 => write!(f, "SOCKS4"),
343            Proto::Socks4A => write!(f, "SOCKS4a"),
344            Proto::Socks5 => write!(f, "SOCKS5"),
345        }
346    }
347}
348
349impl fmt::Debug for ConnectProxyConnector {
350    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351        f.debug_struct("ProxyConnector").finish()
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358
359    #[test]
360    fn parse_proxy_fakeproto() {
361        assert!(Proxy::new("fakeproto://localhost").is_err());
362    }
363
364    #[test]
365    fn parse_proxy_http_user_pass_server_port() {
366        let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999").unwrap();
367        assert_eq!(proxy.username(), Some("user"));
368        assert_eq!(proxy.password(), Some("p@ssw0rd"));
369        assert_eq!(proxy.host(), "localhost");
370        assert_eq!(proxy.port(), 9999);
371        assert_eq!(proxy.inner.proto, Proto::Http);
372    }
373
374    #[test]
375    fn parse_proxy_http_user_pass_server_port_trailing_slash() {
376        let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999/").unwrap();
377        assert_eq!(proxy.username(), Some("user"));
378        assert_eq!(proxy.password(), Some("p@ssw0rd"));
379        assert_eq!(proxy.host(), "localhost");
380        assert_eq!(proxy.port(), 9999);
381        assert_eq!(proxy.inner.proto, Proto::Http);
382    }
383
384    #[test]
385    fn parse_proxy_socks4_user_pass_server_port() {
386        let proxy = Proxy::new("socks4://user:p@ssw0rd@localhost:9999").unwrap();
387        assert_eq!(proxy.username(), Some("user"));
388        assert_eq!(proxy.password(), Some("p@ssw0rd"));
389        assert_eq!(proxy.host(), "localhost");
390        assert_eq!(proxy.port(), 9999);
391        assert_eq!(proxy.inner.proto, Proto::Socks4);
392    }
393
394    #[test]
395    fn parse_proxy_socks4a_user_pass_server_port() {
396        let proxy = Proxy::new("socks4a://user:p@ssw0rd@localhost:9999").unwrap();
397        assert_eq!(proxy.username(), Some("user"));
398        assert_eq!(proxy.password(), Some("p@ssw0rd"));
399        assert_eq!(proxy.host(), "localhost");
400        assert_eq!(proxy.port(), 9999);
401        assert_eq!(proxy.inner.proto, Proto::Socks4A);
402    }
403
404    #[test]
405    fn parse_proxy_socks_user_pass_server_port() {
406        let proxy = Proxy::new("socks://user:p@ssw0rd@localhost:9999").unwrap();
407        assert_eq!(proxy.username(), Some("user"));
408        assert_eq!(proxy.password(), Some("p@ssw0rd"));
409        assert_eq!(proxy.host(), "localhost");
410        assert_eq!(proxy.port(), 9999);
411        assert_eq!(proxy.inner.proto, Proto::Socks5);
412    }
413
414    #[test]
415    fn parse_proxy_socks5_user_pass_server_port() {
416        let proxy = Proxy::new("socks5://user:p@ssw0rd@localhost:9999").unwrap();
417        assert_eq!(proxy.username(), Some("user"));
418        assert_eq!(proxy.password(), Some("p@ssw0rd"));
419        assert_eq!(proxy.host(), "localhost");
420        assert_eq!(proxy.port(), 9999);
421        assert_eq!(proxy.inner.proto, Proto::Socks5);
422    }
423
424    #[test]
425    fn parse_proxy_user_pass_server_port() {
426        let proxy = Proxy::new("user:p@ssw0rd@localhost:9999").unwrap();
427        assert_eq!(proxy.username(), Some("user"));
428        assert_eq!(proxy.password(), Some("p@ssw0rd"));
429        assert_eq!(proxy.host(), "localhost");
430        assert_eq!(proxy.port(), 9999);
431        assert_eq!(proxy.inner.proto, Proto::Http);
432    }
433
434    #[test]
435    fn parse_proxy_server_port() {
436        let proxy = Proxy::new("localhost:9999").unwrap();
437        assert_eq!(proxy.username(), None);
438        assert_eq!(proxy.password(), None);
439        assert_eq!(proxy.host(), "localhost");
440        assert_eq!(proxy.port(), 9999);
441        assert_eq!(proxy.inner.proto, Proto::Http);
442    }
443
444    #[test]
445    fn parse_proxy_server() {
446        let proxy = Proxy::new("localhost").unwrap();
447        assert_eq!(proxy.username(), None);
448        assert_eq!(proxy.password(), None);
449        assert_eq!(proxy.host(), "localhost");
450        assert_eq!(proxy.port(), 80);
451        assert_eq!(proxy.inner.proto, Proto::Http);
452    }
453}
454
455#[cfg(test)]
456mod test {
457    use super::*;
458    use assert_no_alloc::*;
459
460    #[test]
461    fn proxy_clone_does_not_allocate() {
462        let c = Proxy::new("socks://1.2.3.4").unwrap();
463        assert_no_alloc(|| c.clone());
464    }
465
466    #[test]
467    fn proxy_new_default_scheme() {
468        let c = Proxy::new("localhost:1234").unwrap();
469        assert_eq!(c.proto(), Proto::Http);
470        assert_eq!(c.uri(), "http://localhost:1234");
471    }
472
473    #[test]
474    fn proxy_empty_env_url() {
475        let result = Proxy::new_with_flag("", false);
476        assert!(result.is_err());
477    }
478
479    #[test]
480    fn proxy_invalid_env_url() {
481        let result = Proxy::new_with_flag("r32/?//52:**", false);
482        assert!(result.is_err());
483    }
484}