[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, Either, 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 = Either<In, Box<dyn Transport>>;
231
232    fn connect(
233        &self,
234        details: &ConnectionDetails,
235        chained: Option<In>,
236    ) -> Result<Option<Self::Out>, Error> {
237        // If there is already a connection, do nothing.
238        if let Some(transport) = chained {
239            return Ok(Some(Either::A(transport)));
240        }
241
242        // If we're using a CONNECT proxy, we need to resolve that hostname.
243        let maybe_connect_uri = details.config.connect_proxy_uri();
244
245        let Some(connect_uri) = maybe_connect_uri else {
246            // Not using CONNECT
247            return Ok(None);
248        };
249
250        let proxied = details.uri;
251
252        // TODO(martin): it's a bit weird to put the CONNECT proxy
253        // resolver timeout as part of Timeout::Connect, but we don't
254        // have anything more granular for now.
255        let addrs = details
256            .resolver
257            .resolve(connect_uri, details.config, details.timeout)?;
258
259        let proxy_config = details.config.clone_without_proxy();
260
261        // ConnectionDetails to establish a connection to the CONNECT
262        // proxy itself.
263        let proxy_details = ConnectionDetails {
264            uri: connect_uri,
265            addrs,
266            config: &proxy_config,
267            request_level: details.request_level,
268            resolver: details.resolver,
269            now: details.now,
270            timeout: details.timeout,
271            run_connector: details.run_connector.clone(),
272        };
273
274        let transport = (details.run_connector)(&proxy_details)?;
275
276        // unwrap is ok because connect_proxy_uri() above checks it.
277        let proxy = details.config.proxy().unwrap();
278
279        let mut w = TransportAdapter::new(transport);
280
281        proxied.ensure_valid_url()?;
282
283        let proxied_host = proxied.host().unwrap();
284        let proxied_port = proxied
285            .port_u16()
286            .unwrap_or(proxied.scheme().unwrap().default_port().unwrap());
287
288        write!(w, "CONNECT {}:{} HTTP/1.1\r\n", proxied_host, proxied_port)?;
289        write!(w, "Host: {}:{}\r\n", proxied_host, proxied_port)?;
290        if let Some(v) = details.config.user_agent().as_str(DEFAULT_USER_AGENT) {
291            write!(w, "User-Agent: {}\r\n", v)?;
292        }
293        write!(w, "Proxy-Connection: Keep-Alive\r\n")?;
294
295        let use_creds = proxy.username().is_some() || proxy.password().is_some();
296
297        if use_creds {
298            let user = proxy.username().unwrap_or_default();
299            let pass = proxy.password().unwrap_or_default();
300            let creds = BASE64_STANDARD.encode(format!("{}:{}", user, pass));
301            write!(w, "Proxy-Authorization: basic {}\r\n", creds)?;
302        }
303
304        write!(w, "\r\n")?;
305        w.flush()?;
306
307        let mut transport = w.into_inner();
308
309        let response = loop {
310            let made_progress = transport.maybe_await_input(details.timeout)?;
311            let buffers = transport.buffers();
312            let input = buffers.input();
313            let Some((used_input, response)) = try_parse_response::<20>(input)? else {
314                if !made_progress {
315                    let reason = "proxy server did not respond".to_string();
316                    return Err(Error::ConnectProxyFailed(reason));
317                }
318                continue;
319            };
320            buffers.input_consume(used_input);
321            break response;
322        };
323
324        match response.status() {
325            StatusCode::OK => {
326                trace!("CONNECT proxy connected");
327            }
328            x => {
329                let reason = format!("proxy server responded {}/{}", x.as_u16(), x.as_str());
330                return Err(Error::ConnectProxyFailed(reason));
331            }
332        }
333
334        Ok(Some(Either::B(transport)))
335    }
336}
337
338impl TryFrom<&str> for Proto {
339    type Error = Error;
340
341    fn try_from(scheme: &str) -> Result<Self, Self::Error> {
342        match scheme.to_ascii_lowercase().as_str() {
343            "http" => Ok(Proto::Http),
344            "https" => Ok(Proto::Https),
345            "socks4" => Ok(Proto::Socks4),
346            "socks4a" => Ok(Proto::Socks4A),
347            "socks" => Ok(Proto::Socks5),
348            "socks5" => Ok(Proto::Socks5),
349            _ => Err(Error::InvalidProxyUrl),
350        }
351    }
352}
353
354impl fmt::Debug for Proxy {
355    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356        f.debug_struct("Proxy")
357            .field("proto", &self.inner.proto)
358            .field("uri", &DebugUri(&self.inner.uri))
359            .field("from_env", &self.inner.from_env)
360            .finish()
361    }
362}
363
364impl fmt::Display for Proto {
365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366        match self {
367            Proto::Http => write!(f, "HTTP"),
368            Proto::Https => write!(f, "HTTPS"),
369            Proto::Socks4 => write!(f, "SOCKS4"),
370            Proto::Socks4A => write!(f, "SOCKS4a"),
371            Proto::Socks5 => write!(f, "SOCKS5"),
372        }
373    }
374}
375
376impl fmt::Debug for ConnectProxyConnector {
377    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378        f.debug_struct("ProxyConnector").finish()
379    }
380}
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385
386    #[test]
387    fn parse_proxy_fakeproto() {
388        assert!(Proxy::new("fakeproto://localhost").is_err());
389    }
390
391    #[test]
392    fn parse_proxy_http_user_pass_server_port() {
393        let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999").unwrap();
394        assert_eq!(proxy.username(), Some("user"));
395        assert_eq!(proxy.password(), Some("p@ssw0rd"));
396        assert_eq!(proxy.host(), "localhost");
397        assert_eq!(proxy.port(), 9999);
398        assert_eq!(proxy.inner.proto, Proto::Http);
399    }
400
401    #[test]
402    fn parse_proxy_http_user_pass_server_port_trailing_slash() {
403        let proxy = Proxy::new("http://user:p@ssw0rd@localhost:9999/").unwrap();
404        assert_eq!(proxy.username(), Some("user"));
405        assert_eq!(proxy.password(), Some("p@ssw0rd"));
406        assert_eq!(proxy.host(), "localhost");
407        assert_eq!(proxy.port(), 9999);
408        assert_eq!(proxy.inner.proto, Proto::Http);
409    }
410
411    #[test]
412    fn parse_proxy_socks4_user_pass_server_port() {
413        let proxy = Proxy::new("socks4://user:p@ssw0rd@localhost:9999").unwrap();
414        assert_eq!(proxy.username(), Some("user"));
415        assert_eq!(proxy.password(), Some("p@ssw0rd"));
416        assert_eq!(proxy.host(), "localhost");
417        assert_eq!(proxy.port(), 9999);
418        assert_eq!(proxy.inner.proto, Proto::Socks4);
419    }
420
421    #[test]
422    fn parse_proxy_socks4a_user_pass_server_port() {
423        let proxy = Proxy::new("socks4a://user:p@ssw0rd@localhost:9999").unwrap();
424        assert_eq!(proxy.username(), Some("user"));
425        assert_eq!(proxy.password(), Some("p@ssw0rd"));
426        assert_eq!(proxy.host(), "localhost");
427        assert_eq!(proxy.port(), 9999);
428        assert_eq!(proxy.inner.proto, Proto::Socks4A);
429    }
430
431    #[test]
432    fn parse_proxy_socks_user_pass_server_port() {
433        let proxy = Proxy::new("socks://user:p@ssw0rd@localhost:9999").unwrap();
434        assert_eq!(proxy.username(), Some("user"));
435        assert_eq!(proxy.password(), Some("p@ssw0rd"));
436        assert_eq!(proxy.host(), "localhost");
437        assert_eq!(proxy.port(), 9999);
438        assert_eq!(proxy.inner.proto, Proto::Socks5);
439    }
440
441    #[test]
442    fn parse_proxy_socks5_user_pass_server_port() {
443        let proxy = Proxy::new("socks5://user:p@ssw0rd@localhost:9999").unwrap();
444        assert_eq!(proxy.username(), Some("user"));
445        assert_eq!(proxy.password(), Some("p@ssw0rd"));
446        assert_eq!(proxy.host(), "localhost");
447        assert_eq!(proxy.port(), 9999);
448        assert_eq!(proxy.inner.proto, Proto::Socks5);
449    }
450
451    #[test]
452    fn parse_proxy_user_pass_server_port() {
453        let proxy = Proxy::new("user:p@ssw0rd@localhost:9999").unwrap();
454        assert_eq!(proxy.username(), Some("user"));
455        assert_eq!(proxy.password(), Some("p@ssw0rd"));
456        assert_eq!(proxy.host(), "localhost");
457        assert_eq!(proxy.port(), 9999);
458        assert_eq!(proxy.inner.proto, Proto::Http);
459    }
460
461    #[test]
462    fn parse_proxy_server_port() {
463        let proxy = Proxy::new("localhost:9999").unwrap();
464        assert_eq!(proxy.username(), None);
465        assert_eq!(proxy.password(), None);
466        assert_eq!(proxy.host(), "localhost");
467        assert_eq!(proxy.port(), 9999);
468        assert_eq!(proxy.inner.proto, Proto::Http);
469    }
470
471    #[test]
472    fn parse_proxy_server() {
473        let proxy = Proxy::new("localhost").unwrap();
474        assert_eq!(proxy.username(), None);
475        assert_eq!(proxy.password(), None);
476        assert_eq!(proxy.host(), "localhost");
477        assert_eq!(proxy.port(), 80);
478        assert_eq!(proxy.inner.proto, Proto::Http);
479    }
480}
481
482#[cfg(test)]
483mod test {
484    use super::*;
485    use assert_no_alloc::*;
486
487    #[test]
488    fn proxy_clone_does_not_allocate() {
489        let c = Proxy::new("socks://1.2.3.4").unwrap();
490        assert_no_alloc(|| c.clone());
491    }
492
493    #[test]
494    fn proxy_new_default_scheme() {
495        let c = Proxy::new("localhost:1234").unwrap();
496        assert_eq!(c.proto(), Proto::Http);
497        assert_eq!(c.uri(), "http://localhost:1234");
498    }
499
500    #[test]
501    fn proxy_empty_env_url() {
502        let result = Proxy::new_with_flag("", false);
503        assert!(result.is_err());
504    }
505
506    #[test]
507    fn proxy_invalid_env_url() {
508        let result = Proxy::new_with_flag("r32/?//52:**", false);
509        assert!(result.is_err());
510    }
511}