Add rustls support (#390)
This commit is contained in:
		| @@ -15,6 +15,14 @@ matrix: | |||||||
|         - rust: stable |         - rust: stable | ||||||
|           env: FEATURES="--no-default-features" |           env: FEATURES="--no-default-features" | ||||||
|  |  | ||||||
|  |         # rustls-tls | ||||||
|  |         - rust: stable | ||||||
|  |           env: FEATURES="--no-default-features --features rustls-tls" | ||||||
|  |  | ||||||
|  |         # default-tls and rustls-tls | ||||||
|  |         - rust: stable | ||||||
|  |           env: FEATURES="--features rustls-tls" | ||||||
|  |  | ||||||
|         - rust: stable |         - rust: stable | ||||||
|           env: FEATURES="--features hyper-011" |           env: FEATURES="--features hyper-011" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,6 +30,10 @@ tokio = "0.1.7" | |||||||
| tokio-io = "0.1" | tokio-io = "0.1" | ||||||
| url = "1.2" | url = "1.2" | ||||||
| uuid = { version = "0.7", features = ["v4"] } | uuid = { version = "0.7", features = ["v4"] } | ||||||
|  | hyper-rustls = { version = "0.15", optional = true } | ||||||
|  | tokio-rustls = { version = "0.8", optional = true } | ||||||
|  | webpki-roots = { version = "0.15", optional = true } | ||||||
|  | rustls = { version = "0.14", features = ["dangerous_configuration"], optional = true } | ||||||
|  |  | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| env_logger = "0.6" | env_logger = "0.6" | ||||||
| @@ -37,8 +41,10 @@ serde_derive = "1.0" | |||||||
|  |  | ||||||
| [features] | [features] | ||||||
| default = ["default-tls"] | default = ["default-tls"] | ||||||
|  | tls = [] | ||||||
| hyper-011 = ["hyper-old-types"] | hyper-011 = ["hyper-old-types"] | ||||||
| default-tls = ["hyper-tls", "native-tls"] | default-tls = ["hyper-tls", "native-tls", "tls"] | ||||||
|  | rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"] | ||||||
| native-tls-vendored = ["native-tls/vendored"] | native-tls-vendored = ["native-tls/vendored"] | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] | [package.metadata.docs.rs] | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ use header::{HeaderMap, HeaderValue, LOCATION, USER_AGENT, REFERER, ACCEPT, | |||||||
|              ACCEPT_ENCODING, RANGE, TRANSFER_ENCODING, CONTENT_TYPE, CONTENT_LENGTH, CONTENT_ENCODING}; |              ACCEPT_ENCODING, RANGE, TRANSFER_ENCODING, CONTENT_TYPE, CONTENT_LENGTH, CONTENT_ENCODING}; | ||||||
| use mime::{self}; | use mime::{self}; | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "default-tls")] | ||||||
| use native_tls::{TlsConnector, TlsConnectorBuilder}; | use native_tls::TlsConnector; | ||||||
|  |  | ||||||
|  |  | ||||||
| use super::request::{Request, RequestBuilder}; | use super::request::{Request, RequestBuilder}; | ||||||
| @@ -18,8 +18,10 @@ use connect::Connector; | |||||||
| use into_url::to_uri; | use into_url::to_uri; | ||||||
| use redirect::{self, RedirectPolicy, remove_sensitive_headers}; | use redirect::{self, RedirectPolicy, remove_sensitive_headers}; | ||||||
| use {IntoUrl, Method, Proxy, StatusCode, Url}; | use {IntoUrl, Method, Proxy, StatusCode, Url}; | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "tls")] | ||||||
| use {Certificate, Identity}; | use {Certificate, Identity}; | ||||||
|  | #[cfg(feature = "tls")] | ||||||
|  | use ::tls::{ TLSBackend, inner }; | ||||||
|  |  | ||||||
| static DEFAULT_USER_AGENT: &'static str = | static DEFAULT_USER_AGENT: &'static str = | ||||||
|     concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); |     concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); | ||||||
| @@ -46,14 +48,18 @@ struct Config { | |||||||
|     headers: HeaderMap, |     headers: HeaderMap, | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "default-tls")] | ||||||
|     hostname_verification: bool, |     hostname_verification: bool, | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "tls")] | ||||||
|     certs_verification: bool, |     certs_verification: bool, | ||||||
|     proxies: Vec<Proxy>, |     proxies: Vec<Proxy>, | ||||||
|     redirect_policy: RedirectPolicy, |     redirect_policy: RedirectPolicy, | ||||||
|     referer: bool, |     referer: bool, | ||||||
|     timeout: Option<Duration>, |     timeout: Option<Duration>, | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "tls")] | ||||||
|     tls: TlsConnectorBuilder, |     root_certs: Vec<Certificate>, | ||||||
|  |     #[cfg(feature = "tls")] | ||||||
|  |     identity: Option<Identity>, | ||||||
|  |     #[cfg(feature = "tls")] | ||||||
|  |     tls: TLSBackend, | ||||||
|     dns_threads: usize, |     dns_threads: usize, | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -70,14 +76,18 @@ impl ClientBuilder { | |||||||
|                 headers: headers, |                 headers: headers, | ||||||
|                 #[cfg(feature = "default-tls")] |                 #[cfg(feature = "default-tls")] | ||||||
|                 hostname_verification: true, |                 hostname_verification: true, | ||||||
|                 #[cfg(feature = "default-tls")] |                 #[cfg(feature = "tls")] | ||||||
|                 certs_verification: true, |                 certs_verification: true, | ||||||
|                 proxies: Vec::new(), |                 proxies: Vec::new(), | ||||||
|                 redirect_policy: RedirectPolicy::default(), |                 redirect_policy: RedirectPolicy::default(), | ||||||
|                 referer: true, |                 referer: true, | ||||||
|                 timeout: None, |                 timeout: None, | ||||||
|                 #[cfg(feature = "default-tls")] |                 #[cfg(feature = "tls")] | ||||||
|                 tls: TlsConnector::builder(), |                 root_certs: Vec::new(), | ||||||
|  |                 #[cfg(feature = "tls")] | ||||||
|  |                 identity: None, | ||||||
|  |                 #[cfg(feature = "tls")] | ||||||
|  |                 tls: TLSBackend::default(), | ||||||
|                 dns_threads: 4, |                 dns_threads: 4, | ||||||
|             }, |             }, | ||||||
|         } |         } | ||||||
| @@ -87,32 +97,103 @@ impl ClientBuilder { | |||||||
|     /// |     /// | ||||||
|     /// # Errors |     /// # Errors | ||||||
|     /// |     /// | ||||||
|     /// This method fails if native TLS backend cannot be initialized. |     /// This method fails if TLS backend cannot be initialized. | ||||||
|     pub fn build(self) -> ::Result<Client> { |     pub fn build(self) -> ::Result<Client> { | ||||||
|         let config = self.config; |         let config = self.config; | ||||||
|  |         let proxies = Arc::new(config.proxies); | ||||||
|  |  | ||||||
|         let connector = { |         let connector = { | ||||||
|             #[cfg(feature = "default-tls")] |             #[cfg(feature = "tls")] | ||||||
|             { |             match config.tls { | ||||||
|             let mut tls = config.tls; |                 #[cfg(feature = "default-tls")] | ||||||
|             tls.danger_accept_invalid_hostnames(!config.hostname_verification); |                 TLSBackend::Default => { | ||||||
|             tls.danger_accept_invalid_certs(!config.certs_verification); |                     let mut tls = TlsConnector::builder(); | ||||||
|  |                     tls.danger_accept_invalid_hostnames(!config.hostname_verification); | ||||||
|  |                     tls.danger_accept_invalid_certs(!config.certs_verification); | ||||||
|  |  | ||||||
|             let tls = try_!(tls.build()); |                     for cert in config.root_certs { | ||||||
|  |                         let cert = match cert.inner { | ||||||
|  |                             inner::Certificate::Der(buf) => | ||||||
|  |                                 try_!(::native_tls::Certificate::from_der(&buf)), | ||||||
|  |                             inner::Certificate::Pem(buf) => | ||||||
|  |                                 try_!(::native_tls::Certificate::from_pem(&buf)) | ||||||
|  |                         }; | ||||||
|  |                         tls.add_root_certificate(cert); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|             let proxies = Arc::new(config.proxies); |                     if let Some(id) = config.identity { | ||||||
|  |                         let id = match id.inner { | ||||||
|  |                             inner::Identity::Pkcs12(buf, passwd) => | ||||||
|  |                                 try_!(::native_tls::Identity::from_pkcs12(&buf, &passwd)), | ||||||
|  |                             #[cfg(feature = "rustls-tls")] | ||||||
|  |                             _ => return Err(::error::from(::error::Kind::Incompatible)) | ||||||
|  |                         }; | ||||||
|  |                         tls.identity(id); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|             Connector::new(config.dns_threads, tls, proxies.clone()) |                     Connector::new_default_tls(config.dns_threads, tls, proxies.clone())? | ||||||
|  |                 }, | ||||||
|  |                 #[cfg(feature = "rustls-tls")] | ||||||
|  |                 TLSBackend::Rustls => { | ||||||
|  |                     use std::io::Cursor; | ||||||
|  |                     use rustls::TLSError; | ||||||
|  |                     use rustls::internal::pemfile; | ||||||
|  |                     use ::tls::NoVerifier; | ||||||
|  |  | ||||||
|  |                     let mut tls = ::rustls::ClientConfig::new(); | ||||||
|  |                     tls.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); | ||||||
|  |  | ||||||
|  |                     if !config.certs_verification { | ||||||
|  |                         tls.dangerous().set_certificate_verifier(Arc::new(NoVerifier)); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     for cert in config.root_certs { | ||||||
|  |                         match cert.inner { | ||||||
|  |                             inner::Certificate::Der(buf) => try_!(tls.root_store.add(&::rustls::Certificate(buf)) | ||||||
|  |                                 .map_err(TLSError::WebPKIError)), | ||||||
|  |                             inner::Certificate::Pem(buf) => { | ||||||
|  |                                 let mut pem = Cursor::new(buf); | ||||||
|  |                                 let mut certs = try_!(pemfile::certs(&mut pem) | ||||||
|  |                                     .map_err(|_| TLSError::General(String::from("No valid certificate was found")))); | ||||||
|  |                                 for c in certs { | ||||||
|  |                                     try_!(tls.root_store.add(&c) | ||||||
|  |                                         .map_err(TLSError::WebPKIError)); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if let Some(id) = config.identity { | ||||||
|  |                         let (key, certs) = match id.inner { | ||||||
|  |                             inner::Identity::Pem(buf) => { | ||||||
|  |                                 let mut pem = Cursor::new(buf); | ||||||
|  |                                 let mut certs = try_!(pemfile::certs(&mut pem) | ||||||
|  |                                     .map_err(|_| TLSError::General(String::from("No valid certificate was found")))); | ||||||
|  |                                 pem.set_position(0); | ||||||
|  |                                 let mut sk = try_!(pemfile::pkcs8_private_keys(&mut pem) | ||||||
|  |                                     .or_else(|_| { | ||||||
|  |                                         pem.set_position(0); | ||||||
|  |                                         pemfile::rsa_private_keys(&mut pem) | ||||||
|  |                                     }) | ||||||
|  |                                     .map_err(|_| TLSError::General(String::from("No valid private key was found")))); | ||||||
|  |                                 if let (Some(sk), false) = (sk.pop(), certs.is_empty()) { | ||||||
|  |                                     (sk, certs) | ||||||
|  |                                 } else { | ||||||
|  |                                     return Err(::error::from(TLSError::General(String::from("private key or certificate not found")))); | ||||||
|  |                                 } | ||||||
|  |                             }, | ||||||
|  |                             #[cfg(feature = "default-tls")] | ||||||
|  |                             _ => return Err(::error::from(::error::Kind::Incompatible)) | ||||||
|  |                         }; | ||||||
|  |                         tls.set_single_client_cert(certs, key); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     Connector::new_rustls_tls(config.dns_threads, tls, proxies.clone())? | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             #[cfg(not(feature = "tls"))] | ||||||
|             #[cfg(not(feature = "default-tls"))] |  | ||||||
|             { |  | ||||||
|             let proxies = Arc::new(config.proxies); |  | ||||||
|  |  | ||||||
|             Connector::new(config.dns_threads, proxies.clone()) |             Connector::new(config.dns_threads, proxies.clone()) | ||||||
|             } |  | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let hyper_client = ::hyper::Client::builder() |         let hyper_client = ::hyper::Client::builder() | ||||||
| @@ -129,20 +210,34 @@ impl ClientBuilder { | |||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Use native TLS backend. | ||||||
|  |     #[cfg(feature = "default-tls")] | ||||||
|  |     pub fn use_default_tls(mut self) -> ClientBuilder { | ||||||
|  |         self.config.tls = TLSBackend::Default; | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Use rustls TLS backend. | ||||||
|  |     #[cfg(feature = "rustls-tls")] | ||||||
|  |     pub fn use_rustls_tls(mut self) -> ClientBuilder { | ||||||
|  |         self.config.tls = TLSBackend::Rustls; | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Add a custom root certificate. |     /// Add a custom root certificate. | ||||||
|     /// |     /// | ||||||
|     /// This can be used to connect to a server that has a self-signed |     /// This can be used to connect to a server that has a self-signed | ||||||
|     /// certificate for example. |     /// certificate for example. | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "tls")] | ||||||
|     pub fn add_root_certificate(mut self, cert: Certificate) -> ClientBuilder { |     pub fn add_root_certificate(mut self, cert: Certificate) -> ClientBuilder { | ||||||
|         self.config.tls.add_root_certificate(cert.cert()); |         self.config.root_certs.push(cert); | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Sets the identity to be used for client certificate authentication. |     /// Sets the identity to be used for client certificate authentication. | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "tls")] | ||||||
|     pub fn identity(mut self, identity: Identity) -> ClientBuilder { |     pub fn identity(mut self, identity: Identity) -> ClientBuilder { | ||||||
|         self.config.tls.identity(identity.pkcs12()); |         self.config.identity = Some(identity); | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -162,7 +257,6 @@ impl ClientBuilder { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// Controls the use of certificate validation. |     /// Controls the use of certificate validation. | ||||||
|     /// |     /// | ||||||
|     /// Defaults to `false`. |     /// Defaults to `false`. | ||||||
| @@ -174,7 +268,7 @@ impl ClientBuilder { | |||||||
|     /// will be trusted for use. This includes expired certificates. This |     /// will be trusted for use. This includes expired certificates. This | ||||||
|     /// introduces significant vulnerabilities, and should only be used |     /// introduces significant vulnerabilities, and should only be used | ||||||
|     /// as a last resort. |     /// as a last resort. | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "tls")] | ||||||
|     pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder { |     pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder { | ||||||
|         self.config.certs_verification = !accept_invalid_certs; |         self.config.certs_verification = !accept_invalid_certs; | ||||||
|         self |         self | ||||||
| @@ -196,9 +290,9 @@ impl ClientBuilder { | |||||||
|     ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. |     ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. | ||||||
|     ///   The body is **not** automatically inflated. |     ///   The body is **not** automatically inflated. | ||||||
|     /// - When receiving a response, if it's headers contain a `Content-Encoding` value that |     /// - When receiving a response, if it's headers contain a `Content-Encoding` value that | ||||||
|     ///   equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the  |     ///   equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the | ||||||
|     ///   headers' set. The body is automatically deinflated. |     ///   headers' set. The body is automatically deinflated. | ||||||
|     ///  |     /// | ||||||
|     /// Default is enabled. |     /// Default is enabled. | ||||||
|     pub fn gzip(mut self, enable: bool) -> ClientBuilder { |     pub fn gzip(mut self, enable: bool) -> ClientBuilder { | ||||||
|         self.config.gzip = enable; |         self.config.gzip = enable; | ||||||
| @@ -247,7 +341,7 @@ impl Client { | |||||||
|     /// |     /// | ||||||
|     /// # Panics |     /// # Panics | ||||||
|     /// |     /// | ||||||
|     /// This method panics if native TLS backend cannot be created or |     /// This method panics if TLS backend cannot be created or | ||||||
|     /// initialized. Use `Client::builder()` if you wish to handle the failure |     /// initialized. Use `Client::builder()` if you wish to handle the failure | ||||||
|     /// as an `Error` instead of panicking. |     /// as an `Error` instead of panicking. | ||||||
|     pub fn new() -> Client { |     pub fn new() -> Client { | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ use futures::sync::{mpsc, oneshot}; | |||||||
| use request::{Request, RequestBuilder}; | use request::{Request, RequestBuilder}; | ||||||
| use response::Response; | use response::Response; | ||||||
| use {async_impl, header, Method, IntoUrl, Proxy, RedirectPolicy, wait}; | use {async_impl, header, Method, IntoUrl, Proxy, RedirectPolicy, wait}; | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "tls")] | ||||||
| use {Certificate, Identity}; | use {Certificate, Identity}; | ||||||
|  |  | ||||||
| /// A `Client` to make Requests with. | /// A `Client` to make Requests with. | ||||||
| @@ -79,6 +79,18 @@ impl ClientBuilder { | |||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Use native TLS backend. | ||||||
|  |     #[cfg(feature = "default-tls")] | ||||||
|  |     pub fn use_default_tls(self) -> ClientBuilder { | ||||||
|  |         self.with_inner(move |inner| inner.use_default_tls()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Use rustls TLS backend. | ||||||
|  |     #[cfg(feature = "rustls-tls")] | ||||||
|  |     pub fn use_rustls_tls(self) -> ClientBuilder { | ||||||
|  |         self.with_inner(move |inner| inner.use_rustls_tls()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Add a custom root certificate. |     /// Add a custom root certificate. | ||||||
|     /// |     /// | ||||||
|     /// This can be used to connect to a server that has a self-signed |     /// This can be used to connect to a server that has a self-signed | ||||||
| @@ -108,7 +120,7 @@ impl ClientBuilder { | |||||||
|     /// # Errors |     /// # Errors | ||||||
|     /// |     /// | ||||||
|     /// This method fails if adding root certificate was unsuccessful. |     /// This method fails if adding root certificate was unsuccessful. | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "tls")] | ||||||
|     pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder { |     pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder { | ||||||
|         self.with_inner(move |inner| inner.add_root_certificate(cert)) |         self.with_inner(move |inner| inner.add_root_certificate(cert)) | ||||||
|     } |     } | ||||||
| @@ -123,10 +135,18 @@ impl ClientBuilder { | |||||||
|     /// # fn build_client() -> Result<(), Box<std::error::Error>> { |     /// # fn build_client() -> Result<(), Box<std::error::Error>> { | ||||||
|     /// // read a local PKCS12 bundle |     /// // read a local PKCS12 bundle | ||||||
|     /// let mut buf = Vec::new(); |     /// let mut buf = Vec::new(); | ||||||
|     /// File::open("my-ident.pfx")?.read_to_end(&mut buf)?; |  | ||||||
|     /// |     /// | ||||||
|  |     /// #[cfg(feature = "default-tls")] | ||||||
|  |     /// File::open("my-ident.pfx")?.read_to_end(&mut buf)?; | ||||||
|  |     /// #[cfg(feature = "rustls-tls")] | ||||||
|  |     /// File::open("my-ident.pem")?.read_to_end(&mut buf)?; | ||||||
|  |     /// | ||||||
|  |     /// #[cfg(feature = "default-tls")] | ||||||
|     /// // create an Identity from the PKCS#12 archive |     /// // create an Identity from the PKCS#12 archive | ||||||
|     /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?; |     /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?; | ||||||
|  |     /// #[cfg(feature = "rustls-tls")] | ||||||
|  |     /// // create an Identity from the PEM file | ||||||
|  |     /// let pkcs12 = reqwest::Identity::from_pem(&buf)?; | ||||||
|     /// |     /// | ||||||
|     /// // get a client builder |     /// // get a client builder | ||||||
|     /// let client = reqwest::Client::builder() |     /// let client = reqwest::Client::builder() | ||||||
| @@ -136,7 +156,7 @@ impl ClientBuilder { | |||||||
|     /// # Ok(()) |     /// # Ok(()) | ||||||
|     /// # } |     /// # } | ||||||
|     /// ``` |     /// ``` | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "tls")] | ||||||
|     pub fn identity(self, identity: Identity) -> ClientBuilder { |     pub fn identity(self, identity: Identity) -> ClientBuilder { | ||||||
|         self.with_inner(move |inner| inner.identity(identity)) |         self.with_inner(move |inner| inner.identity(identity)) | ||||||
|     } |     } | ||||||
| @@ -157,7 +177,6 @@ impl ClientBuilder { | |||||||
|         self.with_inner(|inner| inner.danger_accept_invalid_hostnames(accept_invalid_hostname)) |         self.with_inner(|inner| inner.danger_accept_invalid_hostnames(accept_invalid_hostname)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// Controls the use of certificate validation. |     /// Controls the use of certificate validation. | ||||||
|     /// |     /// | ||||||
|     /// Defaults to `false`. |     /// Defaults to `false`. | ||||||
| @@ -169,7 +188,7 @@ impl ClientBuilder { | |||||||
|     /// will be trusted for use. This includes expired certificates. This |     /// will be trusted for use. This includes expired certificates. This | ||||||
|     /// introduces significant vulnerabilities, and should only be used |     /// introduces significant vulnerabilities, and should only be used | ||||||
|     /// as a last resort. |     /// as a last resort. | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "tls")] | ||||||
|     pub fn danger_accept_invalid_certs(self, accept_invalid_certs: bool) -> ClientBuilder { |     pub fn danger_accept_invalid_certs(self, accept_invalid_certs: bool) -> ClientBuilder { | ||||||
|         self.with_inner(|inner| inner.danger_accept_invalid_certs(accept_invalid_certs)) |         self.with_inner(|inner| inner.danger_accept_invalid_certs(accept_invalid_certs)) | ||||||
|     } |     } | ||||||
| @@ -223,9 +242,9 @@ impl ClientBuilder { | |||||||
|     ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. |     ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. | ||||||
|     ///   The body is **not** automatically inflated. |     ///   The body is **not** automatically inflated. | ||||||
|     /// - When receiving a response, if it's headers contain a `Content-Encoding` value that |     /// - When receiving a response, if it's headers contain a `Content-Encoding` value that | ||||||
|     ///   equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the  |     ///   equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the | ||||||
|     ///   headers' set. The body is automatically deinflated. |     ///   headers' set. The body is automatically deinflated. | ||||||
|     ///  |     /// | ||||||
|     /// Default is enabled. |     /// Default is enabled. | ||||||
|     pub fn gzip(self, enable: bool) -> ClientBuilder { |     pub fn gzip(self, enable: bool) -> ClientBuilder { | ||||||
|         self.with_inner(|inner| inner.gzip(enable)) |         self.with_inner(|inner| inner.gzip(enable)) | ||||||
|   | |||||||
							
								
								
									
										244
									
								
								src/connect.rs
									
									
									
									
									
								
							
							
						
						
									
										244
									
								
								src/connect.rs
									
									
									
									
									
								
							| @@ -1,51 +1,70 @@ | |||||||
| use bytes::{Buf, BufMut}; | use futures::Future; | ||||||
| use futures::{Future, Poll}; |  | ||||||
| use http::uri::Scheme; | use http::uri::Scheme; | ||||||
| use hyper::client::{HttpConnector}; | use hyper::client::{HttpConnector}; | ||||||
| use hyper::client::connect::{Connect, Connected, Destination}; | use hyper::client::connect::{Connect, Connected, Destination}; | ||||||
| #[cfg(feature = "default-tls")] |  | ||||||
| use hyper_tls::{HttpsConnector, MaybeHttpsStream}; |  | ||||||
| #[cfg(feature = "default-tls")] |  | ||||||
| use native_tls::TlsConnector; |  | ||||||
| use tokio_io::{AsyncRead, AsyncWrite}; | use tokio_io::{AsyncRead, AsyncWrite}; | ||||||
| #[cfg(feature = "default-tls")] |  | ||||||
| use connect_async::{TlsConnectorExt, TlsStream}; |  | ||||||
|  |  | ||||||
| use std::io::{self, Read, Write}; | #[cfg(feature = "default-tls")] | ||||||
|  | use native_tls::{TlsConnector, TlsConnectorBuilder}; | ||||||
|  | #[cfg(feature = "tls")] | ||||||
|  | use futures::Poll; | ||||||
|  | #[cfg(feature = "tls")] | ||||||
|  | use bytes::BufMut; | ||||||
|  |  | ||||||
|  | use std::io; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  |  | ||||||
| use Proxy; | use Proxy; | ||||||
|  |  | ||||||
|  |  | ||||||
| pub(crate) struct Connector { | pub(crate) struct Connector { | ||||||
|     #[cfg(feature = "default-tls")] |  | ||||||
|     http: HttpsConnector<HttpConnector>, |  | ||||||
|     #[cfg(not(feature = "default-tls"))] |  | ||||||
|     http: HttpConnector, |  | ||||||
|     proxies: Arc<Vec<Proxy>>, |     proxies: Arc<Vec<Proxy>>, | ||||||
|  |     inner: Inner | ||||||
|  | } | ||||||
|  |  | ||||||
|  | enum Inner { | ||||||
|  |     #[cfg(not(feature = "tls"))] | ||||||
|  |     Http(HttpConnector), | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "default-tls")] | ||||||
|     tls: TlsConnector, |     DefaultTls(::hyper_tls::HttpsConnector<HttpConnector>, TlsConnector), | ||||||
|  |     #[cfg(feature = "rustls-tls")] | ||||||
|  |     RustlsTls(::hyper_rustls::HttpsConnector<HttpConnector>, Arc<rustls::ClientConfig>) | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Connector { | impl Connector { | ||||||
|     #[cfg(not(feature = "default-tls"))] |     #[cfg(not(feature = "tls"))] | ||||||
|     pub(crate) fn new(threads: usize, proxies: Arc<Vec<Proxy>>) -> Connector { |     pub(crate) fn new(threads: usize, proxies: Arc<Vec<Proxy>>) -> Connector { | ||||||
|         let http = HttpConnector::new(threads); |         let http = HttpConnector::new(threads); | ||||||
|         Connector { |         Connector { | ||||||
|             http, |  | ||||||
|             proxies, |             proxies, | ||||||
|  |             inner: Inner::Http(http) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "default-tls")] | ||||||
|     pub(crate) fn new(threads: usize, tls: TlsConnector, proxies: Arc<Vec<Proxy>>) -> Connector { |     pub(crate) fn new_default_tls(threads: usize, tls: TlsConnectorBuilder, proxies: Arc<Vec<Proxy>>) -> ::Result<Connector> { | ||||||
|  |         let tls = try_!(tls.build()); | ||||||
|  |  | ||||||
|         let mut http = HttpConnector::new(threads); |         let mut http = HttpConnector::new(threads); | ||||||
|         http.enforce_http(false); |         http.enforce_http(false); | ||||||
|         let http = HttpsConnector::from((http, tls.clone())); |         let http = ::hyper_tls::HttpsConnector::from((http, tls.clone())); | ||||||
|  |  | ||||||
|         Connector { |         Ok(Connector { | ||||||
|             http, |  | ||||||
|             proxies, |             proxies, | ||||||
|             tls, |             inner: Inner::DefaultTls(http, tls) | ||||||
|         } |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[cfg(feature = "rustls-tls")] | ||||||
|  |     pub(crate) fn new_rustls_tls(threads: usize, tls: rustls::ClientConfig, proxies: Arc<Vec<Proxy>>) -> ::Result<Connector> { | ||||||
|  |         let mut http = HttpConnector::new(threads); | ||||||
|  |         http.enforce_http(false); | ||||||
|  |         let http = ::hyper_rustls::HttpsConnector::from((http, tls.clone())); | ||||||
|  |  | ||||||
|  |         Ok(Connector { | ||||||
|  |             proxies, | ||||||
|  |             inner: Inner::RustlsTls(http, Arc::new(tls)) | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -55,6 +74,23 @@ impl Connect for Connector { | |||||||
|     type Future = Connecting; |     type Future = Connecting; | ||||||
|  |  | ||||||
|     fn connect(&self, dst: Destination) -> Self::Future { |     fn connect(&self, dst: Destination) -> Self::Future { | ||||||
|  |         macro_rules! connect { | ||||||
|  |             ( $http:expr, $dst:expr, $proxy:expr ) => { | ||||||
|  |                 Box::new($http.connect($dst) | ||||||
|  |                     .map(|(io, connected)| (Box::new(io) as Conn, connected.proxy($proxy)))) | ||||||
|  |             }; | ||||||
|  |             ( $dst:expr, $proxy:expr ) => { | ||||||
|  |                 match &self.inner { | ||||||
|  |                     #[cfg(not(feature = "tls"))] | ||||||
|  |                     Inner::Http(http) => connect!(http, $dst, $proxy), | ||||||
|  |                     #[cfg(feature = "default-tls")] | ||||||
|  |                     Inner::DefaultTls(http, _) => connect!(http, $dst, $proxy), | ||||||
|  |                     #[cfg(feature = "rustls-tls")] | ||||||
|  |                     Inner::RustlsTls(http, _) => connect!(http, $dst, $proxy) | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         for prox in self.proxies.iter() { |         for prox in self.proxies.iter() { | ||||||
|             if let Some(puri) = prox.intercept(&dst) { |             if let Some(puri) = prox.intercept(&dst) { | ||||||
|                 trace!("proxy({:?}) intercepts {:?}", puri, dst); |                 trace!("proxy({:?}) intercepts {:?}", puri, dst); | ||||||
| @@ -69,116 +105,70 @@ impl Connect for Connector { | |||||||
|                 ndst.set_host(puri.host().expect("proxy target should have host")) |                 ndst.set_host(puri.host().expect("proxy target should have host")) | ||||||
|                     .expect("proxy target host should be valid"); |                     .expect("proxy target host should be valid"); | ||||||
|  |  | ||||||
|                 ndst.set_port(puri.port_part().map(|p| p.as_u16())); |                 ndst.set_port(puri.port_part().map(|port| port.as_u16())); | ||||||
|  |  | ||||||
|                 #[cfg(feature = "default-tls")] |                 match &self.inner { | ||||||
|                 { |                     #[cfg(feature = "default-tls")] | ||||||
|                 if dst.scheme() == "https" { |                     Inner::DefaultTls(http, tls) => if dst.scheme() == "https" { | ||||||
|                     let host = dst.host().to_owned(); |                         #[cfg(feature = "default-tls")] | ||||||
|                     let port = dst.port().unwrap_or(443); |                         use connect_async::TlsConnectorExt; | ||||||
|                     let tls = self.tls.clone(); |  | ||||||
|                     return Box::new(self.http.connect(ndst).and_then(move |(conn, connected)| { |                         let host = dst.host().to_owned(); | ||||||
|                         trace!("tunneling HTTPS over proxy"); |                         let port = dst.port().unwrap_or(443); | ||||||
|                         tunnel(conn, host.clone(), port) |                         let tls = tls.clone(); | ||||||
|                             .and_then(move |tunneled| { |                         return Box::new(http.connect(ndst).and_then(move |(conn, connected)| { | ||||||
|                                 tls.connect_async(&host, tunneled) |                             trace!("tunneling HTTPS over proxy"); | ||||||
|                                     .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) |                             tunnel(conn, host.clone(), port) | ||||||
|                             }) |                                 .and_then(move |tunneled| { | ||||||
|                             .map(|io| (Conn::Proxied(io), connected.proxy(true))) |                                     tls.connect_async(&host, tunneled) | ||||||
|                     })); |                                         .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) | ||||||
|  |                                 }) | ||||||
|  |                                 .map(|io| (Box::new(io) as Conn, connected.proxy(true))) | ||||||
|  |                         })); | ||||||
|  |                     }, | ||||||
|  |                     #[cfg(feature = "rustls-tls")] | ||||||
|  |                     Inner::RustlsTls(http, tls) => if dst.scheme() == "https" { | ||||||
|  |                         #[cfg(feature = "rustls-tls")] | ||||||
|  |                         use tokio_rustls::TlsConnector as RustlsConnector; | ||||||
|  |                         #[cfg(feature = "rustls-tls")] | ||||||
|  |                         use tokio_rustls::webpki::DNSNameRef; | ||||||
|  |  | ||||||
|  |                         let host = dst.host().to_owned(); | ||||||
|  |                         let port = dst.port().unwrap_or(443); | ||||||
|  |                         let tls = tls.clone(); | ||||||
|  |                         return Box::new(http.connect(ndst).and_then(move |(conn, connected)| { | ||||||
|  |                             trace!("tunneling HTTPS over proxy"); | ||||||
|  |                             let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host) | ||||||
|  |                                 .map(|dnsname| dnsname.to_owned()) | ||||||
|  |                                 .map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name")); | ||||||
|  |                             tunnel(conn, host, port) | ||||||
|  |                                 .and_then(move |tunneled| Ok((maybe_dnsname?, tunneled))) | ||||||
|  |                                 .and_then(move |(dnsname, tunneled)| { | ||||||
|  |                                     RustlsConnector::from(tls).connect(dnsname.as_ref(), tunneled) | ||||||
|  |                                         .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) | ||||||
|  |                                 }) | ||||||
|  |                                 .map(|io| (Box::new(io) as Conn, connected.proxy(true))) | ||||||
|  |                         })); | ||||||
|  |                     }, | ||||||
|  |                     #[cfg(not(feature = "tls"))] | ||||||
|  |                     Inner::Http(_) => () | ||||||
|                 } |                 } | ||||||
|                 } |  | ||||||
|                 return Box::new(self.http.connect(ndst).map(|(io, connected)| (Conn::Normal(io), connected.proxy(true)))); |                 return connect!(ndst, true); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         Box::new(self.http.connect(dst).map(|(io, connected)| (Conn::Normal(io), connected))) |  | ||||||
|  |         connect!(dst, false) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| type HttpStream = <HttpConnector as Connect>::Transport; | pub(crate) trait AsyncConn: AsyncRead + AsyncWrite {} | ||||||
| #[cfg(feature = "default-tls")] | impl<T: AsyncRead + AsyncWrite> AsyncConn for T {} | ||||||
| type HttpsStream = MaybeHttpsStream<HttpStream>; | 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>; | ||||||
|  |  | ||||||
| pub(crate) enum Conn { | #[cfg(feature = "tls")] | ||||||
|     #[cfg(feature = "default-tls")] |  | ||||||
|     Normal(HttpsStream), |  | ||||||
|     #[cfg(not(feature = "default-tls"))] |  | ||||||
|     Normal(HttpStream), |  | ||||||
|     #[cfg(feature = "default-tls")] |  | ||||||
|     Proxied(TlsStream<MaybeHttpsStream<HttpStream>>), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Read for Conn { |  | ||||||
|     #[inline] |  | ||||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |  | ||||||
|         match *self { |  | ||||||
|             Conn::Normal(ref mut s) => s.read(buf), |  | ||||||
|             #[cfg(feature = "default-tls")] |  | ||||||
|             Conn::Proxied(ref mut s) => s.read(buf), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Write for Conn { |  | ||||||
|     #[inline] |  | ||||||
|     fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |  | ||||||
|         match *self { |  | ||||||
|             Conn::Normal(ref mut s) => s.write(buf), |  | ||||||
|             #[cfg(feature = "default-tls")] |  | ||||||
|             Conn::Proxied(ref mut s) => s.write(buf), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[inline] |  | ||||||
|     fn flush(&mut self) -> io::Result<()> { |  | ||||||
|         match *self { |  | ||||||
|             Conn::Normal(ref mut s) => s.flush(), |  | ||||||
|             #[cfg(feature = "default-tls")] |  | ||||||
|             Conn::Proxied(ref mut s) => s.flush(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl AsyncRead for Conn { |  | ||||||
|     unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { |  | ||||||
|         match *self { |  | ||||||
|             Conn::Normal(ref s) => s.prepare_uninitialized_buffer(buf), |  | ||||||
|             #[cfg(feature = "default-tls")] |  | ||||||
|             Conn::Proxied(ref s) => s.prepare_uninitialized_buffer(buf), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> { |  | ||||||
|         match *self { |  | ||||||
|             Conn::Normal(ref mut s) => s.read_buf(buf), |  | ||||||
|             #[cfg(feature = "default-tls")] |  | ||||||
|             Conn::Proxied(ref mut s) => s.read_buf(buf), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl AsyncWrite for Conn { |  | ||||||
|     fn shutdown(&mut self) -> Poll<(), io::Error> { |  | ||||||
|         match *self { |  | ||||||
|             Conn::Normal(ref mut s) => s.shutdown(), |  | ||||||
|             #[cfg(feature = "default-tls")] |  | ||||||
|             Conn::Proxied(ref mut s) => s.shutdown(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> { |  | ||||||
|         match *self { |  | ||||||
|             Conn::Normal(ref mut s) => s.write_buf(buf), |  | ||||||
|             #[cfg(feature = "default-tls")] |  | ||||||
|             Conn::Proxied(ref mut s) => s.write_buf(buf), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(feature = "default-tls")] |  | ||||||
| fn tunnel<T>(conn: T, host: String, port: u16) -> Tunnel<T> { | fn tunnel<T>(conn: T, host: String, port: u16) -> Tunnel<T> { | ||||||
|      let buf = format!("\ |      let buf = format!("\ | ||||||
|         CONNECT {0}:{1} HTTP/1.1\r\n\ |         CONNECT {0}:{1} HTTP/1.1\r\n\ | ||||||
| @@ -193,20 +183,20 @@ fn tunnel<T>(conn: T, host: String, port: u16) -> Tunnel<T> { | |||||||
|      } |      } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "tls")] | ||||||
| struct Tunnel<T> { | struct Tunnel<T> { | ||||||
|     buf: io::Cursor<Vec<u8>>, |     buf: io::Cursor<Vec<u8>>, | ||||||
|     conn: Option<T>, |     conn: Option<T>, | ||||||
|     state: TunnelState, |     state: TunnelState, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "tls")] | ||||||
| enum TunnelState { | enum TunnelState { | ||||||
|     Writing, |     Writing, | ||||||
|     Reading |     Reading | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "tls")] | ||||||
| impl<T> Future for Tunnel<T> | impl<T> Future for Tunnel<T> | ||||||
| where T: AsyncRead + AsyncWrite { | where T: AsyncRead + AsyncWrite { | ||||||
|     type Item = T; |     type Item = T; | ||||||
| @@ -242,7 +232,7 @@ where T: AsyncRead + AsyncWrite { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "tls")] | ||||||
| #[inline] | #[inline] | ||||||
| fn tunnel_eof() -> io::Error { | fn tunnel_eof() -> io::Error { | ||||||
|     io::Error::new( |     io::Error::new( | ||||||
| @@ -251,7 +241,7 @@ fn tunnel_eof() -> io::Error { | |||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "tls")] | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use std::io::{Read, Write}; |     use std::io::{Read, Write}; | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								src/error.rs
									
									
									
									
									
								
							| @@ -135,8 +135,12 @@ impl Error { | |||||||
|             Kind::Hyper(ref e) => Some(e), |             Kind::Hyper(ref e) => Some(e), | ||||||
|             Kind::Mime(ref e) => Some(e), |             Kind::Mime(ref e) => Some(e), | ||||||
|             Kind::Url(ref e) => Some(e), |             Kind::Url(ref e) => Some(e), | ||||||
|  |             #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] | ||||||
|  |             Kind::Incompatible => None, | ||||||
|             #[cfg(feature = "default-tls")] |             #[cfg(feature = "default-tls")] | ||||||
|             Kind::Tls(ref e) => Some(e), |             Kind::NativeTls(ref e) => Some(e), | ||||||
|  |             #[cfg(feature = "rustls-tls")] | ||||||
|  |             Kind::Rustls(ref e) => Some(e), | ||||||
|             Kind::Io(ref e) => Some(e), |             Kind::Io(ref e) => Some(e), | ||||||
|             Kind::UrlEncoded(ref e) => Some(e), |             Kind::UrlEncoded(ref e) => Some(e), | ||||||
|             Kind::Json(ref e) => Some(e), |             Kind::Json(ref e) => Some(e), | ||||||
| @@ -225,8 +229,12 @@ impl fmt::Display for Error { | |||||||
|             Kind::Mime(ref e) => fmt::Display::fmt(e, f), |             Kind::Mime(ref e) => fmt::Display::fmt(e, f), | ||||||
|             Kind::Url(ref e) => fmt::Display::fmt(e, f), |             Kind::Url(ref e) => fmt::Display::fmt(e, f), | ||||||
|             Kind::UrlBadScheme => f.write_str("URL scheme is not allowed"), |             Kind::UrlBadScheme => f.write_str("URL scheme is not allowed"), | ||||||
|  |             #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] | ||||||
|  |             Kind::Incompatible => f.write_str("Incompatible identity type"), | ||||||
|             #[cfg(feature = "default-tls")] |             #[cfg(feature = "default-tls")] | ||||||
|             Kind::Tls(ref e) => fmt::Display::fmt(e, f), |             Kind::NativeTls(ref e) => fmt::Display::fmt(e, f), | ||||||
|  |             #[cfg(feature = "rustls-tls")] | ||||||
|  |             Kind::Rustls(ref e) => fmt::Display::fmt(e, f), | ||||||
|             Kind::Io(ref e) => fmt::Display::fmt(e, f), |             Kind::Io(ref e) => fmt::Display::fmt(e, f), | ||||||
|             Kind::UrlEncoded(ref e) => fmt::Display::fmt(e, f), |             Kind::UrlEncoded(ref e) => fmt::Display::fmt(e, f), | ||||||
|             Kind::Json(ref e) => fmt::Display::fmt(e, f), |             Kind::Json(ref e) => fmt::Display::fmt(e, f), | ||||||
| @@ -252,8 +260,12 @@ impl StdError for Error { | |||||||
|             Kind::Mime(ref e) => e.description(), |             Kind::Mime(ref e) => e.description(), | ||||||
|             Kind::Url(ref e) => e.description(), |             Kind::Url(ref e) => e.description(), | ||||||
|             Kind::UrlBadScheme => "URL scheme is not allowed", |             Kind::UrlBadScheme => "URL scheme is not allowed", | ||||||
|  |             #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] | ||||||
|  |             Kind::Incompatible => "Incompatible identity type", | ||||||
|             #[cfg(feature = "default-tls")] |             #[cfg(feature = "default-tls")] | ||||||
|             Kind::Tls(ref e) => e.description(), |             Kind::NativeTls(ref e) => e.description(), | ||||||
|  |             #[cfg(feature = "rustls-tls")] | ||||||
|  |             Kind::Rustls(ref e) => e.description(), | ||||||
|             Kind::Io(ref e) => e.description(), |             Kind::Io(ref e) => e.description(), | ||||||
|             Kind::UrlEncoded(ref e) => e.description(), |             Kind::UrlEncoded(ref e) => e.description(), | ||||||
|             Kind::Json(ref e) => e.description(), |             Kind::Json(ref e) => e.description(), | ||||||
| @@ -270,8 +282,12 @@ impl StdError for Error { | |||||||
|             Kind::Hyper(ref e) => e.cause(), |             Kind::Hyper(ref e) => e.cause(), | ||||||
|             Kind::Mime(ref e) => e.cause(), |             Kind::Mime(ref e) => e.cause(), | ||||||
|             Kind::Url(ref e) => e.cause(), |             Kind::Url(ref e) => e.cause(), | ||||||
|  |             #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] | ||||||
|  |             Kind::Incompatible => None, | ||||||
|             #[cfg(feature = "default-tls")] |             #[cfg(feature = "default-tls")] | ||||||
|             Kind::Tls(ref e) => e.cause(), |             Kind::NativeTls(ref e) => e.cause(), | ||||||
|  |             #[cfg(feature = "rustls-tls")] | ||||||
|  |             Kind::Rustls(ref e) => e.cause(), | ||||||
|             Kind::Io(ref e) => e.cause(), |             Kind::Io(ref e) => e.cause(), | ||||||
|             Kind::UrlEncoded(ref e) => e.cause(), |             Kind::UrlEncoded(ref e) => e.cause(), | ||||||
|             Kind::Json(ref e) => e.cause(), |             Kind::Json(ref e) => e.cause(), | ||||||
| @@ -291,8 +307,12 @@ pub(crate) enum Kind { | |||||||
|     Mime(::mime::FromStrError), |     Mime(::mime::FromStrError), | ||||||
|     Url(::url::ParseError), |     Url(::url::ParseError), | ||||||
|     UrlBadScheme, |     UrlBadScheme, | ||||||
|  |     #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] | ||||||
|  |     Incompatible, | ||||||
|     #[cfg(feature = "default-tls")] |     #[cfg(feature = "default-tls")] | ||||||
|     Tls(::native_tls::Error), |     NativeTls(::native_tls::Error), | ||||||
|  |     #[cfg(feature = "rustls-tls")] | ||||||
|  |     Rustls(::rustls::TLSError), | ||||||
|     Io(io::Error), |     Io(io::Error), | ||||||
|     UrlEncoded(::serde_urlencoded::ser::Error), |     UrlEncoded(::serde_urlencoded::ser::Error), | ||||||
|     Json(::serde_json::Error), |     Json(::serde_json::Error), | ||||||
| @@ -355,7 +375,14 @@ impl From<::serde_json::Error> for Kind { | |||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "default-tls")] | ||||||
| impl From<::native_tls::Error> for Kind { | impl From<::native_tls::Error> for Kind { | ||||||
|     fn from(err: ::native_tls::Error) -> Kind { |     fn from(err: ::native_tls::Error) -> Kind { | ||||||
|         Kind::Tls(err) |         Kind::NativeTls(err) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "rustls-tls")] | ||||||
|  | impl From<::rustls::TLSError> for Kind { | ||||||
|  |     fn from(err: ::rustls::TLSError) -> Kind { | ||||||
|  |         Kind::Rustls(err) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/lib.rs
									
									
									
									
									
								
							| @@ -166,6 +166,15 @@ extern crate tokio_io; | |||||||
| extern crate url; | extern crate url; | ||||||
| extern crate uuid; | extern crate uuid; | ||||||
|  |  | ||||||
|  | #[cfg(feature = "rustls-tls")] | ||||||
|  | extern crate hyper_rustls; | ||||||
|  | #[cfg(feature = "rustls-tls")] | ||||||
|  | extern crate tokio_rustls; | ||||||
|  | #[cfg(feature = "rustls-tls")] | ||||||
|  | extern crate webpki_roots; | ||||||
|  | #[cfg(feature = "rustls-tls")] | ||||||
|  | extern crate rustls; | ||||||
|  |  | ||||||
| pub use hyper::header; | pub use hyper::header; | ||||||
| pub use hyper::Method; | pub use hyper::Method; | ||||||
| pub use hyper::{StatusCode, Version}; | pub use hyper::{StatusCode, Version}; | ||||||
| @@ -180,7 +189,7 @@ pub use self::proxy::Proxy; | |||||||
| pub use self::redirect::{RedirectAction, RedirectAttempt, RedirectPolicy}; | pub use self::redirect::{RedirectAction, RedirectAttempt, RedirectPolicy}; | ||||||
| pub use self::request::{Request, RequestBuilder}; | pub use self::request::{Request, RequestBuilder}; | ||||||
| pub use self::response::Response; | pub use self::response::Response; | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "tls")] | ||||||
| pub use self::tls::{Certificate, Identity}; | pub use self::tls::{Certificate, Identity}; | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -199,7 +208,7 @@ mod proxy; | |||||||
| mod redirect; | mod redirect; | ||||||
| mod request; | mod request; | ||||||
| mod response; | mod response; | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "tls")] | ||||||
| mod tls; | mod tls; | ||||||
| mod wait; | mod wait; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										135
									
								
								src/tls.rs
									
									
									
									
									
								
							
							
						
						
									
										135
									
								
								src/tls.rs
									
									
									
									
									
								
							| @@ -1,8 +1,32 @@ | |||||||
| use std::fmt; | use std::fmt; | ||||||
| use native_tls; | #[cfg(feature = "rustls-tls")] | ||||||
|  | use rustls::{TLSError, ServerCertVerifier, RootCertStore, ServerCertVerified}; | ||||||
|  | #[cfg(feature = "rustls-tls")] | ||||||
|  | use tokio_rustls::webpki::DNSNameRef; | ||||||
|  |  | ||||||
| /// Represent an X509 certificate. | /// Represent an X509 certificate. | ||||||
| pub struct Certificate(native_tls::Certificate); | pub struct Certificate { | ||||||
|  |     pub(crate) inner: inner::Certificate | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Represent a private key and X509 cert as a client certificate. | ||||||
|  | pub struct Identity { | ||||||
|  |     pub(crate) inner: inner::Identity | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub(crate) mod inner { | ||||||
|  |     pub(crate) enum Certificate { | ||||||
|  |         Der(Vec<u8>), | ||||||
|  |         Pem(Vec<u8>) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) enum Identity { | ||||||
|  |         #[cfg(feature = "default-tls")] | ||||||
|  |         Pkcs12(Vec<u8>, String), | ||||||
|  |         #[cfg(feature = "rustls-tls")] | ||||||
|  |         Pem(Vec<u8>), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl Certificate { | impl Certificate { | ||||||
|     /// Create a `Certificate` from a binary DER encoded certificate |     /// Create a `Certificate` from a binary DER encoded certificate | ||||||
| @@ -24,10 +48,11 @@ impl Certificate { | |||||||
|     /// |     /// | ||||||
|     /// # Errors |     /// # Errors | ||||||
|     /// |     /// | ||||||
|     /// If the provided buffer is not valid DER, an error will be returned. |     /// It never returns error. | ||||||
|     pub fn from_der(der: &[u8]) -> ::Result<Certificate> { |     pub fn from_der(der: &[u8]) -> ::Result<Certificate> { | ||||||
|         let inner = try_!(native_tls::Certificate::from_der(der)); |         Ok(Certificate { | ||||||
|         Ok(Certificate(inner)) |             inner: inner::Certificate::Der(der.to_owned()) | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -50,29 +75,14 @@ impl Certificate { | |||||||
|     /// |     /// | ||||||
|     /// # Errors |     /// # Errors | ||||||
|     /// |     /// | ||||||
|     /// If the provided buffer is not valid PEM, an error will be returned. |     /// It never returns error. | ||||||
|     pub fn from_pem(der: &[u8]) -> ::Result<Certificate> { |     pub fn from_pem(der: &[u8]) -> ::Result<Certificate> { | ||||||
|         let inner = try_!(native_tls::Certificate::from_pem(der)); |         Ok(Certificate { | ||||||
|         Ok(Certificate(inner)) |             inner: inner::Certificate::Pem(der.to_owned()) | ||||||
|     } |         }) | ||||||
|  |  | ||||||
|     pub(crate) fn cert(self) -> native_tls::Certificate { |  | ||||||
|         self.0 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl fmt::Debug for Certificate { |  | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |  | ||||||
|         f.debug_struct("Certificate") |  | ||||||
|             .finish() |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Represent a private key and X509 cert as a client certificate. |  | ||||||
| pub struct Identity(native_tls::Identity); |  | ||||||
|  |  | ||||||
| impl Identity { | impl Identity { | ||||||
|     /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key. |     /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key. | ||||||
|     /// |     /// | ||||||
| @@ -104,14 +114,49 @@ impl Identity { | |||||||
|     /// |     /// | ||||||
|     /// # Errors |     /// # Errors | ||||||
|     /// |     /// | ||||||
|     /// If the provided buffer is not valid DER, an error will be returned. |     /// It never returns error. | ||||||
|  |     #[cfg(feature = "default-tls")] | ||||||
|     pub fn from_pkcs12_der(der: &[u8], password: &str) -> ::Result<Identity> { |     pub fn from_pkcs12_der(der: &[u8], password: &str) -> ::Result<Identity> { | ||||||
|         let inner = try_!(native_tls::Identity::from_pkcs12(der, password)); |         Ok(Identity { | ||||||
|         Ok(Identity(inner)) |             inner: inner::Identity::Pkcs12(der.to_owned(), password.to_owned()) | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn pkcs12(self) -> native_tls::Identity { |     /// Parses PEM encoded private key and certificate. | ||||||
|         self.0 |     /// | ||||||
|  |     /// The input should contain a PEM encoded private key | ||||||
|  |     /// and at least one PEM encoded certificate. | ||||||
|  |     /// | ||||||
|  |     /// # Examples | ||||||
|  |     /// | ||||||
|  |     /// ``` | ||||||
|  |     /// # use std::fs::File; | ||||||
|  |     /// # use std::io::Read; | ||||||
|  |     /// # fn pem() -> Result<(), Box<std::error::Error>> { | ||||||
|  |     /// let mut buf = Vec::new(); | ||||||
|  |     /// File::open("my-ident.pem")? | ||||||
|  |     ///     .read_to_end(&mut buf)?; | ||||||
|  |     /// let id = reqwest::Identity::from_pem(&buf)?; | ||||||
|  |     /// # drop(id); | ||||||
|  |     /// # Ok(()) | ||||||
|  |     /// # } | ||||||
|  |     /// ``` | ||||||
|  |     /// | ||||||
|  |     /// # Errors | ||||||
|  |     /// | ||||||
|  |     /// It never returns error. | ||||||
|  |     #[cfg(feature = "rustls-tls")] | ||||||
|  |     pub fn from_pem(pem: &[u8]) -> ::Result<Identity> { | ||||||
|  |         Ok(Identity { | ||||||
|  |             inner: inner::Identity::Pem(pem.to_owned()) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl fmt::Debug for Certificate { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         f.debug_struct("Certificate") | ||||||
|  |             .finish() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -122,3 +167,35 @@ impl fmt::Debug for Identity { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub(crate) enum TLSBackend { | ||||||
|  |     #[cfg(feature = "default-tls")] | ||||||
|  |     Default, | ||||||
|  |     #[cfg(feature = "rustls-tls")] | ||||||
|  |     Rustls | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Default for TLSBackend { | ||||||
|  |     fn default() -> TLSBackend { | ||||||
|  |         #[cfg(feature = "default-tls")] | ||||||
|  |         { TLSBackend::Default } | ||||||
|  |  | ||||||
|  |         #[cfg(all(feature = "rustls-tls", not(feature = "default-tls")))] | ||||||
|  |         { TLSBackend::Rustls } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "rustls-tls")] | ||||||
|  | pub(crate) struct NoVerifier; | ||||||
|  |  | ||||||
|  | #[cfg(feature = "rustls-tls")] | ||||||
|  | impl ServerCertVerifier for NoVerifier { | ||||||
|  |     fn verify_server_cert( | ||||||
|  |         &self, | ||||||
|  |         _roots: &RootCertStore, | ||||||
|  |         _presented_certs: &[rustls::Certificate], | ||||||
|  |         _dns_name: DNSNameRef, | ||||||
|  |         _ocsp_response: &[u8] | ||||||
|  |     ) -> Result<ServerCertVerified, TLSError> { | ||||||
|  |         Ok(ServerCertVerified::assertion()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										46
									
								
								tests/badssl.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								tests/badssl.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | extern crate reqwest; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #[cfg(feature = "tls")] | ||||||
|  | #[test] | ||||||
|  | fn test_badssl_modern() { | ||||||
|  |     let text = reqwest::get("https://mozilla-modern.badssl.com/").unwrap() | ||||||
|  |         .text().unwrap(); | ||||||
|  |  | ||||||
|  |     assert!(text.contains("<title>mozilla-modern.badssl.com</title>")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "tls")] | ||||||
|  | #[test] | ||||||
|  | fn test_badssl_self_signed() { | ||||||
|  |     let text = reqwest::Client::builder() | ||||||
|  |         .danger_accept_invalid_certs(true) | ||||||
|  |         .build().unwrap() | ||||||
|  |         .get("https://self-signed.badssl.com/") | ||||||
|  |         .send().unwrap() | ||||||
|  |         .text().unwrap(); | ||||||
|  |  | ||||||
|  |     assert!(text.contains("<title>self-signed.badssl.com</title>")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "default-tls")] | ||||||
|  | #[test] | ||||||
|  | fn test_badssl_wrong_host() { | ||||||
|  |     let text = reqwest::Client::builder() | ||||||
|  |         .danger_accept_invalid_hostnames(true) | ||||||
|  |         .build().unwrap() | ||||||
|  |         .get("https://wrong.host.badssl.com/") | ||||||
|  |         .send().unwrap() | ||||||
|  |         .text().unwrap(); | ||||||
|  |  | ||||||
|  |     assert!(text.contains("<title>wrong.host.badssl.com</title>")); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     let result = reqwest::Client::builder() | ||||||
|  |         .danger_accept_invalid_hostnames(true) | ||||||
|  |         .build().unwrap() | ||||||
|  |         .get("https://self-signed.badssl.com/") | ||||||
|  |         .send(); | ||||||
|  |  | ||||||
|  |     assert!(result.is_err()); | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user