From d932c74e46cd05348add00f85bd4a91925bb728e Mon Sep 17 00:00:00 2001 From: RoDmitry Date: Fri, 12 Apr 2024 13:57:05 +0400 Subject: [PATCH] Refactor message: send_msg(), sign() --- CHANGELOG.md | 5 +++ src/lib.rs | 4 ++ src/smtp/message.rs | 107 ++++++++++++++++++++++++++++++++------------ 3 files changed, 87 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b775c4..ee6c08f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +mail-send unreleased +================================ +- New `send_msg(&mut self, message: &Message<'x>)` +- New `sign(&mut self, signer: &DkimSigner<_>)` method on `Message` + mail-send 0.5.0 ================================ - Bump `mail-parser` dependency to 0.10 diff --git a/src/lib.rs b/src/lib.rs index e9f7377..ef67572 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,6 +178,9 @@ pub enum Error { /// STARTTLS not available MissingStartTls, + + /// Message is already signed + MessageDkimSigned, } impl std::error::Error for Error { @@ -249,6 +252,7 @@ impl Display for Error { ), Error::Timeout => write!(f, "Connection timeout"), Error::MissingStartTls => write!(f, "STARTTLS extension unavailable"), + Error::MessageDkimSigned => write!(f, "Message is already signed"), } } } diff --git a/src/smtp/message.rs b/src/smtp/message.rs index 2e730ee..d97043d 100644 --- a/src/smtp/message.rs +++ b/src/smtp/message.rs @@ -15,8 +15,8 @@ use std::{ #[cfg(feature = "builder")] use mail_builder::{ - headers::{address, HeaderType}, MessageBuilder, + headers::{HeaderType, address}, }; #[cfg(feature = "parser")] use mail_parser::{HeaderName, HeaderValue}; @@ -24,11 +24,21 @@ use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; use crate::SmtpClient; +#[cfg(feature = "dkim")] +#[derive(Debug, Default, Clone, PartialEq)] +enum Dkim { + #[default] + Unsigned, + Signed, +} + #[derive(Debug, Default, Clone)] pub struct Message<'x> { pub mail_from: Address<'x>, pub rcpt_to: Vec>, pub body: Cow<'x, [u8]>, + #[cfg(feature = "dkim")] + dkim: Dkim, } #[derive(Debug, Default, Clone)] @@ -49,10 +59,10 @@ pub struct Parameter<'x> { } impl SmtpClient { - /// Sends a message to the server. - pub async fn send<'x>(&mut self, message: impl IntoMessage<'x>) -> crate::Result<()> { + /// Send headers + #[inline] + async fn send_headers<'x>(&mut self, message: &Message<'x>) -> crate::Result<()> { // Send mail-from - let message = message.into_message()?; self.mail_from( message.mail_from.email.as_ref(), &message.mail_from.parameters, @@ -64,42 +74,38 @@ impl SmtpClient { self.rcpt_to(rcpt.email.as_ref(), &rcpt.parameters).await?; } + Ok(()) + } + + /// Convert into message and send that message to the server. + pub async fn send<'x>(&mut self, message: impl IntoMessage<'x>) -> crate::Result<()> { + // Send mail-from + let message = message.into_message()?; + + self.send_msg(&message).await + } + + /// Send a message to the server. + #[inline] + pub async fn send_msg<'x>(&mut self, message: &Message<'x>) -> crate::Result<()> { + self.send_headers(message).await?; + // Send message self.data(message.body.as_ref()).await } - /// Sends a message to the server. + /// Convert into message, sign and send that message to the server. #[cfg(feature = "dkim")] pub async fn send_signed<'x, V: mail_auth::common::crypto::SigningKey>( &mut self, message: impl IntoMessage<'x>, signer: &mail_auth::dkim::DkimSigner, ) -> crate::Result<()> { - // Send mail-from - - use mail_auth::common::headers::HeaderWriter; - let message = message.into_message()?; - self.mail_from( - message.mail_from.email.as_ref(), - &message.mail_from.parameters, - ) - .await?; - - // Send rcpt-to - for rcpt in &message.rcpt_to { - self.rcpt_to(rcpt.email.as_ref(), &rcpt.parameters).await?; - } - - // Sign message - let signature = signer - .sign(message.body.as_ref()) - .map_err(|_| crate::Error::MissingCredentials)?; - let mut signed_message = Vec::with_capacity(message.body.len() + 64); - signature.write_header(&mut signed_message); - signed_message.extend_from_slice(message.body.as_ref()); + let mut message = message.into_message()?; + message.sign(signer)?; // Send message - self.data(&signed_message).await + self.send_msg(&message).await } pub async fn write_message(&mut self, message: &[u8]) -> tokio::io::Result<()> { @@ -145,6 +151,8 @@ impl<'x> Message<'x> { mail_from: from.into(), rcpt_to: to.into_iter().map(Into::into).collect(), body: body.into(), + #[cfg(feature = "dkim")] + dkim: Dkim::Unsigned, } } @@ -154,26 +162,56 @@ impl<'x> Message<'x> { mail_from: Address::default(), rcpt_to: Vec::new(), body: Default::default(), + #[cfg(feature = "dkim")] + dkim: Dkim::Unsigned, } } /// Set the sender of the message. + #[inline] pub fn from(mut self, address: impl Into>) -> Self { self.mail_from = address.into(); self } /// Add a message recipient. + #[inline] pub fn to(mut self, address: impl Into>) -> Self { self.rcpt_to.push(address.into()); self } /// Set the message body. + #[inline] pub fn body(mut self, body: impl Into>) -> Self { self.body = body.into(); self } + + /// Sign a message + #[cfg(feature = "dkim")] + #[inline] + pub fn sign( + &mut self, + signer: &mail_auth::dkim::DkimSigner, + ) -> crate::Result<()> { + use mail_auth::common::headers::HeaderWriter; + + let signature = signer + .sign(self.body.as_ref()) + .map_err(|_| crate::Error::MissingCredentials)?; + let mut signed_message = Vec::with_capacity(self.body.len() + 64); + signature.write_header(&mut signed_message); + signed_message.extend_from_slice(self.body.as_ref()); + + if self.dkim == Dkim::Signed { + return Err(crate::Error::MessageDkimSigned); + } + self.body = signed_message.into(); + self.dkim = Dkim::Signed; + + Ok(()) + } } impl<'x> From<&'x str> for Address<'x> { @@ -204,10 +242,12 @@ impl<'x> Address<'x> { } impl<'x> Parameters<'x> { + #[inline] pub fn new() -> Self { Self { params: Vec::new() } } + #[inline] pub fn add(&mut self, param: impl Into>) -> &mut Self { self.params.push(param.into()); self @@ -215,6 +255,7 @@ impl<'x> Parameters<'x> { } impl<'x> From<&'x str> for Parameter<'x> { + #[inline] fn from(value: &'x str) -> Self { Parameter { key: value.into(), @@ -224,6 +265,7 @@ impl<'x> From<&'x str> for Parameter<'x> { } impl<'x> From<(&'x str, &'x str)> for Parameter<'x> { + #[inline] fn from(value: (&'x str, &'x str)) -> Self { Parameter { key: value.0.into(), @@ -233,6 +275,7 @@ impl<'x> From<(&'x str, &'x str)> for Parameter<'x> { } impl From<(String, String)> for Parameter<'_> { + #[inline] fn from(value: (String, String)) -> Self { Parameter { key: value.0.into(), @@ -242,6 +285,7 @@ impl From<(String, String)> for Parameter<'_> { } impl From for Parameter<'_> { + #[inline] fn from(value: String) -> Self { Parameter { key: value.into(), @@ -272,11 +316,12 @@ impl Display for Parameter<'_> { } } -pub trait IntoMessage<'x> { +pub trait IntoMessage<'x>: Sized { fn into_message(self) -> crate::Result>; } impl<'x> IntoMessage<'x> for Message<'x> { + #[inline(always)] fn into_message(self) -> crate::Result> { Ok(self) } @@ -347,6 +392,8 @@ impl<'x> IntoMessage<'x> for MessageBuilder<'_> { }) .collect(), body: self.write_to_vec()?.into(), + #[cfg(feature = "dkim")] + dkim: Dkim::Unsigned, }) } } @@ -412,6 +459,8 @@ impl<'x> IntoMessage<'x> for mail_parser::Message<'x> { }) .collect(), body: self.raw_message, + #[cfg(feature = "dkim")] + dkim: Dkim::Unsigned, }) } }