diff --git a/Cargo.toml b/Cargo.toml index 58c4d1b..cacf06a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ rustls-native-certs = { version = "0.3", optional = true } serde = { version = "1", optional = true } serde_json = { version = "1", optional = true } encoding = { version = "0.2", optional = true } +native-tls = { version = "0.2", optional = true } [dev-dependencies] serde = { version = "1", features = ["derive"] } diff --git a/src/agent.rs b/src/agent.rs index 6ee32a5..3d5839e 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -250,7 +250,7 @@ impl Agent { self.request("PATCH", path) } - #[cfg(test)] + #[cfg(all(test, any(feature = "tls", feature = "native-tls")))] pub(crate) fn state(&self) -> &Arc>> { &self.state } diff --git a/src/error.rs b/src/error.rs index e7db332..3a1c390 100644 --- a/src/error.rs +++ b/src/error.rs @@ -31,6 +31,9 @@ pub enum Error { ProxyConnect, /// Incorrect credentials for proxy InvalidProxyCreds, + /// TLS Error + #[cfg(feature = "native-tls")] + TlsError(native_tls::Error), } impl Error { @@ -59,6 +62,8 @@ impl Error { Error::BadProxyCreds => 500, Error::ProxyConnect => 500, Error::InvalidProxyCreds => 500, + #[cfg(feature = "native-tls")] + Error::TlsError(_) => 599, } } @@ -78,6 +83,8 @@ impl Error { Error::BadProxyCreds => "Failed to parse proxy credentials", Error::ProxyConnect => "Proxy failed to connect", Error::InvalidProxyCreds => "Provided proxy credentials are incorrect", + #[cfg(feature = "native-tls")] + Error::TlsError(_) => "TLS Error", } } @@ -97,6 +104,8 @@ impl Error { Error::BadProxyCreds => "Failed to parse proxy credentials".to_string(), Error::ProxyConnect => "Proxy failed to connect".to_string(), Error::InvalidProxyCreds => "Provided proxy credentials are incorrect".to_string(), + #[cfg(feature = "native-tls")] + Error::TlsError(err) => format!("TLS Error: {}", err), } } } diff --git a/src/lib.rs b/src/lib.rs index b0c457e..3f623a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ //! * Obvious API //! //! ``` +//! # #[cfg(feature = "json")] { //! // requires feature: `ureq = { version = "*", features = ["json"] }` //! # #[cfg(feature = "json")] { //! use ureq::json; @@ -195,7 +196,7 @@ mod tests { } #[test] - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "native-tls"))] fn connect_https_google() { let resp = get("https://www.google.com/").call(); assert_eq!( @@ -206,7 +207,7 @@ mod tests { } #[test] - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "native-tls"))] fn connect_https_invalid_name() { let resp = get("https://example.com{REQUEST_URI}/").call(); assert_eq!(400, resp.status()); diff --git a/src/pool.rs b/src/pool.rs index 3e45e82..995c2a1 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -29,12 +29,12 @@ impl ConnectionPool { self.recycle.remove(&PoolKey::new(url)) } - #[cfg(test)] + #[cfg(all(test, any(feature = "tls", feature = "native-tls")))] pub fn len(&self) -> usize { self.recycle.len() } - #[cfg(test)] + #[cfg(all(test, any(feature = "tls", feature = "native-tls")))] pub fn get(&self, hostname: &str, port: u16) -> Option<&Stream> { let key = PoolKey { hostname: hostname.into(), diff --git a/src/stream.rs b/src/stream.rs index 9103022..334c759 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -12,6 +12,9 @@ use rustls::StreamOwned; #[cfg(feature = "socks-proxy")] use socks::{TargetAddr, ToTargetAddr}; +#[cfg(feature = "native-tls")] +use native_tls::{TlsConnector, TlsStream, HandshakeError}; + use crate::proxy::Proto; use crate::proxy::Proxy; @@ -23,6 +26,8 @@ pub enum Stream { Http(TcpStream), #[cfg(feature = "tls")] Https(rustls::StreamOwned), + #[cfg(feature = "native-tls")] + Https(TlsStream), Cursor(Cursor>), #[cfg(test)] Test(Box, Vec), @@ -35,7 +40,7 @@ impl ::std::fmt::Debug for Stream { "Stream[{}]", match self { Stream::Http(_) => "http", - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "native-tls"))] Stream::Https(_) => "https", Stream::Cursor(_) => "cursor", #[cfg(test)] @@ -76,7 +81,7 @@ impl Stream { pub fn is_poolable(&self) -> bool { match self { Stream::Http(_) => true, - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "native-tls"))] Stream::Https(_) => true, _ => false, } @@ -95,7 +100,7 @@ impl Read for Stream { fn read(&mut self, buf: &mut [u8]) -> IoResult { match self { Stream::Http(sock) => sock.read(buf), - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "native-tls"))] Stream::Https(stream) => read_https(stream, buf), Stream::Cursor(read) => read.read(buf), #[cfg(test)] @@ -116,7 +121,20 @@ fn read_https( } } +#[cfg(feature = "native-tls")] +fn read_https( + stream: &mut TlsStream, + buf: &mut [u8], +) -> IoResult { + match stream.read(buf) { + Ok(size) => Ok(size), + Err(ref e) if is_close_notify(e) => Ok(0), + Err(e) => Err(e), + } +} + #[allow(deprecated)] +#[cfg(any(feature = "tls", feature = "native-tls"))] fn is_close_notify(e: &std::io::Error) -> bool { if e.kind() != ErrorKind::ConnectionAborted { return false; @@ -135,7 +153,7 @@ impl Write for Stream { fn write(&mut self, buf: &[u8]) -> IoResult { match self { Stream::Http(sock) => sock.write(buf), - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "native-tls"))] Stream::Https(stream) => stream.write(buf), Stream::Cursor(_) => panic!("Write to read only stream"), #[cfg(test)] @@ -145,7 +163,7 @@ impl Write for Stream { fn flush(&mut self) -> IoResult<()> { match self { Stream::Http(sock) => sock.flush(), - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "native-tls"))] Stream::Https(stream) => stream.flush(), Stream::Cursor(_) => panic!("Flush read only stream"), #[cfg(test)] @@ -162,6 +180,7 @@ pub(crate) fn connect_http(unit: &Unit) -> Result { connect_host(unit, hostname, port).map(Stream::Http) } + #[cfg(all(feature = "tls", feature = "native-certs"))] fn configure_certs(config: &mut rustls::ClientConfig) { config.root_store = @@ -204,6 +223,23 @@ pub(crate) fn connect_https(unit: &Unit) -> Result { Ok(Stream::Https(stream)) } +#[cfg(feature = "native-tls")] +pub(crate) fn connect_https(unit: &Unit) -> Result { + let hostname = unit.url.host_str().unwrap(); + let port = unit.url.port().unwrap_or(443); + let sock = connect_host(unit, hostname, port)?; + + let tls_connector = TlsConnector::new().map_err(|e| Error::TlsError(e))?; + let stream = tls_connector.connect(hostname, sock).map_err(|e| { + match e { + HandshakeError::Failure(err) => Error::TlsError(err), + _ => Error::BadStatusRead, + } + })?; + + Ok(Stream::Https(stream)) +} + pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result { // let sock_addrs: Vec = match unit.proxy { @@ -470,7 +506,8 @@ pub(crate) fn connect_test(unit: &Unit) -> Result { Err(Error::UnknownScheme(unit.url.scheme().to_string())) } -#[cfg(not(feature = "tls"))] +#[cfg(not(any(feature = "tls", feature = "native-tls")))] pub(crate) fn connect_https(unit: &Unit) -> Result { Err(Error::UnknownScheme(unit.url.scheme().to_string())) } + diff --git a/src/test/range.rs b/src/test/range.rs index 1aacf7f..a73acee 100644 --- a/src/test/range.rs +++ b/src/test/range.rs @@ -1,9 +1,11 @@ +#[cfg(any(feature = "tls", feature = "native-tls"))] use std::io::Read; +#[cfg(any(feature = "tls", feature = "native-tls"))] use super::super::*; #[test] -#[cfg(feature = "tls")] +#[cfg(any(feature = "tls", feature = "native-tls"))] fn read_range() { let resp = get("https://ureq.s3.eu-central-1.amazonaws.com/sherlock.txt") .set("Range", "bytes=1000-1999") @@ -20,7 +22,7 @@ fn read_range() { } #[test] -#[cfg(feature = "tls")] +#[cfg(any(feature = "tls", feature = "native-tls"))] fn agent_pool() { let agent = agent(); diff --git a/tests/https-agent.rs b/tests/https-agent.rs index e17c559..5703f68 100644 --- a/tests/https-agent.rs +++ b/tests/https-agent.rs @@ -1,7 +1,7 @@ +#[cfg(all(test, any(feature = "tls", feature = "native-tls")))] use std::io::Read; -#[cfg(feature = "tls")] -#[test] +#[cfg(all(test, any(feature = "tls", feature = "native-tls")))] fn tls_connection_close() { let agent = ureq::Agent::default().build(); let resp = agent