Send user-agent in proxy tunnel requests
This commit is contained in:
		| @@ -137,6 +137,11 @@ impl ClientBuilder { | |||||||
|         let proxies = Arc::new(config.proxies); |         let proxies = Arc::new(config.proxies); | ||||||
|  |  | ||||||
|         let mut connector = { |         let mut connector = { | ||||||
|  |             #[cfg(feature = "tls")] | ||||||
|  |             fn user_agent(headers: &HeaderMap) -> HeaderValue { | ||||||
|  |                 headers[USER_AGENT].clone() | ||||||
|  |             } | ||||||
|  |  | ||||||
|             #[cfg(feature = "tls")] |             #[cfg(feature = "tls")] | ||||||
|             match config.tls { |             match config.tls { | ||||||
|                 #[cfg(feature = "default-tls")] |                 #[cfg(feature = "default-tls")] | ||||||
| @@ -156,6 +161,7 @@ impl ClientBuilder { | |||||||
|                     Connector::new_default_tls( |                     Connector::new_default_tls( | ||||||
|                         tls, |                         tls, | ||||||
|                         proxies.clone(), |                         proxies.clone(), | ||||||
|  |                         user_agent(&config.headers), | ||||||
|                         config.local_address, |                         config.local_address, | ||||||
|                         config.nodelay, |                         config.nodelay, | ||||||
|                     )? |                     )? | ||||||
| @@ -189,6 +195,7 @@ impl ClientBuilder { | |||||||
|                     Connector::new_rustls_tls( |                     Connector::new_rustls_tls( | ||||||
|                         tls, |                         tls, | ||||||
|                         proxies.clone(), |                         proxies.clone(), | ||||||
|  |                         user_agent(&config.headers), | ||||||
|                         config.local_address, |                         config.local_address, | ||||||
|                         config.nodelay, |                         config.nodelay, | ||||||
|                     )? |                     )? | ||||||
|   | |||||||
							
								
								
									
										102
									
								
								src/connect.rs
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								src/connect.rs
									
									
									
									
									
								
							| @@ -5,6 +5,8 @@ use tokio::io::{AsyncRead, AsyncWrite}; | |||||||
|  |  | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "default-tls")] | ||||||
| use native_tls::{TlsConnector, TlsConnectorBuilder}; | use native_tls::{TlsConnector, TlsConnectorBuilder}; | ||||||
|  | #[cfg(feature = "tls")] | ||||||
|  | use http::header::HeaderValue; | ||||||
|  |  | ||||||
| use std::future::Future; | use std::future::Future; | ||||||
| use std::io; | use std::io; | ||||||
| @@ -23,12 +25,15 @@ use tokio::future::FutureExt as _; | |||||||
| //#[cfg(not(feature = "trust-dns"))] | //#[cfg(not(feature = "trust-dns"))] | ||||||
| type HttpConnector = hyper::client::HttpConnector; | type HttpConnector = hyper::client::HttpConnector; | ||||||
|  |  | ||||||
|  | #[derive(Clone)] | ||||||
| pub(crate) struct Connector { | pub(crate) struct Connector { | ||||||
|     inner: Inner, |     inner: Inner, | ||||||
|     proxies: Arc<Vec<Proxy>>, |     proxies: Arc<Vec<Proxy>>, | ||||||
|     timeout: Option<Duration>, |     timeout: Option<Duration>, | ||||||
|     #[cfg(feature = "tls")] |     #[cfg(feature = "tls")] | ||||||
|     nodelay: bool, |     nodelay: bool, | ||||||
|  |     #[cfg(feature = "tls")] | ||||||
|  |     user_agent: HeaderValue, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| @@ -69,6 +74,7 @@ impl Connector { | |||||||
|     pub(crate) fn new_default_tls<T>( |     pub(crate) fn new_default_tls<T>( | ||||||
|         tls: TlsConnectorBuilder, |         tls: TlsConnectorBuilder, | ||||||
|         proxies: Arc<Vec<Proxy>>, |         proxies: Arc<Vec<Proxy>>, | ||||||
|  |         user_agent: HeaderValue, | ||||||
|         local_addr: T, |         local_addr: T, | ||||||
|         nodelay: bool, |         nodelay: bool, | ||||||
|     ) -> crate::Result<Connector> |     ) -> crate::Result<Connector> | ||||||
| @@ -86,6 +92,7 @@ impl Connector { | |||||||
|             proxies, |             proxies, | ||||||
|             timeout: None, |             timeout: None, | ||||||
|             nodelay, |             nodelay, | ||||||
|  |             user_agent, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -93,6 +100,7 @@ impl Connector { | |||||||
|     pub(crate) fn new_rustls_tls<T>( |     pub(crate) fn new_rustls_tls<T>( | ||||||
|         tls: rustls::ClientConfig, |         tls: rustls::ClientConfig, | ||||||
|         proxies: Arc<Vec<Proxy>>, |         proxies: Arc<Vec<Proxy>>, | ||||||
|  |         user_agent: HeaderValue, | ||||||
|         local_addr: T, |         local_addr: T, | ||||||
|         nodelay: bool, |         nodelay: bool, | ||||||
|     ) -> crate::Result<Connector> |     ) -> crate::Result<Connector> | ||||||
| @@ -121,6 +129,7 @@ impl Connector { | |||||||
|             proxies, |             proxies, | ||||||
|             timeout: None, |             timeout: None, | ||||||
|             nodelay, |             nodelay, | ||||||
|  |             user_agent, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -186,30 +195,15 @@ impl Connector { | |||||||
|             Inner::Http(_) => socks::connect(proxy, dst, dns), |             Inner::Http(_) => socks::connect(proxy, dst, dns), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } |  | ||||||
|  |  | ||||||
| //#[cfg(feature = "trust-dns")] |     async fn connect_with_maybe_proxy( | ||||||
| //fn http_connector() -> crate::Result<HttpConnector> { |         self, | ||||||
| //    TrustDnsResolver::new() |  | ||||||
| //        .map(HttpConnector::new_with_resolver) |  | ||||||
| //        .map_err(crate::error::dns_system_conf) |  | ||||||
| //} |  | ||||||
|  |  | ||||||
| //#[cfg(not(feature = "trust-dns"))] |  | ||||||
| fn http_connector() -> crate::Result<HttpConnector> { |  | ||||||
|     Ok(HttpConnector::new()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async fn connect_with_maybe_proxy( |  | ||||||
|     inner: Inner, |  | ||||||
|         dst: Destination, |         dst: Destination, | ||||||
|         is_proxy: bool, |         is_proxy: bool, | ||||||
|     no_delay: bool, |     ) -> Result<(Conn, Connected), io::Error> { | ||||||
| ) -> Result<(Conn, Connected), io::Error> { |         match self.inner { | ||||||
|     match inner { |  | ||||||
|             #[cfg(not(feature = "tls"))] |             #[cfg(not(feature = "tls"))] | ||||||
|             Inner::Http(http) => { |             Inner::Http(http) => { | ||||||
|             drop(no_delay); // only used for TLS? |  | ||||||
|                 let (io, connected) = http.connect(dst).await?; |                 let (io, connected) = http.connect(dst).await?; | ||||||
|                 Ok((Box::new(io) as Conn, connected.proxy(is_proxy))) |                 Ok((Box::new(io) as Conn, connected.proxy(is_proxy))) | ||||||
|             } |             } | ||||||
| @@ -217,7 +211,7 @@ async fn connect_with_maybe_proxy( | |||||||
|             Inner::DefaultTls(http, tls) => { |             Inner::DefaultTls(http, tls) => { | ||||||
|                 let mut http = http.clone(); |                 let mut http = http.clone(); | ||||||
|  |  | ||||||
|             http.set_nodelay(no_delay || (dst.scheme() == "https")); |                 http.set_nodelay(self.nodelay || (dst.scheme() == "https")); | ||||||
|  |  | ||||||
|                 let tls_connector = tokio_tls::TlsConnector::from(tls.clone()); |                 let tls_connector = tokio_tls::TlsConnector::from(tls.clone()); | ||||||
|                 let http = hyper_tls::HttpsConnector::from((http, tls_connector)); |                 let http = hyper_tls::HttpsConnector::from((http, tls_connector)); | ||||||
| @@ -252,14 +246,13 @@ async fn connect_with_maybe_proxy( | |||||||
|                 Ok((Box::new(io) as Conn, connected.proxy(is_proxy))) |                 Ok((Box::new(io) as Conn, connected.proxy(is_proxy))) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| } |     } | ||||||
|  |  | ||||||
| async fn connect_via_proxy( |     async fn connect_via_proxy( | ||||||
|     inner: Inner, |         self, | ||||||
|         dst: Destination, |         dst: Destination, | ||||||
|         proxy_scheme: ProxyScheme, |         proxy_scheme: ProxyScheme, | ||||||
|     no_delay: bool, |     ) -> Result<(Conn, Connected), io::Error> { | ||||||
| ) -> Result<(Conn, Connected), io::Error> { |  | ||||||
|         log::trace!("proxy({:?}) intercepts {:?}", proxy_scheme, dst); |         log::trace!("proxy({:?}) intercepts {:?}", proxy_scheme, dst); | ||||||
|  |  | ||||||
|         let (puri, _auth) = match proxy_scheme { |         let (puri, _auth) = match proxy_scheme { | ||||||
| @@ -282,19 +275,19 @@ async fn connect_via_proxy( | |||||||
|         #[cfg(feature = "tls")] |         #[cfg(feature = "tls")] | ||||||
|         let auth = _auth; |         let auth = _auth; | ||||||
|  |  | ||||||
|     match &inner { |         match &self.inner { | ||||||
|             #[cfg(feature = "default-tls")] |             #[cfg(feature = "default-tls")] | ||||||
|             Inner::DefaultTls(http, tls) => { |             Inner::DefaultTls(http, tls) => { | ||||||
|                 if dst.scheme() == "https" { |                 if dst.scheme() == "https" { | ||||||
|                     let host = dst.host().to_owned(); |                     let host = dst.host().to_owned(); | ||||||
|                     let port = dst.port().unwrap_or(443); |                     let port = dst.port().unwrap_or(443); | ||||||
|                     let mut http = http.clone(); |                     let mut http = http.clone(); | ||||||
|                 http.set_nodelay(no_delay); |                     http.set_nodelay(self.nodelay); | ||||||
|                     let tls_connector = tokio_tls::TlsConnector::from(tls.clone()); |                     let tls_connector = tokio_tls::TlsConnector::from(tls.clone()); | ||||||
|                     let http = hyper_tls::HttpsConnector::from((http, tls_connector)); |                     let http = hyper_tls::HttpsConnector::from((http, tls_connector)); | ||||||
|                     let (conn, connected) = http.connect(ndst).await?; |                     let (conn, connected) = http.connect(ndst).await?; | ||||||
|                     log::trace!("tunneling HTTPS over proxy"); |                     log::trace!("tunneling HTTPS over proxy"); | ||||||
|                 let tunneled = tunnel(conn, host.clone(), port, auth).await?; |                     let tunneled = tunnel(conn, host.clone(), port, self.user_agent.clone(), auth).await?; | ||||||
|                     let tls_connector = tokio_tls::TlsConnector::from(tls.clone()); |                     let tls_connector = tokio_tls::TlsConnector::from(tls.clone()); | ||||||
|                     let io = tls_connector |                     let io = tls_connector | ||||||
|                         .connect(&host, tunneled) |                         .connect(&host, tunneled) | ||||||
| @@ -343,9 +336,23 @@ async fn connect_via_proxy( | |||||||
|             Inner::Http(_) => (), |             Inner::Http(_) => (), | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     connect_with_maybe_proxy(inner, ndst, true, no_delay).await |         self.connect_with_maybe_proxy(ndst, true).await | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | //#[cfg(feature = "trust-dns")] | ||||||
|  | //fn http_connector() -> crate::Result<HttpConnector> { | ||||||
|  | //    TrustDnsResolver::new() | ||||||
|  | //        .map(HttpConnector::new_with_resolver) | ||||||
|  | //        .map_err(crate::error::dns_system_conf) | ||||||
|  | //} | ||||||
|  |  | ||||||
|  | //#[cfg(not(feature = "trust-dns"))] | ||||||
|  | fn http_connector() -> crate::Result<HttpConnector> { | ||||||
|  |     Ok(HttpConnector::new()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| async fn with_timeout<T, F>(f: F, timeout: Option<Duration>) -> Result<T, io::Error> | async fn with_timeout<T, F>(f: F, timeout: Option<Duration>) -> Result<T, io::Error> | ||||||
| where | where | ||||||
|     F: Future<Output = Result<T, io::Error>>, |     F: Future<Output = Result<T, io::Error>>, | ||||||
| @@ -366,15 +373,11 @@ impl Connect for Connector { | |||||||
|     type Future = Connecting; |     type Future = Connecting; | ||||||
|  |  | ||||||
|     fn connect(&self, dst: Destination) -> Self::Future { |     fn connect(&self, dst: Destination) -> Self::Future { | ||||||
|         #[cfg(feature = "tls")] |  | ||||||
|         let no_delay = self.nodelay; |  | ||||||
|         #[cfg(not(feature = "tls"))] |  | ||||||
|         let no_delay = false; |  | ||||||
|         let timeout = self.timeout; |         let timeout = self.timeout; | ||||||
|         for prox in self.proxies.iter() { |         for prox in self.proxies.iter() { | ||||||
|             if let Some(proxy_scheme) = prox.intercept(&dst) { |             if let Some(proxy_scheme) = prox.intercept(&dst) { | ||||||
|                 return with_timeout( |                 return with_timeout( | ||||||
|                     connect_via_proxy(self.inner.clone(), dst, proxy_scheme, no_delay), |                     self.clone().connect_via_proxy(dst, proxy_scheme), | ||||||
|                     timeout, |                     timeout, | ||||||
|                 ) |                 ) | ||||||
|                 .boxed(); |                 .boxed(); | ||||||
| @@ -382,7 +385,7 @@ impl Connect for Connector { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         with_timeout( |         with_timeout( | ||||||
|             connect_with_maybe_proxy(self.inner.clone(), dst, false, no_delay), |             self.clone().connect_with_maybe_proxy(dst, false), | ||||||
|             timeout, |             timeout, | ||||||
|         ) |         ) | ||||||
|         .boxed() |         .boxed() | ||||||
| @@ -401,7 +404,8 @@ async fn tunnel<T>( | |||||||
|     mut conn: T, |     mut conn: T, | ||||||
|     host: String, |     host: String, | ||||||
|     port: u16, |     port: u16, | ||||||
|     auth: Option<http::header::HeaderValue>, |     user_agent: HeaderValue, | ||||||
|  |     auth: Option<HeaderValue>, | ||||||
| ) -> Result<T, io::Error> | ) -> Result<T, io::Error> | ||||||
| where | where | ||||||
|     T: AsyncRead + AsyncWrite + Unpin, |     T: AsyncRead + AsyncWrite + Unpin, | ||||||
| @@ -417,6 +421,14 @@ where | |||||||
|     ) |     ) | ||||||
|     .into_bytes(); |     .into_bytes(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     // user-agent | ||||||
|  |     buf.extend_from_slice(b"User-Agent: "); | ||||||
|  |     buf.extend_from_slice(user_agent.as_bytes()); | ||||||
|  |     buf.extend_from_slice(b"\r\n"); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     // proxy-authorization | ||||||
|     if let Some(value) = auth { |     if let Some(value) = auth { | ||||||
|         log::debug!("tunnel to {}:{} using basic auth", host, port); |         log::debug!("tunnel to {}:{} using basic auth", host, port); | ||||||
|         buf.extend_from_slice(b"Proxy-Authorization: "); |         buf.extend_from_slice(b"Proxy-Authorization: "); | ||||||
| @@ -541,6 +553,7 @@ mod tests { | |||||||
|     use tokio::net::tcp::TcpStream; |     use tokio::net::tcp::TcpStream; | ||||||
|     use tokio::runtime::current_thread::Runtime; |     use tokio::runtime::current_thread::Runtime; | ||||||
|  |  | ||||||
|  |     static TUNNEL_UA: &'static str = "tunnel-test/x.y"; | ||||||
|     static TUNNEL_OK: &[u8] = b"\ |     static TUNNEL_OK: &[u8] = b"\ | ||||||
|         HTTP/1.1 200 OK\r\n\ |         HTTP/1.1 200 OK\r\n\ | ||||||
|         \r\n\ |         \r\n\ | ||||||
| @@ -560,11 +573,13 @@ mod tests { | |||||||
|                 "\ |                 "\ | ||||||
|                  CONNECT {0}:{1} HTTP/1.1\r\n\ |                  CONNECT {0}:{1} HTTP/1.1\r\n\ | ||||||
|                  Host: {0}:{1}\r\n\ |                  Host: {0}:{1}\r\n\ | ||||||
|                  {2}\ |                  User-Agent: {2}\r\n\ | ||||||
|  |                  {3}\ | ||||||
|                  \r\n\ |                  \r\n\ | ||||||
|                  ", |                  ", | ||||||
|                 addr.ip(), |                 addr.ip(), | ||||||
|                 addr.port(), |                 addr.port(), | ||||||
|  |                 TUNNEL_UA, | ||||||
|                 $auth |                 $auth | ||||||
|             ) |             ) | ||||||
|             .into_bytes(); |             .into_bytes(); | ||||||
| @@ -581,6 +596,10 @@ mod tests { | |||||||
|         }}; |         }}; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn ua() -> http::header::HeaderValue { | ||||||
|  |         http::header::HeaderValue::from_static(TUNNEL_UA) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_tunnel() { |     fn test_tunnel() { | ||||||
|         let addr = mock_tunnel!(); |         let addr = mock_tunnel!(); | ||||||
| @@ -590,7 +609,7 @@ mod tests { | |||||||
|             let tcp = TcpStream::connect(&addr).await?; |             let tcp = TcpStream::connect(&addr).await?; | ||||||
|             let host = addr.ip().to_string(); |             let host = addr.ip().to_string(); | ||||||
|             let port = addr.port(); |             let port = addr.port(); | ||||||
|             tunnel(tcp, host, port, None).await |             tunnel(tcp, host, port, ua(), None).await | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         rt.block_on(f).unwrap(); |         rt.block_on(f).unwrap(); | ||||||
| @@ -605,7 +624,7 @@ mod tests { | |||||||
|             let tcp = TcpStream::connect(&addr).await?; |             let tcp = TcpStream::connect(&addr).await?; | ||||||
|             let host = addr.ip().to_string(); |             let host = addr.ip().to_string(); | ||||||
|             let port = addr.port(); |             let port = addr.port(); | ||||||
|             tunnel(tcp, host, port, None).await |             tunnel(tcp, host, port, ua(), None).await | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         rt.block_on(f).unwrap_err(); |         rt.block_on(f).unwrap_err(); | ||||||
| @@ -620,7 +639,7 @@ mod tests { | |||||||
|             let tcp = TcpStream::connect(&addr).await?; |             let tcp = TcpStream::connect(&addr).await?; | ||||||
|             let host = addr.ip().to_string(); |             let host = addr.ip().to_string(); | ||||||
|             let port = addr.port(); |             let port = addr.port(); | ||||||
|             tunnel(tcp, host, port, None).await |             tunnel(tcp, host, port, ua(), None).await | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         rt.block_on(f).unwrap_err(); |         rt.block_on(f).unwrap_err(); | ||||||
| @@ -641,7 +660,7 @@ mod tests { | |||||||
|             let tcp = TcpStream::connect(&addr).await?; |             let tcp = TcpStream::connect(&addr).await?; | ||||||
|             let host = addr.ip().to_string(); |             let host = addr.ip().to_string(); | ||||||
|             let port = addr.port(); |             let port = addr.port(); | ||||||
|             tunnel(tcp, host, port, None).await |             tunnel(tcp, host, port, ua(), None).await | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let error = rt.block_on(f).unwrap_err(); |         let error = rt.block_on(f).unwrap_err(); | ||||||
| @@ -664,6 +683,7 @@ mod tests { | |||||||
|                 tcp, |                 tcp, | ||||||
|                 host, |                 host, | ||||||
|                 port, |                 port, | ||||||
|  |                 ua(), | ||||||
|                 Some(proxy::encode_basic_auth("Aladdin", "open sesame")), |                 Some(proxy::encode_basic_auth("Aladdin", "open sesame")), | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user