diff --git a/examples/README.md b/examples/README.md index b1bde9d..1ba24a0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,3 +7,4 @@ Examples: * basic - This is a very basic example of using the client. * gmail_oauth2 - This is an example using oauth2 for logging into gmail as a secure appplication. * rustls - This demonstrates how to use Rustls instead of Openssl for secure connections (helpful for cross compilation). + * timeout - This demonstrates how to use timeouts while connecting to an IMAP server. diff --git a/examples/timeout.rs b/examples/timeout.rs new file mode 100644 index 0000000..d2d717e --- /dev/null +++ b/examples/timeout.rs @@ -0,0 +1,92 @@ +extern crate imap; +extern crate native_tls; + +use imap::Client; +use native_tls::TlsConnector; +use native_tls::TlsStream; +use std::env; +use std::error::Error; +use std::fmt; +use std::net::{SocketAddr, TcpStream, ToSocketAddrs}; +use std::time::Duration; + +fn main() -> Result<(), Box> { + let server = env::var("IMAP_SERVER")?; + let port = env::var("IMAP_PORT").unwrap_or_else(|_| String::from("993")); + let port = port.parse()?; + + let username = env::var("IMAP_USER")?; + let password = env::var("IMAP_PASSWORD")?; + + let timeout = env::var("IMAP_TIMEOUT").unwrap_or_else(|_| String::from("1")); + let timeout = timeout.parse()?; + let timeout = Duration::from_secs(timeout); + + let tls = TlsConnector::builder().build()?; + + let client = connect_all_timeout((server.as_str(), port), server.as_str(), &tls, timeout)?; + + let mut session = client.login(&username, &password).map_err(|e| e.0)?; + + // do something productive with session + + session.logout()?; + + Ok(()) +} + +// connect to an IMAP host with a `Duration` timeout; note that this accepts only a single +// `SocketAddr` while `connect_all_timeout` does resolve the DNS entry and try to connect to all; +// this is necessary due to the difference of the function signatures of `TcpStream::connect` and +// `TcpStream::connect_timeout` +fn connect_timeout>( + addr: &SocketAddr, + domain: S, + ssl_connector: &TlsConnector, + timeout: Duration, +) -> Result>, Box> { + // the timeout is actually used with the initial TcpStream + let tcp_stream = TcpStream::connect_timeout(addr, timeout)?; + + let tls_stream = TlsConnector::connect(ssl_connector, domain.as_ref(), tcp_stream)?; + + let mut client = Client::new(tls_stream); + + // don't forget to wait for the IMAP protocol server greeting ;) + client.read_greeting()?; + + Ok(client) +} + +// resolve address and try to connect to all in order; note that this function is required to fully +// mimic `imap::connect` with the usage of `ToSocketAddrs` +fn connect_all_timeout>( + addr: A, + domain: S, + ssl_connector: &TlsConnector, + timeout: Duration, +) -> Result>, Box> { + let addrs = addr.to_socket_addrs()?; + + for addr in addrs { + match connect_timeout(&addr, &domain, ssl_connector, timeout) { + Ok(client) => return Ok(client), + Err(error) => eprintln!("couldn't connect to {}: {}", addr, error), + } + } + + Err(Box::new(TimeoutError)) +} + +// very simple timeout error; instead of printing the errors immediately like in +// `connect_all_timeout`, you may want to collect and return them +#[derive(Debug)] +struct TimeoutError; + +impl fmt::Display for TimeoutError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "all addresses failed to connect") + } +} + +impl Error for TimeoutError {} diff --git a/src/client.rs b/src/client.rs index a640668..33f5d1a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -240,6 +240,29 @@ impl Client { /// /// This method primarily exists for writing tests that mock the underlying transport, but can /// also be used to support IMAP over custom tunnels. + /// + /// **Note:** In case you do need to use `Client::new` over `imap::connect`, you will need to + /// listen for the IMAP protocol server greeting before authenticating: + /// + /// ```rust,no_run + /// # extern crate imap; + /// # extern crate native_tls; + /// # use imap::Client; + /// # use native_tls::TlsConnector; + /// # use std::io; + /// # use std::net::TcpStream; + /// # fn main() { + /// # let server = "imap.example.com"; + /// # let username = ""; + /// # let password = ""; + /// # let tcp = TcpStream::connect((server, 993)).unwrap(); + /// # let ssl_connector = TlsConnector::builder().build().unwrap(); + /// # let tls = TlsConnector::connect(&ssl_connector, server.as_ref(), tcp).unwrap(); + /// let mut client = Client::new(tls); + /// client.read_greeting().unwrap(); + /// let session = client.login(username, password).unwrap(); + /// # } + /// ``` pub fn new(stream: T) -> Client { Client { conn: Connection {