| @@ -4,10 +4,24 @@ use std::time::Duration; | |||||||
|  |  | ||||||
| use bytes::Bytes; | use bytes::Bytes; | ||||||
| use futures::{Async, Future, Poll}; | use futures::{Async, Future, Poll}; | ||||||
|  | use header::{ | ||||||
|  |     HeaderMap, | ||||||
|  |     HeaderValue, | ||||||
|  |     ACCEPT, | ||||||
|  |     ACCEPT_ENCODING, | ||||||
|  |     CONTENT_LENGTH, | ||||||
|  |     CONTENT_ENCODING, | ||||||
|  |     CONTENT_TYPE, | ||||||
|  |     LOCATION, | ||||||
|  |     PROXY_AUTHORIZATION, | ||||||
|  |     RANGE, | ||||||
|  |     REFERER, | ||||||
|  |     TRANSFER_ENCODING, | ||||||
|  |     USER_AGENT, | ||||||
|  | }; | ||||||
|  | use http::Uri; | ||||||
| use hyper::client::ResponseFuture; | use hyper::client::ResponseFuture; | ||||||
| use header::{HeaderMap, HeaderValue, LOCATION, USER_AGENT, REFERER, ACCEPT, | use mime; | ||||||
|              ACCEPT_ENCODING, RANGE, TRANSFER_ENCODING, CONTENT_TYPE, CONTENT_LENGTH, CONTENT_ENCODING}; |  | ||||||
| use mime::{self}; |  | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "default-tls")] | ||||||
| use native_tls::TlsConnector; | use native_tls::TlsConnector; | ||||||
|  |  | ||||||
| @@ -197,6 +211,10 @@ impl ClientBuilder { | |||||||
|         let hyper_client = ::hyper::Client::builder() |         let hyper_client = ::hyper::Client::builder() | ||||||
|             .build(connector); |             .build(connector); | ||||||
|  |  | ||||||
|  |         let proxies_maybe_http_auth = proxies | ||||||
|  |             .iter() | ||||||
|  |             .any(|p| p.maybe_has_http_auth()); | ||||||
|  |  | ||||||
|         Ok(Client { |         Ok(Client { | ||||||
|             inner: Arc::new(ClientRef { |             inner: Arc::new(ClientRef { | ||||||
|                 gzip: config.gzip, |                 gzip: config.gzip, | ||||||
| @@ -204,6 +222,8 @@ impl ClientBuilder { | |||||||
|                 headers: config.headers, |                 headers: config.headers, | ||||||
|                 redirect_policy: config.redirect_policy, |                 redirect_policy: config.redirect_policy, | ||||||
|                 referer: config.referer, |                 referer: config.referer, | ||||||
|  |                 proxies, | ||||||
|  |                 proxies_maybe_http_auth, | ||||||
|             }), |             }), | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| @@ -470,6 +490,8 @@ impl Client { | |||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |         self.proxy_auth(&uri, &mut headers); | ||||||
|  |  | ||||||
|         let mut req = ::hyper::Request::builder() |         let mut req = ::hyper::Request::builder() | ||||||
|             .method(method.clone()) |             .method(method.clone()) | ||||||
|             .uri(uri.clone()) |             .uri(uri.clone()) | ||||||
| @@ -495,6 +517,40 @@ impl Client { | |||||||
|             }), |             }), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn proxy_auth(&self, dst: &Uri, headers: &mut HeaderMap) { | ||||||
|  |         if !self.inner.proxies_maybe_http_auth { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Only set the header here if the destination scheme is 'http', | ||||||
|  |         // since otherwise, the header will be included in the CONNECT tunnel | ||||||
|  |         // request instead. | ||||||
|  |         if dst.scheme_part() != Some(&::http::uri::Scheme::HTTP) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if headers.contains_key(PROXY_AUTHORIZATION) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         for proxy in self.inner.proxies.iter() { | ||||||
|  |             if proxy.is_match(dst) { | ||||||
|  |                 match proxy.auth() { | ||||||
|  |                     Some(::proxy::Auth::Basic(ref header)) => { | ||||||
|  |                         headers.insert( | ||||||
|  |                             PROXY_AUTHORIZATION, | ||||||
|  |                             header.clone() | ||||||
|  |                         ); | ||||||
|  |                     }, | ||||||
|  |                     None => (), | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl fmt::Debug for Client { | impl fmt::Debug for Client { | ||||||
| @@ -520,6 +576,8 @@ struct ClientRef { | |||||||
|     hyper: HyperClient, |     hyper: HyperClient, | ||||||
|     redirect_policy: RedirectPolicy, |     redirect_policy: RedirectPolicy, | ||||||
|     referer: bool, |     referer: bool, | ||||||
|  |     proxies: Arc<Vec<Proxy>>, | ||||||
|  |     proxies_maybe_http_auth: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct Pending { | pub struct Pending { | ||||||
|   | |||||||
| @@ -114,6 +114,9 @@ impl Connect for Connector { | |||||||
|  |  | ||||||
|                 ndst.set_port(puri.port_part().map(|port| port.as_u16())); |                 ndst.set_port(puri.port_part().map(|port| port.as_u16())); | ||||||
|  |  | ||||||
|  |                 #[cfg(feature = "tls")] | ||||||
|  |                 let auth = prox.auth().cloned(); | ||||||
|  |  | ||||||
|                 match &self.inner { |                 match &self.inner { | ||||||
|                     #[cfg(feature = "default-tls")] |                     #[cfg(feature = "default-tls")] | ||||||
|                     Inner::DefaultTls(http, tls) => if dst.scheme() == "https" { |                     Inner::DefaultTls(http, tls) => if dst.scheme() == "https" { | ||||||
| @@ -125,7 +128,7 @@ impl Connect for Connector { | |||||||
|                         let tls = tls.clone(); |                         let tls = tls.clone(); | ||||||
|                         return Box::new(http.connect(ndst).and_then(move |(conn, connected)| { |                         return Box::new(http.connect(ndst).and_then(move |(conn, connected)| { | ||||||
|                             trace!("tunneling HTTPS over proxy"); |                             trace!("tunneling HTTPS over proxy"); | ||||||
|                             tunnel(conn, host.clone(), port) |                             tunnel(conn, host.clone(), port, auth) | ||||||
|                                 .and_then(move |tunneled| { |                                 .and_then(move |tunneled| { | ||||||
|                                     tls.connect_async(&host, tunneled) |                                     tls.connect_async(&host, tunneled) | ||||||
|                                         .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) |                                         .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) | ||||||
| @@ -148,7 +151,7 @@ impl Connect for Connector { | |||||||
|                             let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host) |                             let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host) | ||||||
|                                 .map(|dnsname| dnsname.to_owned()) |                                 .map(|dnsname| dnsname.to_owned()) | ||||||
|                                 .map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name")); |                                 .map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name")); | ||||||
|                             tunnel(conn, host, port) |                             tunnel(conn, host, port, auth) | ||||||
|                                 .and_then(move |tunneled| Ok((maybe_dnsname?, tunneled))) |                                 .and_then(move |tunneled| Ok((maybe_dnsname?, tunneled))) | ||||||
|                                 .and_then(move |(dnsname, tunneled)| { |                                 .and_then(move |(dnsname, tunneled)| { | ||||||
|                                     RustlsConnector::from(tls).connect(dnsname.as_ref(), tunneled) |                                     RustlsConnector::from(tls).connect(dnsname.as_ref(), tunneled) | ||||||
| @@ -176,18 +179,30 @@ pub(crate) type Conn = Box<dyn AsyncConn + Send + Sync + 'static>; | |||||||
| pub(crate) type Connecting = Box<Future<Item=(Conn, Connected), Error=io::Error> + Send>; | pub(crate) type Connecting = Box<Future<Item=(Conn, Connected), Error=io::Error> + Send>; | ||||||
|  |  | ||||||
| #[cfg(feature = "tls")] | #[cfg(feature = "tls")] | ||||||
| fn tunnel<T>(conn: T, host: String, port: u16) -> Tunnel<T> { | fn tunnel<T>(conn: T, host: String, port: u16, auth: Option<::proxy::Auth>) -> Tunnel<T> { | ||||||
|      let buf = format!("\ |     let mut buf = format!("\ | ||||||
|         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\ | ||||||
|         \r\n\ |  | ||||||
|     ", host, port).into_bytes(); |     ", host, port).into_bytes(); | ||||||
|  |  | ||||||
|      Tunnel { |     match auth { | ||||||
|  |         Some(::proxy::Auth::Basic(value)) => { | ||||||
|  |             debug!("tunnel to {}:{} using basic auth", host, port); | ||||||
|  |             buf.extend_from_slice(b"Proxy-Authorization: "); | ||||||
|  |             buf.extend_from_slice(value.as_bytes()); | ||||||
|  |             buf.extend_from_slice(b"\r\n"); | ||||||
|  |         }, | ||||||
|  |         None => (), | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // headers end | ||||||
|  |     buf.extend_from_slice(b"\r\n"); | ||||||
|  |  | ||||||
|  |     Tunnel { | ||||||
|         buf: io::Cursor::new(buf), |         buf: io::Cursor::new(buf), | ||||||
|         conn: Some(conn), |         conn: Some(conn), | ||||||
|         state: TunnelState::Writing, |         state: TunnelState::Writing, | ||||||
|      } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(feature = "tls")] | #[cfg(feature = "tls")] | ||||||
| @@ -230,6 +245,8 @@ where T: AsyncRead + AsyncWrite { | |||||||
|                             return Ok(self.conn.take().unwrap().into()); |                             return Ok(self.conn.take().unwrap().into()); | ||||||
|                         } |                         } | ||||||
|                         // else read more |                         // else read more | ||||||
|  |                     } else if read.starts_with(b"HTTP/1.1 407") { | ||||||
|  |                         return Err(io::Error::new(io::ErrorKind::Other, "proxy authentication required")); | ||||||
|                     } else { |                     } else { | ||||||
|                         return Err(io::Error::new(io::ErrorKind::Other, "unsuccessful tunnel")); |                         return Err(io::Error::new(io::ErrorKind::Other, "unsuccessful tunnel")); | ||||||
|                     } |                     } | ||||||
| @@ -258,23 +275,29 @@ mod tests { | |||||||
|     use tokio::runtime::current_thread::Runtime; |     use tokio::runtime::current_thread::Runtime; | ||||||
|     use tokio::net::TcpStream; |     use tokio::net::TcpStream; | ||||||
|     use super::tunnel; |     use super::tunnel; | ||||||
|  |     use proxy; | ||||||
|  |  | ||||||
|  |     static TUNNEL_OK: &'static [u8] = b"\ | ||||||
|  |         HTTP/1.1 200 OK\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     "; | ||||||
|  |  | ||||||
|     macro_rules! mock_tunnel { |     macro_rules! mock_tunnel { | ||||||
|         () => ({ |         () => ({ | ||||||
|             mock_tunnel!(b"\ |             mock_tunnel!(TUNNEL_OK) | ||||||
|                 HTTP/1.1 200 OK\r\n\ |  | ||||||
|                 \r\n\ |  | ||||||
|             ") |  | ||||||
|         }); |         }); | ||||||
|         ($write:expr) => ({ |         ($write:expr) => ({ | ||||||
|  |             mock_tunnel!($write, "") | ||||||
|  |         }); | ||||||
|  |         ($write:expr, $auth:expr) => ({ | ||||||
|             let listener = TcpListener::bind("127.0.0.1:0").unwrap(); |             let listener = TcpListener::bind("127.0.0.1:0").unwrap(); | ||||||
|             let addr = listener.local_addr().unwrap(); |             let addr = listener.local_addr().unwrap(); | ||||||
|             let connect_expected = format!("\ |             let connect_expected = format!("\ | ||||||
|                 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}\ | ||||||
|                 \r\n\ |                 \r\n\ | ||||||
|             ", addr.ip(), addr.port()).into_bytes(); |             ", addr.ip(), addr.port(), $auth).into_bytes(); | ||||||
|  |  | ||||||
|             thread::spawn(move || { |             thread::spawn(move || { | ||||||
|                 let (mut sock, _) = listener.accept().unwrap(); |                 let (mut sock, _) = listener.accept().unwrap(); | ||||||
| @@ -297,7 +320,7 @@ mod tests { | |||||||
|         let host = addr.ip().to_string(); |         let host = addr.ip().to_string(); | ||||||
|         let port = addr.port(); |         let port = addr.port(); | ||||||
|         let work = work.and_then(|tcp| { |         let work = work.and_then(|tcp| { | ||||||
|             tunnel(tcp, host, port) |             tunnel(tcp, host, port, None) | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         rt.block_on(work).unwrap(); |         rt.block_on(work).unwrap(); | ||||||
| @@ -312,14 +335,14 @@ mod tests { | |||||||
|         let host = addr.ip().to_string(); |         let host = addr.ip().to_string(); | ||||||
|         let port = addr.port(); |         let port = addr.port(); | ||||||
|         let work = work.and_then(|tcp| { |         let work = work.and_then(|tcp| { | ||||||
|             tunnel(tcp, host, port) |             tunnel(tcp, host, port, None) | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         rt.block_on(work).unwrap_err(); |         rt.block_on(work).unwrap_err(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_tunnel_bad_response() { |     fn test_tunnel_non_http_response() { | ||||||
|         let addr = mock_tunnel!(b"foo bar baz hallo"); |         let addr = mock_tunnel!(b"foo bar baz hallo"); | ||||||
|  |  | ||||||
|         let mut rt = Runtime::new().unwrap(); |         let mut rt = Runtime::new().unwrap(); | ||||||
| @@ -327,9 +350,47 @@ mod tests { | |||||||
|         let host = addr.ip().to_string(); |         let host = addr.ip().to_string(); | ||||||
|         let port = addr.port(); |         let port = addr.port(); | ||||||
|         let work = work.and_then(|tcp| { |         let work = work.and_then(|tcp| { | ||||||
|             tunnel(tcp, host, port) |             tunnel(tcp, host, port, None) | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         rt.block_on(work).unwrap_err(); |         rt.block_on(work).unwrap_err(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_tunnel_proxy_unauthorized() { | ||||||
|  |         let addr = mock_tunnel!(b"\ | ||||||
|  |             HTTP/1.1 407 Proxy Authentication Required\r\n\ | ||||||
|  |             Proxy-Authenticate: Basic realm=\"nope\"\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |         "); | ||||||
|  |  | ||||||
|  |         let mut rt = Runtime::new().unwrap(); | ||||||
|  |         let work = TcpStream::connect(&addr); | ||||||
|  |         let host = addr.ip().to_string(); | ||||||
|  |         let port = addr.port(); | ||||||
|  |         let work = work.and_then(|tcp| { | ||||||
|  |             tunnel(tcp, host, port, None) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         let error = rt.block_on(work).unwrap_err(); | ||||||
|  |         assert_eq!(error.to_string(), "proxy authentication required"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_tunnel_basic_auth() { | ||||||
|  |         let addr = mock_tunnel!( | ||||||
|  |             TUNNEL_OK, | ||||||
|  |             "Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n" | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         let mut rt = Runtime::new().unwrap(); | ||||||
|  |         let work = TcpStream::connect(&addr); | ||||||
|  |         let host = addr.ip().to_string(); | ||||||
|  |         let port = addr.port(); | ||||||
|  |         let work = work.and_then(|tcp| { | ||||||
|  |             tunnel(tcp, host, port, Some(proxy::Auth::basic("Aladdin", "open sesame"))) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         rt.block_on(work).unwrap(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										99
									
								
								src/proxy.rs
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								src/proxy.rs
									
									
									
									
									
								
							| @@ -1,6 +1,7 @@ | |||||||
| use std::fmt; | use std::fmt; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  |  | ||||||
|  | use http::{header::HeaderValue, Uri}; | ||||||
| use hyper::client::connect::Destination; | use hyper::client::connect::Destination; | ||||||
| use {into_url, IntoUrl, Url}; | use {into_url, IntoUrl, Url}; | ||||||
|  |  | ||||||
| @@ -30,9 +31,15 @@ use {into_url, IntoUrl, Url}; | |||||||
| /// would prevent a `Proxy` later in the list from ever working, so take care. | /// would prevent a `Proxy` later in the list from ever working, so take care. | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| pub struct Proxy { | pub struct Proxy { | ||||||
|  |     auth: Option<Auth>, | ||||||
|     intercept: Intercept, |     intercept: Intercept, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Debug)] | ||||||
|  | pub(crate) enum Auth { | ||||||
|  |     Basic(HeaderValue), | ||||||
|  | } | ||||||
|  |  | ||||||
| impl Proxy { | impl Proxy { | ||||||
|     /// Proxy all HTTP traffic to the passed URL. |     /// Proxy all HTTP traffic to the passed URL. | ||||||
|     /// |     /// | ||||||
| @@ -124,7 +131,43 @@ impl Proxy { | |||||||
|  |  | ||||||
|     fn new(intercept: Intercept) -> Proxy { |     fn new(intercept: Intercept) -> Proxy { | ||||||
|         Proxy { |         Proxy { | ||||||
|             intercept: intercept, |             auth: None, | ||||||
|  |             intercept, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Set the `Proxy-Authorization` header using Basic auth. | ||||||
|  |     /// | ||||||
|  |     /// # Example | ||||||
|  |     /// | ||||||
|  |     /// ``` | ||||||
|  |     /// # extern crate reqwest; | ||||||
|  |     /// # fn run() -> Result<(), Box<::std::error::Error>> { | ||||||
|  |     /// let proxy = reqwest::Proxy::https("http://localhost:1234")? | ||||||
|  |     ///     .basic_auth("Aladdin", "open sesame"); | ||||||
|  |     /// # Ok(()) | ||||||
|  |     /// # } | ||||||
|  |     /// # fn main() {} | ||||||
|  |     /// ``` | ||||||
|  |     pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy { | ||||||
|  |         self.auth = Some(Auth::basic(username, password)); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn auth(&self) -> Option<&Auth> { | ||||||
|  |         self.auth.as_ref() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn maybe_has_http_auth(&self) -> bool { | ||||||
|  |         match self.auth { | ||||||
|  |             Some(Auth::Basic(_)) => match self.intercept { | ||||||
|  |                 Intercept::All(_) | | ||||||
|  |                 Intercept::Http(_) | | ||||||
|  |                 // Custom *may* match 'http', so assume so. | ||||||
|  |                 Intercept::Custom(_) => true, | ||||||
|  |                 Intercept::Https(_) => false, | ||||||
|  |             }, | ||||||
|  |             None => false, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -161,6 +204,31 @@ impl Proxy { | |||||||
|             }, |             }, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool { | ||||||
|  |         match self.intercept { | ||||||
|  |             Intercept::All(_) => true, | ||||||
|  |             Intercept::Http(_) => { | ||||||
|  |                 uri.scheme() == "http" | ||||||
|  |             }, | ||||||
|  |             Intercept::Https(_) => { | ||||||
|  |                 uri.scheme() == "https" | ||||||
|  |             }, | ||||||
|  |             Intercept::Custom(ref fun) => { | ||||||
|  |                 (fun.0)( | ||||||
|  |                     &format!( | ||||||
|  |                         "{}://{}{}{}", | ||||||
|  |                         uri.scheme(), | ||||||
|  |                         uri.host(), | ||||||
|  |                         uri.port().map(|_| ":").unwrap_or(""), | ||||||
|  |                         uri.port().map(|p| p.to_string()).unwrap_or(String::new()) | ||||||
|  |                     ) | ||||||
|  |                         .parse() | ||||||
|  |                         .expect("should be valid Url") | ||||||
|  |                 ).is_some() | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| @@ -203,6 +271,35 @@ impl Dst for Destination { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[doc(hidden)] | ||||||
|  | impl Dst for Uri { | ||||||
|  |     fn scheme(&self) -> &str { | ||||||
|  |         self.scheme_part() | ||||||
|  |             .expect("Uri should have a scheme") | ||||||
|  |             .as_str() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn host(&self) -> &str { | ||||||
|  |         Uri::host(self) | ||||||
|  |             .expect("<Uri as Dst>::host should have a str") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn port(&self) -> Option<u16> { | ||||||
|  |         self.port_part().map(|p| p.as_u16()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Auth { | ||||||
|  |     pub(crate) fn basic(username: &str, password: &str) -> Auth { | ||||||
|  |         let val = format!("{}:{}", username, password); | ||||||
|  |         let mut header = format!("Basic {}", base64::encode(&val)) | ||||||
|  |             .parse::<HeaderValue>() | ||||||
|  |             .expect("base64 is always valid HeaderValue"); | ||||||
|  |         header.set_sensitive(true); | ||||||
|  |         Auth::Basic(header) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use super::*; |     use super::*; | ||||||
|   | |||||||
| @@ -1,6 +1,13 @@ | |||||||
| use std::fmt; | use std::fmt; | ||||||
|  |  | ||||||
| use header::HeaderMap; | use header::{ | ||||||
|  |     HeaderMap, | ||||||
|  |     AUTHORIZATION, | ||||||
|  |     COOKIE, | ||||||
|  |     PROXY_AUTHORIZATION, | ||||||
|  |     WWW_AUTHENTICATE, | ||||||
|  |  | ||||||
|  | }; | ||||||
| use hyper::StatusCode; | use hyper::StatusCode; | ||||||
|  |  | ||||||
| use Url; | use Url; | ||||||
| @@ -233,10 +240,11 @@ pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, prev | |||||||
|         let cross_host = next.host_str() != previous.host_str() || |         let cross_host = next.host_str() != previous.host_str() || | ||||||
|                          next.port_or_known_default() != previous.port_or_known_default(); |                          next.port_or_known_default() != previous.port_or_known_default(); | ||||||
|         if cross_host { |         if cross_host { | ||||||
|             headers.remove("authorization"); |             headers.remove(AUTHORIZATION); | ||||||
|             headers.remove("cookie"); |             headers.remove(COOKIE); | ||||||
|             headers.remove("cookie2"); |             headers.remove("cookie2"); | ||||||
|             headers.remove("www-authenticate"); |             headers.remove(PROXY_AUTHORIZATION); | ||||||
|  |             headers.remove(WWW_AUTHENTICATE); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ extern crate reqwest; | |||||||
| mod support; | mod support; | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_http_proxy() { | fn http_proxy() { | ||||||
|     let server = server! { |     let server = server! { | ||||||
|         request: b"\ |         request: b"\ | ||||||
|             GET http://hyper.rs/prox HTTP/1.1\r\n\ |             GET http://hyper.rs/prox HTTP/1.1\r\n\ | ||||||
| @@ -37,3 +37,43 @@ fn test_http_proxy() { | |||||||
|     assert_eq!(res.status(), reqwest::StatusCode::OK); |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
|     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied"); |     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn http_proxy_basic_auth() { | ||||||
|  |     let server = server! { | ||||||
|  |         request: b"\ | ||||||
|  |             GET http://hyper.rs/prox HTTP/1.1\r\n\ | ||||||
|  |             user-agent: $USERAGENT\r\n\ | ||||||
|  |             accept: */*\r\n\ | ||||||
|  |             accept-encoding: gzip\r\n\ | ||||||
|  |             proxy-authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\ | ||||||
|  |             host: hyper.rs\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             ", | ||||||
|  |         response: b"\ | ||||||
|  |             HTTP/1.1 200 OK\r\n\ | ||||||
|  |             Server: proxied\r\n\ | ||||||
|  |             Content-Length: 0\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             " | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let proxy = format!("http://{}", server.addr()); | ||||||
|  |  | ||||||
|  |     let url = "http://hyper.rs/prox"; | ||||||
|  |     let res = reqwest::Client::builder() | ||||||
|  |         .proxy( | ||||||
|  |             reqwest::Proxy::http(&proxy) | ||||||
|  |             .unwrap() | ||||||
|  |             .basic_auth("Aladdin", "open sesame") | ||||||
|  |         ) | ||||||
|  |         .build() | ||||||
|  |         .unwrap() | ||||||
|  |         .get(url) | ||||||
|  |         .send() | ||||||
|  |         .unwrap(); | ||||||
|  |  | ||||||
|  |     assert_eq!(res.url().as_str(), url); | ||||||
|  |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
|  |     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied"); | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user