diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 10ba00d..59c2e0d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,10 @@ jobs: with: command: test args: --features=tokio02 + - uses: actions-rs/cargo@v1 + with: + command: test + args: --features=tokio03 check: name: Check diff --git a/Cargo.toml b/Cargo.toml index 7255d55..1921c20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,9 @@ async-trait = { version = "0.1", optional = true } tokio02_crate = { package = "tokio", version = "0.2.7", features = ["fs", "process", "tcp", "dns", "io-util"], optional = true } tokio02_native_tls_crate = { package = "tokio-native-tls", version = "0.1", optional = true } tokio02_rustls = { package = "tokio-rustls", version = "0.14", optional = true } +tokio03_crate = { package = "tokio", version = "0.3", features = ["fs", "process", "net", "io-util"], optional = true } +tokio03_native_tls_crate = { package = "tokio-native-tls", version = "0.2", optional = true } +tokio03_rustls = { package = "tokio-rustls", version = "0.20", optional = true } [dev-dependencies] criterion = "0.3" @@ -65,6 +68,7 @@ tracing-subscriber = "0.2.10" glob = "0.3" walkdir = "2" tokio02_crate = { package = "tokio", version = "0.2.7", features = ["macros", "rt-threaded"] } +tokio03_crate = { package = "tokio", version = "0.3", features = ["macros", "rt-multi-thread"] } [[bench]] harness = false @@ -87,6 +91,9 @@ async-std1 = ["async-std", "async-trait", "async-attributes"] tokio02 = ["tokio02_crate", "async-trait", "futures-io", "futures-util"] tokio02-native-tls = ["tokio02", "native-tls", "tokio02_native_tls_crate"] tokio02-rustls-tls = ["tokio02", "rustls-tls", "tokio02_rustls"] +tokio03 = ["tokio03_crate", "async-trait", "futures-io", "futures-util"] +tokio03-native-tls = ["tokio03", "native-tls", "tokio03_native_tls_crate"] +tokio03-rustls-tls = ["tokio03", "rustls-tls", "tokio03_rustls"] [package.metadata.docs.rs] all-features = true @@ -115,3 +122,12 @@ required-features = ["smtp-transport", "tokio02", "tokio02-native-tls"] [[example]] name = "tokio02_smtp_starttls" required-features = ["smtp-transport", "tokio02", "tokio02-native-tls"] + +[[example]] +name = "tokio03_smtp_tls" +required-features = ["smtp-transport", "tokio03", "tokio03-native-tls"] + +[[example]] +name = "tokio03_smtp_starttls" +required-features = ["smtp-transport", "tokio03", "tokio03-native-tls"] + diff --git a/examples/tokio03_smtp_starttls.rs b/examples/tokio03_smtp_starttls.rs new file mode 100644 index 0000000..131e33b --- /dev/null +++ b/examples/tokio03_smtp_starttls.rs @@ -0,0 +1,36 @@ +// This line is only to make it compile from lettre's examples folder, +// since it uses Rust 2018 crate renaming to import tokio. +// Won't be needed in user's code. +use tokio03_crate as tokio; + +use lettre::{ + transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio03Connector, + Tokio03Transport, +}; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + + let email = Message::builder() + .from("NoBody ".parse().unwrap()) + .reply_to("Yuin ".parse().unwrap()) + .to("Hei ".parse().unwrap()) + .subject("Happy new async year") + .body("Be happy with async!") + .unwrap(); + + let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); + + // Open a remote connection to gmail using STARTTLS + let mailer = AsyncSmtpTransport::::starttls_relay("smtp.gmail.com") + .unwrap() + .credentials(creds) + .build(); + + // Send the email + match mailer.send(email).await { + Ok(_) => println!("Email sent successfully!"), + Err(e) => panic!("Could not send email: {:?}", e), + } +} diff --git a/examples/tokio03_smtp_tls.rs b/examples/tokio03_smtp_tls.rs new file mode 100644 index 0000000..344460a --- /dev/null +++ b/examples/tokio03_smtp_tls.rs @@ -0,0 +1,36 @@ +// This line is only to make it compile from lettre's examples folder, +// since it uses Rust 2018 crate renaming to import tokio. +// Won't be needed in user's code. +use tokio03_crate as tokio; + +use lettre::{ + transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio03Connector, + Tokio03Transport, +}; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + + let email = Message::builder() + .from("NoBody ".parse().unwrap()) + .reply_to("Yuin ".parse().unwrap()) + .to("Hei ".parse().unwrap()) + .subject("Happy new async year") + .body("Be happy with async!") + .unwrap(); + + let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); + + // Open a remote connection to gmail + let mailer = AsyncSmtpTransport::::relay("smtp.gmail.com") + .unwrap() + .credentials(creds) + .build(); + + // Send the email + match mailer.send(email).await { + Ok(_) => println!("Email sent successfully!"), + Err(e) => panic!("Could not send email: {:?}", e), + } +} diff --git a/src/lib.rs b/src/lib.rs index 8f62822..d2f6628 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,9 @@ //! * **tokio02**: Allow to asyncronously send emails using tokio 0.2.x //! * **tokio02-rustls-tls**: Async TLS support with the `rustls` crate using tokio 0.2 //! * **tokio02-native-tls**: Async TLS support with the `native-tls` crate using tokio 0.2 +//! * **tokio03**: Allow to asyncronously send emails using tokio 0.3.x +//! * **tokio03-rustls-tls**: Async TLS support with the `rustls` crate using tokio 0.3 +//! * **tokio03-native-tls**: Async TLS support with the `native-tls` crate using tokio 0.3 //! * **async-std1**: Allow to asyncronously send emails using async-std 1.x (SMTP isn't supported yet) //! * **r2d2**: Connection pool for SMTP transport //! * **tracing**: Logging using the `tracing` crate @@ -61,12 +64,19 @@ pub use crate::transport::file::FileTransport; pub use crate::transport::sendmail::SendmailTransport; #[cfg(all(feature = "smtp-transport", feature = "connection-pool"))] pub use crate::transport::smtp::r2d2::SmtpConnectionManager; +#[cfg(all( + feature = "smtp-transport", + any(feature = "tokio02", feature = "tokio03") +))] +pub use crate::transport::smtp::AsyncSmtpTransport; #[cfg(feature = "smtp-transport")] pub use crate::transport::smtp::SmtpTransport; #[cfg(all(feature = "smtp-transport", feature = "tokio02"))] -pub use crate::transport::smtp::{AsyncSmtpTransport, Tokio02Connector}; +pub use crate::transport::smtp::Tokio02Connector; +#[cfg(all(feature = "smtp-transport", feature = "tokio03"))] +pub use crate::transport::smtp::Tokio03Connector; pub use crate::{address::Address, transport::stub::StubTransport}; -#[cfg(any(feature = "async-std1", feature = "tokio02"))] +#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio03"))] use async_trait::async_trait; #[cfg(feature = "builder")] use std::convert::TryFrom; @@ -257,6 +267,29 @@ pub trait Tokio02Transport { async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result; } +/// tokio 0.3.x based Transport method for emails +#[cfg(feature = "tokio03")] +#[cfg_attr(docsrs, doc(cfg(feature = "tokio03")))] +#[async_trait] +pub trait Tokio03Transport { + /// Response produced by the Transport + type Ok; + /// Error produced by the Transport + type Error; + + /// Sends the email + #[cfg(feature = "builder")] + #[cfg_attr(docsrs, doc(cfg(feature = "builder")))] + // TODO take &Message + async fn send(&self, message: Message) -> Result { + let raw = message.formatted(); + let envelope = message.envelope(); + self.send_raw(&envelope, &raw).await + } + + async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result; +} + #[cfg(test)] mod test { use super::*; diff --git a/src/transport/file/mod.rs b/src/transport/file/mod.rs index 1fbb24c..40cfcc1 100644 --- a/src/transport/file/mod.rs +++ b/src/transport/file/mod.rs @@ -90,8 +90,10 @@ pub use self::error::Error; use crate::AsyncStd1Transport; #[cfg(feature = "tokio02")] use crate::Tokio02Transport; +#[cfg(feature = "tokio03")] +use crate::Tokio03Transport; use crate::{Envelope, Transport}; -#[cfg(any(feature = "async-std1", feature = "tokio02"))] +#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio03"))] use async_trait::async_trait; use std::{ path::{Path, PathBuf}, @@ -199,3 +201,19 @@ impl Tokio02Transport for FileTransport { Ok(email_id.to_string()) } } + +#[cfg(feature = "tokio03")] +#[async_trait] +impl Tokio03Transport for FileTransport { + type Ok = Id; + type Error = Error; + + async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { + use tokio03_crate::fs; + + let (email_id, file, serialized) = self.send_raw_impl(envelope, email)?; + + fs::write(file, serialized).await?; + Ok(email_id.to_string()) + } +} diff --git a/src/transport/sendmail/mod.rs b/src/transport/sendmail/mod.rs index 5cf6139..94838d5 100644 --- a/src/transport/sendmail/mod.rs +++ b/src/transport/sendmail/mod.rs @@ -65,8 +65,10 @@ pub use self::error::Error; use crate::AsyncStd1Transport; #[cfg(feature = "tokio02")] use crate::Tokio02Transport; +#[cfg(feature = "tokio03")] +use crate::Tokio03Transport; use crate::{Envelope, Transport}; -#[cfg(any(feature = "async-std1", feature = "tokio02"))] +#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio03"))] use async_trait::async_trait; use std::{ ffi::OsString, @@ -125,6 +127,21 @@ impl SendmailTransport { .stdout(Stdio::piped()); c } + + #[cfg(feature = "tokio03")] + fn tokio03_command(&self, envelope: &Envelope) -> tokio03_crate::process::Command { + use tokio03_crate::process::Command; + + let mut c = Command::new(&self.command); + c.kill_on_drop(true); + c.arg("-i") + .arg("-f") + .arg(envelope.from().map(|f| f.as_ref()).unwrap_or("\"\"")) + .args(envelope.to()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()); + c + } } impl Default for SendmailTransport { @@ -204,3 +221,28 @@ impl Tokio02Transport for SendmailTransport { } } } + +#[cfg(feature = "tokio03")] +#[async_trait] +impl Tokio03Transport for SendmailTransport { + type Ok = (); + type Error = Error; + + async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { + use tokio03_crate::io::AsyncWriteExt; + + let mut command = self.tokio03_command(envelope); + + // Spawn the sendmail command + let mut process = command.spawn()?; + + process.stdin.as_mut().unwrap().write_all(&email).await?; + let output = process.wait_with_output().await?; + + if output.status.success() { + Ok(()) + } else { + Err(Error::Client(String::from_utf8(output.stderr)?)) + } + } +} diff --git a/src/transport/smtp/async_transport.rs b/src/transport/smtp/async_transport.rs index 7a537cc..6eac753 100644 --- a/src/transport/smtp/async_transport.rs +++ b/src/transport/smtp/async_transport.rs @@ -1,11 +1,15 @@ use async_trait::async_trait; -#[cfg(feature = "tokio02")] +#[cfg(any(feature = "tokio02", feature = "tokio03"))] use super::Tls; use super::{ client::AsyncSmtpConnection, ClientId, Credentials, Error, Mechanism, Response, SmtpInfo, }; -use crate::{Envelope, Tokio02Transport}; +use crate::Envelope; +#[cfg(feature = "tokio02")] +use crate::Tokio02Transport; +#[cfg(feature = "tokio03")] +use crate::Tokio03Transport; #[allow(missing_debug_implementations)] #[derive(Clone)] @@ -14,6 +18,7 @@ pub struct AsyncSmtpTransport { inner: AsyncSmtpClient, } +#[cfg(feature = "tokio02")] #[async_trait] impl Tokio02Transport for AsyncSmtpTransport { type Ok = Response; @@ -31,6 +36,24 @@ impl Tokio02Transport for AsyncSmtpTransport { } } +#[cfg(feature = "tokio03")] +#[async_trait] +impl Tokio03Transport for AsyncSmtpTransport { + type Ok = Response; + type Error = Error; + + /// Sends an email + async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { + let mut conn = self.inner.connection().await?; + + let result = conn.send(envelope, email).await?; + + conn.quit().await?; + + Ok(result) + } +} + impl AsyncSmtpTransport where C: AsyncSmtpConnector, @@ -41,11 +64,16 @@ where /// /// Creates an encrypted transport over submissions port, using the provided domain /// to validate TLS certificates. - #[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))] + #[cfg(any( + feature = "tokio02-native-tls", + feature = "tokio02-rustls-tls", + feature = "tokio03-native-tls", + feature = "tokio03-rustls-tls" + ))] pub fn relay(relay: &str) -> Result { use super::{TlsParameters, SUBMISSIONS_PORT}; - let tls_parameters = TlsParameters::builder(relay.into()).build_tokio02()?; + let tls_parameters = TlsParameters::new(relay.into())?; Ok(Self::builder_dangerous(relay) .port(SUBMISSIONS_PORT) @@ -63,7 +91,12 @@ where /// /// An error is returned if the connection can't be upgraded. No credentials /// or emails will be sent to the server, protecting from downgrade attacks. - #[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))] + #[cfg(any( + feature = "tokio02-native-tls", + feature = "tokio02-rustls-tls", + feature = "tokio03-native-tls", + feature = "tokio03-rustls-tls" + ))] pub fn starttls_relay(relay: &str) -> Result { use super::{TlsParameters, SUBMISSION_PORT}; @@ -133,7 +166,12 @@ impl AsyncSmtpTransportBuilder { } /// Set the TLS settings to use - #[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))] + #[cfg(any( + feature = "tokio02-native-tls", + feature = "tokio02-rustls-tls", + feature = "tokio03-native-tls", + feature = "tokio03-rustls-tls" + ))] pub fn tls(mut self, tls: Tls) -> Self { self.info.tls = tls; self @@ -235,6 +273,48 @@ impl AsyncSmtpConnector for Tokio02Connector { } } +#[derive(Debug, Copy, Clone, Default)] +#[cfg(feature = "tokio03")] +#[cfg_attr(docsrs, doc(cfg(feature = "tokio03")))] +pub struct Tokio03Connector; + +#[async_trait] +#[cfg(feature = "tokio03")] +impl AsyncSmtpConnector for Tokio03Connector { + async fn connect( + hostname: &str, + port: u16, + hello_name: &ClientId, + tls: &Tls, + ) -> Result { + #[allow(clippy::match_single_binding)] + let tls_parameters = match tls { + #[cfg(any(feature = "tokio03-native-tls", feature = "tokio03-rustls-tls"))] + Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()), + _ => None, + }; + #[allow(unused_mut)] + let mut conn = + AsyncSmtpConnection::connect_tokio03(hostname, port, hello_name, tls_parameters) + .await?; + + #[cfg(any(feature = "tokio03-native-tls", feature = "tokio03-rustls-tls"))] + match tls { + Tls::Opportunistic(ref tls_parameters) => { + if conn.can_starttls() { + conn.starttls(tls_parameters.clone(), hello_name).await?; + } + } + Tls::Required(ref tls_parameters) => { + conn.starttls(tls_parameters.clone(), hello_name).await?; + } + _ => (), + } + + Ok(conn) + } +} + mod private { use super::*; @@ -242,4 +322,7 @@ mod private { #[cfg(feature = "tokio02")] impl Sealed for Tokio02Connector {} + + #[cfg(feature = "tokio03")] + impl Sealed for Tokio03Connector {} } diff --git a/src/transport/smtp/client/async_connection.rs b/src/transport/smtp/client/async_connection.rs index cdfd970..311af54 100644 --- a/src/transport/smtp/client/async_connection.rs +++ b/src/transport/smtp/client/async_connection.rs @@ -45,11 +45,10 @@ impl AsyncSmtpConnection { &self.server_info } - // FIXME add simple connect and rename this one - /// Connects to the configured server /// /// Sends EHLO and parses server information + #[cfg(feature = "tokio02")] pub async fn connect_tokio02( hostname: &str, port: u16, @@ -60,6 +59,20 @@ impl AsyncSmtpConnection { Self::connect_impl(stream, hello_name).await } + /// Connects to the configured server + /// + /// Sends EHLO and parses server information + #[cfg(feature = "tokio03")] + pub async fn connect_tokio03( + hostname: &str, + port: u16, + hello_name: &ClientId, + tls_parameters: Option, + ) -> Result { + let stream = AsyncNetworkStream::connect_tokio03(hostname, port, tls_parameters).await?; + Self::connect_impl(stream, hello_name).await + } + async fn connect_impl( stream: AsyncNetworkStream, hello_name: &ClientId, diff --git a/src/transport/smtp/client/async_net.rs b/src/transport/smtp/client/async_net.rs index 4ef56e7..42104a8 100644 --- a/src/transport/smtp/client/async_net.rs +++ b/src/transport/smtp/client/async_net.rs @@ -8,17 +8,30 @@ use std::{ use futures_io::{Error as IoError, ErrorKind, Result as IoResult}; #[cfg(feature = "tokio02")] -use tokio02_crate::io::{AsyncRead, AsyncWrite}; +use tokio02_crate::io::{AsyncRead as _, AsyncWrite as _}; #[cfg(feature = "tokio02")] -use tokio02_crate::net::TcpStream; +use tokio02_crate::net::TcpStream as Tokio02TcpStream; +#[cfg(feature = "tokio03")] +use tokio03_crate::io::{AsyncRead as _, AsyncWrite as _, ReadBuf as Tokio03ReadBuf}; +#[cfg(feature = "tokio03")] +use tokio03_crate::net::TcpStream as Tokio03TcpStream; #[cfg(feature = "tokio02-native-tls")] -use tokio02_native_tls_crate::TlsStream; +use tokio02_native_tls_crate::TlsStream as Tokio02TlsStream; +#[cfg(feature = "tokio03-native-tls")] +use tokio03_native_tls_crate::TlsStream as Tokio03TlsStream; #[cfg(feature = "tokio02-rustls-tls")] -use tokio02_rustls::client::TlsStream as RustlsTlsStream; +use tokio02_rustls::client::TlsStream as Tokio02RustlsTlsStream; +#[cfg(feature = "tokio03-rustls-tls")] +use tokio03_rustls::client::TlsStream as Tokio03RustlsTlsStream; -#[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))] +#[cfg(any( + feature = "tokio02-native-tls", + feature = "tokio02-rustls-tls", + feature = "tokio03-native-tls", + feature = "tokio03-rustls-tls" +))] use super::InnerTlsParameters; use super::TlsParameters; use crate::transport::smtp::Error; @@ -31,15 +44,24 @@ pub struct AsyncNetworkStream { /// Represents the different types of underlying network streams #[allow(dead_code)] enum InnerAsyncNetworkStream { - /// Plain TCP stream + /// Plain Tokio 0.2 TCP stream #[cfg(feature = "tokio02")] - Tokio02Tcp(TcpStream), - /// Encrypted TCP stream + Tokio02Tcp(Tokio02TcpStream), + /// Encrypted Tokio 0.2 TCP stream #[cfg(feature = "tokio02-native-tls")] - Tokio02NativeTls(TlsStream), - /// Encrypted TCP stream + Tokio02NativeTls(Tokio02TlsStream), + /// Encrypted Tokio 0.2 TCP stream #[cfg(feature = "tokio02-rustls-tls")] - Tokio02RustlsTls(Box>), + Tokio02RustlsTls(Box>), + /// Plain Tokio 0.3 TCP stream + #[cfg(feature = "tokio03")] + Tokio03Tcp(Tokio03TcpStream), + /// Encrypted Tokio 0.3 TCP stream + #[cfg(feature = "tokio03-native-tls")] + Tokio03NativeTls(Tokio03TlsStream), + /// Encrypted Tokio 0.3 TCP stream + #[cfg(feature = "tokio03-rustls-tls")] + Tokio03RustlsTls(Box>), /// Can't be built None, } @@ -47,7 +69,7 @@ enum InnerAsyncNetworkStream { impl AsyncNetworkStream { fn new(inner: InnerAsyncNetworkStream) -> Self { if let InnerAsyncNetworkStream::None = inner { - debug_assert!(false, "InnerAsyncNetworkStream::None should never be built"); + debug_assert!(false, "InnerAsyncNetworkStream::None must never be built"); } AsyncNetworkStream { inner } @@ -64,11 +86,19 @@ impl AsyncNetworkStream { } #[cfg(feature = "tokio02-rustls-tls")] InnerAsyncNetworkStream::Tokio02RustlsTls(ref s) => s.get_ref().0.peer_addr(), + #[cfg(feature = "tokio03")] + InnerAsyncNetworkStream::Tokio03Tcp(ref s) => s.peer_addr(), + #[cfg(feature = "tokio03-native-tls")] + InnerAsyncNetworkStream::Tokio03NativeTls(ref s) => { + s.get_ref().get_ref().get_ref().peer_addr() + } + #[cfg(feature = "tokio03-rustls-tls")] + InnerAsyncNetworkStream::Tokio03RustlsTls(ref s) => s.get_ref().0.peer_addr(), InnerAsyncNetworkStream::None => { - debug_assert!(false, "InnerAsyncNetworkStream::None should never be built"); + debug_assert!(false, "InnerAsyncNetworkStream::None must never be built"); Err(IoError::new( ErrorKind::Other, - "InnerAsyncNetworkStream::None should never be built", + "InnerAsyncNetworkStream::None must never be built", )) } } @@ -85,8 +115,16 @@ impl AsyncNetworkStream { } #[cfg(feature = "tokio02-rustls-tls")] InnerAsyncNetworkStream::Tokio02RustlsTls(ref s) => s.get_ref().0.shutdown(how), + #[cfg(feature = "tokio03")] + InnerAsyncNetworkStream::Tokio03Tcp(ref s) => s.shutdown(how), + #[cfg(feature = "tokio03-native-tls")] + InnerAsyncNetworkStream::Tokio03NativeTls(ref s) => { + s.get_ref().get_ref().get_ref().shutdown(how) + } + #[cfg(feature = "tokio03-rustls-tls")] + InnerAsyncNetworkStream::Tokio03RustlsTls(ref s) => s.get_ref().0.shutdown(how), InnerAsyncNetworkStream::None => { - debug_assert!(false, "InnerAsyncNetworkStream::None should never be built"); + debug_assert!(false, "InnerAsyncNetworkStream::None must never be built"); Ok(()) } } @@ -98,7 +136,7 @@ impl AsyncNetworkStream { port: u16, tls_parameters: Option, ) -> Result { - let tcp_stream = TcpStream::connect((hostname, port)).await?; + let tcp_stream = Tokio02TcpStream::connect((hostname, port)).await?; let mut stream = AsyncNetworkStream::new(InnerAsyncNetworkStream::Tokio02Tcp(tcp_stream)); if let Some(tls_parameters) = tls_parameters { @@ -107,9 +145,27 @@ impl AsyncNetworkStream { Ok(stream) } + #[cfg(feature = "tokio03")] + pub async fn connect_tokio03( + hostname: &str, + port: u16, + tls_parameters: Option, + ) -> Result { + let tcp_stream = Tokio03TcpStream::connect((hostname, port)).await?; + + let mut stream = AsyncNetworkStream::new(InnerAsyncNetworkStream::Tokio03Tcp(tcp_stream)); + if let Some(tls_parameters) = tls_parameters { + stream.upgrade_tls(tls_parameters).await?; + } + Ok(stream) + } + pub async fn upgrade_tls(&mut self, tls_parameters: TlsParameters) -> Result<(), Error> { match &self.inner { - #[cfg(not(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls")))] + #[cfg(all( + feature = "tokio02", + not(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls")) + ))] InnerAsyncNetworkStream::Tokio02Tcp(_) => { let _ = tls_parameters; panic!("Trying to upgrade an AsyncNetworkStream without having enabled either the tokio02-native-tls or the tokio02-rustls-tls feature"); @@ -127,6 +183,27 @@ impl AsyncNetworkStream { self.inner = Self::upgrade_tokio02_tls(tcp_stream, tls_parameters).await?; Ok(()) } + #[cfg(all( + feature = "tokio03", + not(any(feature = "tokio03-native-tls", feature = "tokio03-rustls-tls")) + ))] + InnerAsyncNetworkStream::Tokio03Tcp(_) => { + let _ = tls_parameters; + panic!("Trying to upgrade an AsyncNetworkStream without having enabled either the tokio03-native-tls or the tokio03-rustls-tls feature"); + } + + #[cfg(any(feature = "tokio03-native-tls", feature = "tokio03-rustls-tls"))] + InnerAsyncNetworkStream::Tokio03Tcp(_) => { + // get owned TcpStream + let tcp_stream = std::mem::replace(&mut self.inner, InnerAsyncNetworkStream::None); + let tcp_stream = match tcp_stream { + InnerAsyncNetworkStream::Tokio03Tcp(tcp_stream) => tcp_stream, + _ => unreachable!(), + }; + + self.inner = Self::upgrade_tokio03_tls(tcp_stream, tls_parameters).await?; + Ok(()) + } _ => Ok(()), } } @@ -134,7 +211,7 @@ impl AsyncNetworkStream { #[allow(unused_variables)] #[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))] async fn upgrade_tokio02_tls( - tcp_stream: TcpStream, + tcp_stream: Tokio02TcpStream, mut tls_parameters: TlsParameters, ) -> Result { let domain = std::mem::take(&mut tls_parameters.domain); @@ -173,6 +250,48 @@ impl AsyncNetworkStream { } } + #[allow(unused_variables)] + #[cfg(any(feature = "tokio03-native-tls", feature = "tokio03-rustls-tls"))] + async fn upgrade_tokio03_tls( + tcp_stream: Tokio03TcpStream, + mut tls_parameters: TlsParameters, + ) -> Result { + let domain = std::mem::take(&mut tls_parameters.domain); + + match tls_parameters.connector { + #[cfg(feature = "native-tls")] + InnerTlsParameters::NativeTls(connector) => { + #[cfg(not(feature = "tokio03-native-tls"))] + panic!("built without the tokio03-native-tls feature"); + + #[cfg(feature = "tokio03-native-tls")] + return { + use tokio03_native_tls_crate::TlsConnector; + + let connector = TlsConnector::from(connector); + let stream = connector.connect(&domain, tcp_stream).await?; + Ok(InnerAsyncNetworkStream::Tokio03NativeTls(stream)) + }; + } + #[cfg(feature = "rustls-tls")] + InnerTlsParameters::RustlsTls(config) => { + #[cfg(not(feature = "tokio03-rustls-tls"))] + panic!("built without the tokio03-rustls-tls feature"); + + #[cfg(feature = "tokio03-rustls-tls")] + return { + use tokio03_rustls::{webpki::DNSNameRef, TlsConnector}; + + let domain = DNSNameRef::try_from_ascii_str(&domain)?; + + let connector = TlsConnector::from(Arc::new(config)); + let stream = connector.connect(domain, tcp_stream).await?; + Ok(InnerAsyncNetworkStream::Tokio03RustlsTls(Box::new(stream))) + }; + } + } + } + pub fn is_encrypted(&self) -> bool { match self.inner { #[cfg(feature = "tokio02")] @@ -181,6 +300,12 @@ impl AsyncNetworkStream { InnerAsyncNetworkStream::Tokio02NativeTls(_) => true, #[cfg(feature = "tokio02-rustls-tls")] InnerAsyncNetworkStream::Tokio02RustlsTls(_) => true, + #[cfg(feature = "tokio03")] + InnerAsyncNetworkStream::Tokio03Tcp(_) => false, + #[cfg(feature = "tokio03-native-tls")] + InnerAsyncNetworkStream::Tokio03NativeTls(_) => true, + #[cfg(feature = "tokio03-rustls-tls")] + InnerAsyncNetworkStream::Tokio03RustlsTls(_) => true, InnerAsyncNetworkStream::None => false, } } @@ -199,8 +324,35 @@ impl futures_io::AsyncRead for AsyncNetworkStream { InnerAsyncNetworkStream::Tokio02NativeTls(ref mut s) => Pin::new(s).poll_read(cx, buf), #[cfg(feature = "tokio02-rustls-tls")] InnerAsyncNetworkStream::Tokio02RustlsTls(ref mut s) => Pin::new(s).poll_read(cx, buf), + #[cfg(feature = "tokio03")] + InnerAsyncNetworkStream::Tokio03Tcp(ref mut s) => { + let mut b = Tokio03ReadBuf::new(buf); + match Pin::new(s).poll_read(cx, &mut b) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())), + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } + #[cfg(feature = "tokio03-native-tls")] + InnerAsyncNetworkStream::Tokio03NativeTls(ref mut s) => { + let mut b = Tokio03ReadBuf::new(buf); + match Pin::new(s).poll_read(cx, &mut b) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())), + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } + #[cfg(feature = "tokio03-rustls-tls")] + InnerAsyncNetworkStream::Tokio03RustlsTls(ref mut s) => { + let mut b = Tokio03ReadBuf::new(buf); + match Pin::new(s).poll_read(cx, &mut b) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())), + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } InnerAsyncNetworkStream::None => { - debug_assert!(false, "InnerAsyncNetworkStream::None should never be built"); + debug_assert!(false, "InnerAsyncNetworkStream::None must never be built"); Poll::Ready(Ok(0)) } } @@ -220,8 +372,14 @@ impl futures_io::AsyncWrite for AsyncNetworkStream { InnerAsyncNetworkStream::Tokio02NativeTls(ref mut s) => Pin::new(s).poll_write(cx, buf), #[cfg(feature = "tokio02-rustls-tls")] InnerAsyncNetworkStream::Tokio02RustlsTls(ref mut s) => Pin::new(s).poll_write(cx, buf), + #[cfg(feature = "tokio03")] + InnerAsyncNetworkStream::Tokio03Tcp(ref mut s) => Pin::new(s).poll_write(cx, buf), + #[cfg(feature = "tokio03-native-tls")] + InnerAsyncNetworkStream::Tokio03NativeTls(ref mut s) => Pin::new(s).poll_write(cx, buf), + #[cfg(feature = "tokio03-rustls-tls")] + InnerAsyncNetworkStream::Tokio03RustlsTls(ref mut s) => Pin::new(s).poll_write(cx, buf), InnerAsyncNetworkStream::None => { - debug_assert!(false, "InnerAsyncNetworkStream::None should never be built"); + debug_assert!(false, "InnerAsyncNetworkStream::None must never be built"); Poll::Ready(Ok(0)) } } @@ -235,8 +393,14 @@ impl futures_io::AsyncWrite for AsyncNetworkStream { InnerAsyncNetworkStream::Tokio02NativeTls(ref mut s) => Pin::new(s).poll_flush(cx), #[cfg(feature = "tokio02-rustls-tls")] InnerAsyncNetworkStream::Tokio02RustlsTls(ref mut s) => Pin::new(s).poll_flush(cx), + #[cfg(feature = "tokio03")] + InnerAsyncNetworkStream::Tokio03Tcp(ref mut s) => Pin::new(s).poll_flush(cx), + #[cfg(feature = "tokio03-native-tls")] + InnerAsyncNetworkStream::Tokio03NativeTls(ref mut s) => Pin::new(s).poll_flush(cx), + #[cfg(feature = "tokio03-rustls-tls")] + InnerAsyncNetworkStream::Tokio03RustlsTls(ref mut s) => Pin::new(s).poll_flush(cx), InnerAsyncNetworkStream::None => { - debug_assert!(false, "InnerAsyncNetworkStream::None should never be built"); + debug_assert!(false, "InnerAsyncNetworkStream::None must never be built"); Poll::Ready(Ok(())) } } diff --git a/src/transport/smtp/client/mod.rs b/src/transport/smtp/client/mod.rs index 61c493f..1391fd6 100644 --- a/src/transport/smtp/client/mod.rs +++ b/src/transport/smtp/client/mod.rs @@ -24,9 +24,9 @@ #[cfg(feature = "serde")] use std::fmt::Debug; -#[cfg(feature = "tokio02")] +#[cfg(any(feature = "tokio02", feature = "tokio03"))] pub(crate) use self::async_connection::AsyncSmtpConnection; -#[cfg(feature = "tokio02")] +#[cfg(any(feature = "tokio02", feature = "tokio03"))] pub(crate) use self::async_net::AsyncNetworkStream; use self::net::NetworkStream; #[cfg(any(feature = "native-tls", feature = "rustls-tls"))] @@ -37,9 +37,9 @@ pub use self::{ tls::{Certificate, Tls, TlsParameters, TlsParametersBuilder}, }; -#[cfg(feature = "tokio02")] +#[cfg(any(feature = "tokio02", feature = "tokio03"))] mod async_connection; -#[cfg(feature = "tokio02")] +#[cfg(any(feature = "tokio02", feature = "tokio03"))] mod async_net; mod connection; mod mock; diff --git a/src/transport/smtp/client/tls.rs b/src/transport/smtp/client/tls.rs index 498d342..0092997 100644 --- a/src/transport/smtp/client/tls.rs +++ b/src/transport/smtp/client/tls.rs @@ -126,15 +126,6 @@ impl TlsParametersBuilder { return self.build_rustls(); } - #[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))] - pub(crate) fn build_tokio02(self) -> Result { - #[cfg(feature = "tokio02-native-tls")] - return self.build_native(); - - #[cfg(not(feature = "tokio02-native-tls"))] - return self.build_rustls(); - } - /// Creates a new `TlsParameters` using native-tls with the provided configuration #[cfg(feature = "native-tls")] #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] diff --git a/src/transport/smtp/mod.rs b/src/transport/smtp/mod.rs index 5bc89c1..27deeaa 100644 --- a/src/transport/smtp/mod.rs +++ b/src/transport/smtp/mod.rs @@ -155,8 +155,12 @@ //! #[cfg(feature = "tokio02")] +pub use self::async_transport::Tokio02Connector; +#[cfg(feature = "tokio03")] +pub use self::async_transport::Tokio03Connector; +#[cfg(any(feature = "tokio02", feature = "tokio03"))] pub use self::async_transport::{ - AsyncSmtpConnector, AsyncSmtpTransport, AsyncSmtpTransportBuilder, Tokio02Connector, + AsyncSmtpConnector, AsyncSmtpTransport, AsyncSmtpTransportBuilder, }; #[cfg(feature = "r2d2")] pub use self::pool::PoolConfig; @@ -176,7 +180,7 @@ use crate::transport::smtp::{ use client::Tls; use std::time::Duration; -#[cfg(feature = "tokio02")] +#[cfg(any(feature = "tokio02", feature = "tokio03"))] mod async_transport; pub mod authentication; pub mod client;