hyper_util/client/legacy/connect/proxy/
tunnel.rs1use 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#[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 #[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 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 pub fn with_auth(mut self, mut auth: HeaderValue) -> Self {
77 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 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 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}