From d4df9a2965eabbbb050a8be1a88c454c67087577 Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Sat, 27 Feb 2021 17:23:48 +0100 Subject: [PATCH] feat(transport): Add SMTPUTF8 handling (#540) --- src/address/envelope.rs | 8 ++++++ src/address/types.rs | 5 ++++ src/transport/smtp/client/async_connection.rs | 25 ++++++++++++++++++- src/transport/smtp/client/connection.rs | 25 ++++++++++++++++++- 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/address/envelope.rs b/src/address/envelope.rs index 84c5ba1..27ed35c 100644 --- a/src/address/envelope.rs +++ b/src/address/envelope.rs @@ -103,6 +103,14 @@ impl Envelope { pub fn from(&self) -> Option<&Address> { self.reverse_path.as_ref() } + + /// Check if any of the addresses in the envelope contains non-ascii chars + pub(crate) fn has_non_ascii_addresses(&self) -> bool { + self.reverse_path + .iter() + .chain(self.forward_path.iter()) + .any(|a| !a.is_ascii()) + } } #[cfg(feature = "builder")] diff --git a/src/address/types.rs b/src/address/types.rs index 481ea2d..99e3e2e 100644 --- a/src/address/types.rs +++ b/src/address/types.rs @@ -173,6 +173,11 @@ impl Address { Err(AddressError::InvalidDomain) } + + /// Check if the address contains non-ascii chars + pub(super) fn is_ascii(&self) -> bool { + self.serialized.is_ascii() + } } impl Display for Address { diff --git a/src/transport/smtp/client/async_connection.rs b/src/transport/smtp/client/async_connection.rs index 7b8678b..b4bf04c 100644 --- a/src/transport/smtp/client/async_connection.rs +++ b/src/transport/smtp/client/async_connection.rs @@ -112,9 +112,32 @@ impl AsyncSmtpConnection { // Mail let mut mail_options = vec![]; - if self.server_info().supports_feature(Extension::EightBitMime) { + // Internationalization handling + // + // * 8BITMIME: https://tools.ietf.org/html/rfc6152 + // * SMTPUTF8: https://tools.ietf.org/html/rfc653 + + // Check for non-ascii addresses and use the SMTPUTF8 option if any. + if envelope.has_non_ascii_addresses() { + if !self.server_info().supports_feature(Extension::SmtpUtfEight) { + // don't try to send non-ascii addresses (per RFC) + return Err(Error::Client( + "Envelope contains non-ascii chars but server does not support SMTPUTF8", + )); + } + mail_options.push(MailParameter::SmtpUtfEight); + } + + // Check for non-ascii content in message + if !email.is_ascii() { + if !self.server_info().supports_feature(Extension::EightBitMime) { + return Err(Error::Client( + "Message contains non-ascii chars but server does not support 8BITMIME", + )); + } mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime)); } + try_smtp!( self.command(Mail::new(envelope.from().cloned(), mail_options)) .await, diff --git a/src/transport/smtp/client/connection.rs b/src/transport/smtp/client/connection.rs index b19ef2b..362fa99 100644 --- a/src/transport/smtp/client/connection.rs +++ b/src/transport/smtp/client/connection.rs @@ -82,9 +82,32 @@ impl SmtpConnection { // Mail let mut mail_options = vec![]; - if self.server_info().supports_feature(Extension::EightBitMime) { + // Internationalization handling + // + // * 8BITMIME: https://tools.ietf.org/html/rfc6152 + // * SMTPUTF8: https://tools.ietf.org/html/rfc653 + + // Check for non-ascii addresses and use the SMTPUTF8 option if any. + if envelope.has_non_ascii_addresses() { + if !self.server_info().supports_feature(Extension::SmtpUtfEight) { + // don't try to send non-ascii addresses (per RFC) + return Err(Error::Client( + "Envelope contains non-ascii chars but server does not support SMTPUTF8", + )); + } + mail_options.push(MailParameter::SmtpUtfEight); + } + + // Check for non-ascii content in message + if !email.is_ascii() { + if !self.server_info().supports_feature(Extension::EightBitMime) { + return Err(Error::Client( + "Message contains non-ascii chars but server does not support 8BITMIME", + )); + } mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime)); } + try_smtp!( self.command(Mail::new(envelope.from().cloned(), mail_options)), self