diff --git a/Cargo.toml b/Cargo.toml index b541d62..4ebeb9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,12 @@ maintenance = { status = "actively-developed" } is-it-maintained-issue-resolution = { repository = "jonhoo/rust-imap" } is-it-maintained-open-issues = { repository = "jonhoo/rust-imap" } +[features] +tls = ["native-tls"] +default = ["tls"] + [dependencies] -native-tls = "0.2.2" +native-tls = { version = "0.2.2", optional = true } regex = "1.0" bufstream = "0.1" imap-proto = "0.9.0" @@ -35,3 +39,15 @@ lazy_static = "1.4" lettre = "0.9" lettre_email = "0.9" rustls-connector = "0.8.0" + +[[example]] +name = "basic" +required-features = ["default"] + +[[example]] +name = "gmail_oauth2" +required-features = ["default"] + +[[test]] +name = "imap_integration" +required-features = ["default"] diff --git a/src/client.rs b/src/client.rs index f517a6c..7c7aceb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,6 @@ use base64; use bufstream::BufStream; +#[cfg(feature = "tls")] use native_tls::{TlsConnector, TlsStream}; use nom; use std::collections::HashSet; @@ -109,39 +110,6 @@ impl DerefMut for Session { } } -/// Connect to a server using an insecure TCP connection. -/// -/// The returned [`Client`] is unauthenticated; to access session-related methods (through -/// [`Session`]), use [`Client::login`] or [`Client::authenticate`]. -/// -/// Consider using [`connect`] for a secured connection where possible. -/// You can upgrade an insecure client to a secure one using [`Client::secure`]. -/// ```rust,no_run -/// # extern crate native_tls; -/// # extern crate imap; -/// # use std::io; -/// # use native_tls::TlsConnector; -/// # fn main() { -/// // a plain, unencrypted TCP connection -/// let client = imap::connect_insecure(("imap.example.org", 143)).unwrap(); -/// -/// // upgrade to SSL -/// let tls = TlsConnector::builder().build().unwrap(); -/// let tls_client = client.secure("imap.example.org", &tls); -/// # } -/// ``` -pub fn connect_insecure(addr: A) -> Result> { - match TcpStream::connect(addr) { - Ok(stream) => { - let mut socket = Client::new(stream); - - socket.read_greeting()?; - Ok(socket) - } - Err(e) => Err(Error::Io(e)), - } -} - /// Connect to a server using a TLS-encrypted connection. /// /// The returned [`Client`] is unauthenticated; to access session-related methods (through @@ -162,6 +130,7 @@ pub fn connect_insecure(addr: A) -> Result> /// let client = imap::connect(("imap.example.org", 993), "imap.example.org", &tls).unwrap(); /// # } /// ``` +#[cfg(feature = "tls")] pub fn connect>( addr: A, domain: S, @@ -186,6 +155,7 @@ impl Client { /// This will upgrade an IMAP client from using a regular TCP connection to use TLS. /// /// The domain parameter is required to perform hostname verification. + #[cfg(feature = "tls")] pub fn secure>( mut self, domain: S, diff --git a/src/error.rs b/src/error.rs index 756e197..5e9669e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,6 +3,7 @@ use std::error::Error as StdError; use std::fmt; use std::io::Error as IoError; +#[cfg(feature = "tls")] use std::net::TcpStream; use std::result; use std::str::Utf8Error; @@ -10,7 +11,9 @@ use std::str::Utf8Error; use base64::DecodeError; use bufstream::IntoInnerError as BufError; use imap_proto::Response; +#[cfg(feature = "tls")] use native_tls::Error as TlsError; +#[cfg(feature = "tls")] use native_tls::HandshakeError as TlsHandshakeError; /// A convenience wrapper around `Result` for `imap::Error`. @@ -22,8 +25,10 @@ pub enum Error { /// An `io::Error` that occurred while trying to read or write to a network stream. Io(IoError), /// An error from the `native_tls` library during the TLS handshake. + #[cfg(feature = "tls")] TlsHandshake(TlsHandshakeError), /// An error from the `native_tls` library while managing the socket. + #[cfg(feature = "tls")] Tls(TlsError), /// A BAD response from the IMAP server. Bad(String), @@ -38,6 +43,8 @@ pub enum Error { Validate(ValidateError), /// Error appending an e-mail. Append, + #[doc(hidden)] + __Nonexhaustive, } impl From for Error { @@ -58,12 +65,14 @@ impl From> for Error { } } +#[cfg(feature = "tls")] impl From> for Error { fn from(err: TlsHandshakeError) -> Error { Error::TlsHandshake(err) } } +#[cfg(feature = "tls")] impl From for Error { fn from(err: TlsError) -> Error { Error::Tls(err) @@ -80,7 +89,9 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Error::Io(ref e) => fmt::Display::fmt(e, f), + #[cfg(feature = "tls")] Error::Tls(ref e) => fmt::Display::fmt(e, f), + #[cfg(feature = "tls")] Error::TlsHandshake(ref e) => fmt::Display::fmt(e, f), Error::Validate(ref e) => fmt::Display::fmt(e, f), Error::No(ref data) | Error::Bad(ref data) => { @@ -95,7 +106,9 @@ impl StdError for Error { fn description(&self) -> &str { match *self { Error::Io(ref e) => e.description(), + #[cfg(feature = "tls")] Error::Tls(ref e) => e.description(), + #[cfg(feature = "tls")] Error::TlsHandshake(ref e) => e.description(), Error::Parse(ref e) => e.description(), Error::Validate(ref e) => e.description(), @@ -103,13 +116,16 @@ impl StdError for Error { Error::No(_) => "No Response", Error::ConnectionLost => "Connection lost", Error::Append => "Could not append mail to mailbox", + Error::__Nonexhaustive => "Unknown", } } fn cause(&self) -> Option<&dyn StdError> { match *self { Error::Io(ref e) => Some(e), + #[cfg(feature = "tls")] Error::Tls(ref e) => Some(e), + #[cfg(feature = "tls")] Error::TlsHandshake(ref e) => Some(e), Error::Parse(ParseError::DataNotUtf8(_, ref e)) => Some(e), _ => None, diff --git a/src/extensions/idle.rs b/src/extensions/idle.rs index 806a9b3..e802d32 100644 --- a/src/extensions/idle.rs +++ b/src/extensions/idle.rs @@ -3,6 +3,7 @@ use crate::client::Session; use crate::error::{Error, Result}; +#[cfg(feature = "tls")] use native_tls::TlsStream; use std::io::{self, Read, Write}; use std::net::TcpStream; @@ -164,6 +165,7 @@ impl<'a> SetReadTimeout for TcpStream { } } +#[cfg(feature = "tls")] impl<'a> SetReadTimeout for TlsStream { fn set_read_timeout(&mut self, timeout: Option) -> Result<()> { self.get_ref().set_read_timeout(timeout).map_err(Error::Io) diff --git a/src/lib.rs b/src/lib.rs index 7c85baa..48fc3d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,13 +50,29 @@ //! let body = std::str::from_utf8(body) //! .expect("message was not valid utf-8") //! .to_string(); -//! +//! //! // be nice to the server and log out //! imap_session.logout()?; -//! +//! //! Ok(Some(body)) //! } //! ``` +//! +//! ## Opting out of `native_tls` +//! +//! For situations where using openssl becomes problematic, you can disable the +//! default feature which provides integration with the `native_tls` crate. One major +//! reason you might want to do this is cross-compiling. To opt out of native_tls, add +//! this to your Cargo.toml file: +//! +//! ```toml +//! [dependencies.imap] +//! version = "" +//! default-features = false +//! ``` +//! +//! Even without `native_tls`, you can still use TLS by leveraging the pure Rust `rustls` +//! crate. See the example/rustls.rs file for a working example. #![deny(missing_docs)] #![warn(rust_2018_idioms)] diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index ed9eeb4..8b1c629 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -47,26 +47,16 @@ fn smtp(user: &str) -> lettre::SmtpTransport { .transport() } -#[test] -fn connect_insecure() { - imap::connect_insecure(&format!( - "{}:3143", - std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string()) - )) - .unwrap(); -} - #[test] #[ignore] fn connect_insecure_then_secure() { + let host = std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string()); + let stream = TcpStream::connect((host.as_ref(), 3143)).unwrap(); + // ignored because of https://github.com/greenmail-mail-test/greenmail/issues/135 - imap::connect_insecure(&format!( - "{}:3143", - std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string()) - )) - .unwrap() - .secure("imap.example.com", &tls()) - .unwrap(); + imap::Client::new(stream) + .secure("imap.example.com", &tls()) + .unwrap(); } #[test]