diff --git a/.travis.yml b/.travis.yml index ef199b9..41f4de5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,18 +8,15 @@ matrix: rust: stable - rust: stable - env: FEATURES="" - rust: beta - env: FEATURES="" - rust: nightly - env: FEATURES="" + + # Disable default-tls + - rust: stable + env: FEATURES="--no-default-features" - rust: stable env: FEATURES="--features hyper-011" - - rust: beta - env: FEATURES="--features hyper-011" - - rust: nightly - env: FEATURES="--features hyper-011" # minimum version - rust: 1.26.0 diff --git a/Cargo.toml b/Cargo.toml index 1f5ffba..d7f7eaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ futures = "0.1.23" http = "0.1.10" hyper = "0.12.13" hyper-old-types = { version = "0.11", optional = true, features = ["compat"] } -hyper-tls = "0.3" +hyper-tls = { version = "0.3", optional = true } libflate = "0.1.18" log = "0.4" mime = "0.3.7" mime_guess = "2.0.0-alpha.6" -native-tls = "0.2" +native-tls = { version = "0.2", optional = true } serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.5" @@ -36,8 +36,9 @@ env_logger = "0.5" serde_derive = "1.0" [features] -default = [] +default = ["default-tls"] hyper-011 = ["hyper-old-types"] +default-tls = ["hyper-tls", "native-tls"] [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docs_rs_workaround"] diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 56ec98e..673abd5 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -8,6 +8,7 @@ use hyper::client::ResponseFuture; use header::{HeaderMap, HeaderValue, LOCATION, USER_AGENT, REFERER, ACCEPT, ACCEPT_ENCODING, RANGE, TRANSFER_ENCODING, CONTENT_TYPE, CONTENT_LENGTH, CONTENT_ENCODING}; use mime::{self}; +#[cfg(feature = "default-tls")] use native_tls::{TlsConnector, TlsConnectorBuilder}; @@ -16,7 +17,9 @@ use super::response::Response; use connect::Connector; use into_url::to_uri; use redirect::{self, RedirectPolicy, remove_sensitive_headers}; -use {Certificate, Identity, IntoUrl, Method, Proxy, StatusCode, Url}; +use {IntoUrl, Method, Proxy, StatusCode, Url}; +#[cfg(feature = "default-tls")] +use {Certificate, Identity}; static DEFAULT_USER_AGENT: &'static str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); @@ -41,12 +44,15 @@ pub struct ClientBuilder { struct Config { gzip: bool, headers: HeaderMap, + #[cfg(feature = "default-tls")] hostname_verification: bool, + #[cfg(feature = "default-tls")] certs_verification: bool, proxies: Vec, redirect_policy: RedirectPolicy, referer: bool, timeout: Option, + #[cfg(feature = "default-tls")] tls: TlsConnectorBuilder, dns_threads: usize, } @@ -62,12 +68,15 @@ impl ClientBuilder { config: Config { gzip: true, headers: headers, + #[cfg(feature = "default-tls")] hostname_verification: true, + #[cfg(feature = "default-tls")] certs_verification: true, proxies: Vec::new(), redirect_policy: RedirectPolicy::default(), referer: true, timeout: None, + #[cfg(feature = "default-tls")] tls: TlsConnector::builder(), dns_threads: 4, }, @@ -80,16 +89,31 @@ impl ClientBuilder { /// /// This method fails if native TLS backend cannot be initialized. pub fn build(self) -> ::Result { - let mut config = self.config; + let config = self.config; - config.tls.danger_accept_invalid_hostnames(!config.hostname_verification); - config.tls.danger_accept_invalid_certs(!config.certs_verification); - let tls = try_!(config.tls.build()); + let connector = { + #[cfg(feature = "default-tls")] + { + let mut tls = config.tls; + tls.danger_accept_invalid_hostnames(!config.hostname_verification); + tls.danger_accept_invalid_certs(!config.certs_verification); - let proxies = Arc::new(config.proxies); + let tls = try_!(tls.build()); - let connector = Connector::new(config.dns_threads, tls, proxies.clone()); + let proxies = Arc::new(config.proxies); + + Connector::new(config.dns_threads, tls, proxies.clone()) + } + + + #[cfg(not(feature = "default-tls"))] + { + let proxies = Arc::new(config.proxies); + + Connector::new(config.dns_threads, proxies.clone()) + } + }; let hyper_client = ::hyper::Client::builder() .build(connector); @@ -109,12 +133,14 @@ impl ClientBuilder { /// /// This can be used to connect to a server that has a self-signed /// certificate for example. + #[cfg(feature = "default-tls")] pub fn add_root_certificate(mut self, cert: Certificate) -> ClientBuilder { self.config.tls.add_root_certificate(cert.cert()); self } /// Sets the identity to be used for client certificate authentication. + #[cfg(feature = "default-tls")] pub fn identity(mut self, identity: Identity) -> ClientBuilder { self.config.tls.identity(identity.pkcs12()); self @@ -130,6 +156,7 @@ impl ClientBuilder { /// hostname verification is not used, any valid certificate for any /// site will be trusted for use from any other. This introduces a /// significant vulnerability to man-in-the-middle attacks. + #[cfg(feature = "default-tls")] pub fn danger_accept_invalid_hostnames(mut self, accept_invalid_hostname: bool) -> ClientBuilder { self.config.hostname_verification = !accept_invalid_hostname; self @@ -147,6 +174,7 @@ impl ClientBuilder { /// will be trusted for use. This includes expired certificates. This /// introduces significant vulnerabilities, and should only be used /// as a last resort. + #[cfg(feature = "default-tls")] pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder { self.config.certs_verification = !accept_invalid_certs; self diff --git a/src/client.rs b/src/client.rs index 31497c0..02d722b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,7 +9,9 @@ use futures::sync::{mpsc, oneshot}; use request::{Request, RequestBuilder}; use response::Response; -use {async_impl, header, Certificate, Identity, Method, IntoUrl, Proxy, RedirectPolicy, wait}; +use {async_impl, header, Method, IntoUrl, Proxy, RedirectPolicy, wait}; +#[cfg(feature = "default-tls")] +use {Certificate, Identity}; /// A `Client` to make Requests with. /// @@ -106,6 +108,7 @@ impl ClientBuilder { /// # Errors /// /// This method fails if adding root certificate was unsuccessful. + #[cfg(feature = "default-tls")] pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder { self.with_inner(move |inner| inner.add_root_certificate(cert)) } @@ -133,6 +136,7 @@ impl ClientBuilder { /// # Ok(()) /// # } /// ``` + #[cfg(feature = "default-tls")] pub fn identity(self, identity: Identity) -> ClientBuilder { self.with_inner(move |inner| inner.identity(identity)) } @@ -148,6 +152,7 @@ impl ClientBuilder { /// hostname verification is not used, any valid certificate for any /// site will be trusted for use from any other. This introduces a /// significant vulnerability to man-in-the-middle attacks. + #[cfg(feature = "default-tls")] pub fn danger_accept_invalid_hostnames(self, accept_invalid_hostname: bool) -> ClientBuilder { self.with_inner(|inner| inner.danger_accept_invalid_hostnames(accept_invalid_hostname)) } @@ -164,6 +169,7 @@ impl ClientBuilder { /// will be trusted for use. This includes expired certificates. This /// introduces significant vulnerabilities, and should only be used /// as a last resort. + #[cfg(feature = "default-tls")] pub fn danger_accept_invalid_certs(self, accept_invalid_certs: bool) -> ClientBuilder { self.with_inner(|inner| inner.danger_accept_invalid_certs(accept_invalid_certs)) } diff --git a/src/connect.rs b/src/connect.rs index 4474b36..cc9ff09 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -1,34 +1,50 @@ -use bytes::{Buf, BufMut, IntoBuf}; -use futures::{Async, Future, Poll}; +use bytes::{Buf, BufMut}; +use futures::{Future, Poll}; use http::uri::Scheme; use hyper::client::{HttpConnector}; 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}; +#[cfg(feature = "default-tls")] use connect_async::{TlsConnectorExt, TlsStream}; -use std::io::{self, Cursor, Read, Write}; +use std::io::{self, Read, Write}; use std::sync::Arc; use Proxy; pub(crate) struct Connector { - https: HttpsConnector, + #[cfg(feature = "default-tls")] + http: HttpsConnector, + #[cfg(not(feature = "default-tls"))] + http: HttpConnector, proxies: Arc>, + #[cfg(feature = "default-tls")] tls: TlsConnector, } impl Connector { + #[cfg(not(feature = "default-tls"))] + pub(crate) fn new(threads: usize, proxies: Arc>) -> Connector { + let http = HttpConnector::new(threads); + Connector { + http, + proxies, + } + } + #[cfg(feature = "default-tls")] pub(crate) fn new(threads: usize, tls: TlsConnector, proxies: Arc>) -> Connector { let mut http = HttpConnector::new(threads); http.enforce_http(false); - let https = HttpsConnector::from((http, tls.clone())); + let http = HttpsConnector::from((http, tls.clone())); Connector { - https: https, - proxies: proxies, - tls: tls, + http, + proxies, + tls, } } } @@ -55,11 +71,13 @@ impl Connect for Connector { ndst.set_port(puri.port()); + #[cfg(feature = "default-tls")] + { if dst.scheme() == "https" { let host = dst.host().to_owned(); let port = dst.port().unwrap_or(443); let tls = self.tls.clone(); - return Box::new(self.https.connect(ndst).and_then(move |(conn, connected)| { + return Box::new(self.http.connect(ndst).and_then(move |(conn, connected)| { trace!("tunneling HTTPS over proxy"); tunnel(conn, host.clone(), port) .and_then(move |tunneled| { @@ -69,20 +87,27 @@ impl Connect for Connector { .map(|io| (Conn::Proxied(io), connected.proxy(true))) })); } - return Box::new(self.https.connect(ndst).map(|(io, connected)| (Conn::Normal(io), connected.proxy(true)))); + } + return Box::new(self.http.connect(ndst).map(|(io, connected)| (Conn::Normal(io), connected.proxy(true)))); } } - Box::new(self.https.connect(dst).map(|(io, connected)| (Conn::Normal(io), connected))) + Box::new(self.http.connect(dst).map(|(io, connected)| (Conn::Normal(io), connected))) } } type HttpStream = ::Transport; +#[cfg(feature = "default-tls")] type HttpsStream = MaybeHttpsStream; + pub(crate) type Connecting = Box + Send>; pub(crate) enum Conn { + #[cfg(feature = "default-tls")] Normal(HttpsStream), + #[cfg(not(feature = "default-tls"))] + Normal(HttpStream), + #[cfg(feature = "default-tls")] Proxied(TlsStream>), } @@ -91,6 +116,7 @@ impl Read for Conn { fn read(&mut self, buf: &mut [u8]) -> io::Result { match *self { Conn::Normal(ref mut s) => s.read(buf), + #[cfg(feature = "default-tls")] Conn::Proxied(ref mut s) => s.read(buf), } } @@ -101,6 +127,7 @@ impl Write for Conn { fn write(&mut self, buf: &[u8]) -> io::Result { match *self { Conn::Normal(ref mut s) => s.write(buf), + #[cfg(feature = "default-tls")] Conn::Proxied(ref mut s) => s.write(buf), } } @@ -109,6 +136,7 @@ impl Write for Conn { 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(), } } @@ -118,6 +146,7 @@ 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), } } @@ -125,6 +154,7 @@ impl AsyncRead for Conn { fn read_buf(&mut self, buf: &mut B) -> Poll { match *self { Conn::Normal(ref mut s) => s.read_buf(buf), + #[cfg(feature = "default-tls")] Conn::Proxied(ref mut s) => s.read_buf(buf), } } @@ -134,6 +164,7 @@ 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(), } } @@ -141,11 +172,13 @@ impl AsyncWrite for Conn { fn write_buf(&mut self, buf: &mut B) -> Poll { 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(conn: T, host: String, port: u16) -> Tunnel { let buf = format!("\ CONNECT {0}:{1} HTTP/1.1\r\n\ @@ -154,23 +187,26 @@ fn tunnel(conn: T, host: String, port: u16) -> Tunnel { ", host, port).into_bytes(); Tunnel { - buf: buf.into_buf(), + buf: io::Cursor::new(buf), conn: Some(conn), state: TunnelState::Writing, } } +#[cfg(feature = "default-tls")] struct Tunnel { - buf: Cursor>, + buf: io::Cursor>, conn: Option, state: TunnelState, } +#[cfg(feature = "default-tls")] enum TunnelState { Writing, Reading } +#[cfg(feature = "default-tls")] impl Future for Tunnel where T: AsyncRead + AsyncWrite { type Item = T; @@ -194,7 +230,7 @@ where T: AsyncRead + AsyncWrite { } else if read.len() > 12 { if read.starts_with(b"HTTP/1.1 200") || read.starts_with(b"HTTP/1.0 200") { if read.ends_with(b"\r\n\r\n") { - return Ok(Async::Ready(self.conn.take().unwrap())); + return Ok(self.conn.take().unwrap().into()); } // else read more } else { @@ -206,6 +242,7 @@ where T: AsyncRead + AsyncWrite { } } +#[cfg(feature = "default-tls")] #[inline] fn tunnel_eof() -> io::Error { io::Error::new( @@ -214,6 +251,7 @@ fn tunnel_eof() -> io::Error { ) } +#[cfg(feature = "default-tls")] #[cfg(test)] mod tests { use std::io::{Read, Write}; diff --git a/src/connect_async.rs b/src/connect_async.rs index f870fcf..ee9c769 100644 --- a/src/connect_async.rs +++ b/src/connect_async.rs @@ -1,8 +1,7 @@ use std::io::{self, Read, Write}; use futures::{Poll, Future, Async}; -use native_tls; -use native_tls::{HandshakeError, Error, TlsConnector}; +use native_tls::{self, HandshakeError, Error, TlsConnector}; use tokio_io::{AsyncRead, AsyncWrite}; /// A wrapper around an underlying raw stream which implements the TLS or SSL @@ -128,4 +127,4 @@ impl Future for MidHandshake { } } } -} \ No newline at end of file +} diff --git a/src/error.rs b/src/error.rs index 807c42f..cf2edb2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -135,6 +135,7 @@ impl Error { Kind::Hyper(ref e) => Some(e), Kind::Mime(ref e) => Some(e), Kind::Url(ref e) => Some(e), + #[cfg(feature = "default-tls")] Kind::Tls(ref e) => Some(e), Kind::Io(ref e) => Some(e), Kind::UrlEncoded(ref e) => Some(e), @@ -224,6 +225,7 @@ impl fmt::Display for Error { Kind::Mime(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"), + #[cfg(feature = "default-tls")] Kind::Tls(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), @@ -250,6 +252,7 @@ impl StdError for Error { Kind::Mime(ref e) => e.description(), Kind::Url(ref e) => e.description(), Kind::UrlBadScheme => "URL scheme is not allowed", + #[cfg(feature = "default-tls")] Kind::Tls(ref e) => e.description(), Kind::Io(ref e) => e.description(), Kind::UrlEncoded(ref e) => e.description(), @@ -267,6 +270,7 @@ impl StdError for Error { Kind::Hyper(ref e) => e.cause(), Kind::Mime(ref e) => e.cause(), Kind::Url(ref e) => e.cause(), + #[cfg(feature = "default-tls")] Kind::Tls(ref e) => e.cause(), Kind::Io(ref e) => e.cause(), Kind::UrlEncoded(ref e) => e.cause(), @@ -287,6 +291,7 @@ pub(crate) enum Kind { Mime(::mime::FromStrError), Url(::url::ParseError), UrlBadScheme, + #[cfg(feature = "default-tls")] Tls(::native_tls::Error), Io(io::Error), UrlEncoded(::serde_urlencoded::ser::Error), @@ -347,6 +352,7 @@ impl From<::serde_json::Error> for Kind { } } +#[cfg(feature = "default-tls")] impl From<::native_tls::Error> for Kind { fn from(err: ::native_tls::Error) -> Kind { Kind::Tls(err) diff --git a/src/lib.rs b/src/lib.rs index 0df1dac..2b40871 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,6 +117,16 @@ //! # } //! ``` //! +//! ## Optional Features +//! +//! The following are a list of [Cargo features][cargo-features] that can be +//! enabled or disabled: +//! +//! - **default-tls** *(enabled by default)*: Provides TLS support via the +//! `native-tls` library to connect over HTTPS. +//! - **hyper-011**: Provides support for hyper's old typed headers. +//! +//! //! [hyper]: http://hyper.rs //! [client]: ./struct.Client.html //! [response]: ./struct.Response.html @@ -124,6 +134,7 @@ //! [builder]: ./struct.RequestBuilder.html //! [serde]: http://serde.rs //! [cookiejar_issue]: https://github.com/seanmonstar/reqwest/issues/14 +//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section extern crate base64; extern crate bytes; @@ -134,12 +145,14 @@ extern crate http; extern crate hyper; #[cfg(feature = "hyper-011")] pub extern crate hyper_old_types as hyper_011; +#[cfg(feature = "default-tls")] extern crate hyper_tls; #[macro_use] extern crate log; extern crate libflate; extern crate mime; extern crate mime_guess; +#[cfg(feature = "default-tls")] extern crate native_tls; extern crate serde; #[cfg(test)] @@ -148,7 +161,7 @@ extern crate serde_derive; extern crate serde_json; extern crate serde_urlencoded; extern crate tokio; -#[macro_use] +#[cfg_attr(feature = "default-tls", macro_use)] extern crate tokio_io; extern crate url; extern crate uuid; @@ -167,6 +180,7 @@ pub use self::proxy::Proxy; pub use self::redirect::{RedirectAction, RedirectAttempt, RedirectPolicy}; pub use self::request::{Request, RequestBuilder}; pub use self::response::Response; +#[cfg(feature = "default-tls")] pub use self::tls::{Certificate, Identity}; @@ -176,6 +190,7 @@ mod error; mod async_impl; mod connect; +#[cfg(feature = "default-tls")] mod connect_async; mod body; mod client; @@ -184,6 +199,7 @@ mod proxy; mod redirect; mod request; mod response; +#[cfg(feature = "default-tls")] mod tls; mod wait;