Skip to main content

hyper_util/client/legacy/connect/proxy/
tunnel.rs

1use std::error::Error as StdError;
2use std::marker::PhantomData;
3use std::pin::Pin;
4use std::task::{self, Poll, ready};
5
6use http::{HeaderMap, HeaderValue, Uri};
7use hyper::rt::{Read, Write};
8use pin_project_lite::pin_project;
9use tower_service::Service;
10
11/// Tunnel Proxy via HTTP CONNECT
12///
13/// This is a connector that can be used by the `legacy::Client`. It wraps
14/// another connector, and after getting an underlying connection, it creates
15/// an HTTP CONNECT tunnel over it.
16#[derive(Debug, Clone)]
17pub struct Tunnel<C> {
18    headers: Headers,
19    inner: C,
20    proxy_dst: Uri,
21}
22
23#[derive(Clone, Debug)]
24enum Headers {
25    Empty,
26    Auth(HeaderValue),
27    Extra(HeaderMap),
28}
29
30#[derive(Debug)]
31pub enum TunnelError {
32    ConnectFailed(Box<dyn StdError + Send + Sync>),
33    Io(std::io::Error),
34    MissingHost,
35    ProxyAuthRequired,
36    ProxyHeadersTooLong,
37    TunnelUnexpectedEof,
38    TunnelUnsuccessful,
39}
40
41pin_project! {
42    // Not publicly exported (so missing_docs doesn't trigger).
43    //
44    // We return this `Future` instead of the `Pin<Box<dyn Future>>` directly
45    // so that users don't rely on it fitting in a `Pin<Box<dyn Future>>` slot
46    // (and thus we can change the type in the future).
47    #[must_use = "futures do nothing unless polled"]
48    #[allow(missing_debug_implementations)]
49    pub struct Tunneling<F, T> {
50        #[pin]
51        fut: BoxTunneling<T>,
52        _marker: PhantomData<F>,
53    }
54}
55
56type BoxTunneling<T> = Pin<Box<dyn Future<Output = Result<T, TunnelError>> + Send>>;
57
58impl<C> Tunnel<C> {
59    /// Create a new Tunnel service.
60    ///
61    /// This wraps an underlying connector, and stores the address of a
62    /// tunneling proxy server.
63    ///
64    /// A `Tunnel` can then be called with any destination. The `dst` passed to
65    /// `call` will not be used to create the underlying connection, but will
66    /// be used in an HTTP CONNECT request sent to the proxy destination.
67    pub fn new(proxy_dst: Uri, connector: C) -> Self {
68        Self {
69            headers: Headers::Empty,
70            inner: connector,
71            proxy_dst,
72        }
73    }
74
75    /// Add `proxy-authorization` header value to the CONNECT request.
76    pub fn with_auth(mut self, mut auth: HeaderValue) -> Self {
77        // just in case the user forgot
78        auth.set_sensitive(true);
79        match self.headers {
80            Headers::Empty => {
81                self.headers = Headers::Auth(auth);
82            }
83            Headers::Auth(ref mut existing) => {
84                *existing = auth;
85            }
86            Headers::Extra(ref mut extra) => {
87                extra.insert(http::header::PROXY_AUTHORIZATION, auth);
88            }
89        }
90
91        self
92    }
93
94    /// Add extra headers to be sent with the CONNECT request.
95    ///
96    /// If existing headers have been set, these will be merged.
97    pub fn with_headers(mut self, mut headers: HeaderMap) -> Self {
98        match self.headers {
99            Headers::Empty => {
100                self.headers = Headers::Extra(headers);
101            }
102            Headers::Auth(auth) => {
103                headers
104                    .entry(http::header::PROXY_AUTHORIZATION)
105                    .or_insert(auth);
106                self.headers = Headers::Extra(headers);
107            }
108            Headers::Extra(ref mut extra) => {
109                extra.extend(headers);
110            }
111        }
112
113        self
114    }
115}
116
117impl<C> Service<Uri> for Tunnel<C>
118where
119    C: Service<Uri>,
120    C::Future: Send + 'static,
121    C::Response: Read + Write + Unpin + Send + 'static,
122    C::Error: Into<Box<dyn StdError + Send + Sync>>,
123{
124    type Response = C::Response;
125    type Error = TunnelError;
126    type Future = Tunneling<C::Future, C::Response>;
127
128    fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
129        ready!(self.inner.poll_ready(cx)).map_err(|e| TunnelError::ConnectFailed(e.into()))?;
130        Poll::Ready(Ok(()))
131    }
132
133    fn call(&mut self, dst: Uri) -> Self::Future {
134        let connecting = self.inner.call(self.proxy_dst.clone());
135        let headers = self.headers.clone();
136
137        Tunneling {
138            fut: Box::pin(async move {
139                let conn = connecting
140                    .await
141                    .map_err(|e| TunnelError::ConnectFailed(e.into()))?;
142                tunnel(
143                    conn,
144                    dst.host().ok_or(TunnelError::MissingHost)?,
145                    dst.port().map(|p| p.as_u16()).unwrap_or(443),
146                    &headers,
147                )
148                .await
149            }),
150            _marker: PhantomData,
151        }
152    }
153}
154
155impl<F, T, E> Future for Tunneling<F, T>
156where
157    F: Future<Output = Result<T, E>>,
158{
159    type Output = Result<T, TunnelError>;
160
161    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
162        self.project().fut.poll(cx)
163    }
164}
165
166async fn tunnel<T>(mut conn: T, host: &str, port: u16, headers: &Headers) -> Result<T, TunnelError>
167where
168    T: Read + Write + Unpin,
169{
170    let mut buf = format!(
171        "\
172         CONNECT {host}:{port} HTTP/1.1\r\n\
173         Host: {host}:{port}\r\n\
174         "
175    )
176    .into_bytes();
177
178    match headers {
179        Headers::Auth(auth) => {
180            buf.extend_from_slice(b"Proxy-Authorization: ");
181            buf.extend_from_slice(auth.as_bytes());
182            buf.extend_from_slice(b"\r\n");
183        }
184        Headers::Extra(extra) => {
185            for (name, value) in extra {
186                buf.extend_from_slice(name.as_str().as_bytes());
187                buf.extend_from_slice(b": ");
188                buf.extend_from_slice(value.as_bytes());
189                buf.extend_from_slice(b"\r\n");
190            }
191        }
192        Headers::Empty => (),
193    }
194
195    // headers end
196    buf.extend_from_slice(b"\r\n");
197
198    crate::rt::write_all(&mut conn, &buf)
199        .await
200        .map_err(TunnelError::Io)?;
201
202    let mut buf = [0; 8192];
203    let mut pos = 0;
204
205    loop {
206        let n = crate::rt::read(&mut conn, &mut buf[pos..])
207            .await
208            .map_err(TunnelError::Io)?;
209
210        if n == 0 {
211            return Err(TunnelError::TunnelUnexpectedEof);
212        }
213        pos += n;
214
215        let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
216        let mut res = httparse::Response::new(&mut headers);
217        match res.parse(&buf[..pos]) {
218            Ok(httparse::Status::Complete(_)) => match res.code {
219                Some(200) => return Ok(conn),
220                Some(407) => return Err(TunnelError::ProxyAuthRequired),
221                _ => return Err(TunnelError::TunnelUnsuccessful),
222            },
223            Ok(httparse::Status::Partial) => {
224                if pos == buf.len() {
225                    return Err(TunnelError::ProxyHeadersTooLong);
226                }
227            }
228            Err(_) => return Err(TunnelError::TunnelUnsuccessful),
229        }
230    }
231}
232
233const MAX_HEADERS: usize = 100;
234
235impl std::fmt::Display for TunnelError {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        f.write_str("tunnel error: ")?;
238
239        f.write_str(match self {
240            TunnelError::MissingHost => "missing destination host",
241            TunnelError::ProxyAuthRequired => "proxy authorization required",
242            TunnelError::ProxyHeadersTooLong => "proxy response headers too long",
243            TunnelError::TunnelUnexpectedEof => "unexpected end of file",
244            TunnelError::TunnelUnsuccessful => "unsuccessful",
245            TunnelError::ConnectFailed(_) => "failed to create underlying connection",
246            TunnelError::Io(_) => "io error establishing tunnel",
247        })
248    }
249}
250
251impl std::error::Error for TunnelError {
252    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
253        match self {
254            TunnelError::Io(e) => Some(e),
255            TunnelError::ConnectFailed(e) => Some(&**e),
256            _ => None,
257        }
258    }
259}
260
261#[cfg(all(test, feature = "tokio"))]
262mod tests {
263    use std::time::Duration;
264
265    use tokio::io::AsyncWriteExt;
266
267    use super::{Headers, TunnelError, tunnel};
268    use crate::rt::TokioIo;
269
270    async fn handshake(response: &'static [u8]) -> Result<(), TunnelError> {
271        let (client, mut server) = tokio::io::duplex(1024);
272        tokio::spawn(async move {
273            server.write_all(response).await.unwrap();
274        });
275
276        tokio::time::timeout(
277            Duration::from_secs(1),
278            tunnel(TokioIo::new(client), "example.com", 443, &Headers::Empty),
279        )
280        .await
281        .expect("handshake should not hang")
282        .map(drop)
283    }
284
285    #[tokio::test]
286    async fn established() {
287        handshake(b"HTTP/1.1 200 Connection established\r\n\r\n")
288            .await
289            .expect("200 response should establish the tunnel");
290    }
291
292    #[tokio::test]
293    async fn established_with_early_data() {
294        handshake(b"HTTP/1.1 200 OK\r\n\r\nHELLO")
295            .await
296            .expect("early data must not prevent establishing the tunnel");
297    }
298
299    #[tokio::test]
300    async fn proxy_auth_required() {
301        let err = handshake(b"HTTP/1.1 407 Proxy Authentication Required\r\n\r\n")
302            .await
303            .unwrap_err();
304        assert!(matches!(err, TunnelError::ProxyAuthRequired));
305    }
306
307    #[tokio::test]
308    async fn non_200_is_unsuccessful() {
309        let err = handshake(b"HTTP/1.1 500 Internal Server Error\r\n\r\n")
310            .await
311            .unwrap_err();
312        assert!(matches!(err, TunnelError::TunnelUnsuccessful));
313    }
314
315    #[tokio::test]
316    async fn malformed_status_is_rejected() {
317        let err = handshake(b"HTTP/1.1 2000 OK\r\n\r\n").await.unwrap_err();
318        assert!(matches!(err, TunnelError::TunnelUnsuccessful));
319    }
320}