diff --git a/src/address/envelope.rs b/src/address/envelope.rs new file mode 100644 index 0000000..3ea3341 --- /dev/null +++ b/src/address/envelope.rs @@ -0,0 +1,134 @@ +#[cfg(feature = "builder")] +use std::convert::TryFrom; + +use super::Address; +#[cfg(feature = "builder")] +use crate::message::header::{self, Headers}; +#[cfg(feature = "builder")] +use crate::message::{Mailbox, Mailboxes}; +use crate::Error; + +/// Simple email envelope representation +/// +/// We only accept mailboxes, and do not support source routes (as per RFC). +#[derive(PartialEq, Eq, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Envelope { + /// The envelope recipients' addresses + /// + /// This can not be empty. + forward_path: Vec
, + /// The envelope sender address + reverse_path: Option
, +} + +impl Envelope { + /// Creates a new envelope, which may fail if `to` is empty. + /// + /// # Examples + /// + /// ``` + /// use std::str::FromStr; + /// # use lettre::Address; + /// # use lettre::address::Envelope; + /// + /// let sender = Address::from_str("from@email.com").unwrap(); + /// let recipients = vec![Address::from_str("to@email.com").unwrap()]; + /// + /// let envelope = Envelope::new(Some(sender), recipients); + /// ``` + /// + /// # Errors + /// + /// If `to` has no elements in it. + pub fn new(from: Option
, to: Vec
) -> Result { + if to.is_empty() { + return Err(Error::MissingTo); + } + Ok(Envelope { + forward_path: to, + reverse_path: from, + }) + } + + /// Gets the destination addresses of the envelope. + /// + /// # Examples + /// + /// ``` + /// use std::str::FromStr; + /// # use lettre::Address; + /// # use lettre::address::Envelope; + /// + /// let sender = Address::from_str("from@email.com").unwrap(); + /// let recipients = vec![Address::from_str("to@email.com").unwrap()]; + /// + /// let envelope = Envelope::new(Some(sender), recipients.clone()).unwrap(); + /// assert_eq!(envelope.to(), recipients.as_slice()); + /// ``` + pub fn to(&self) -> &[Address] { + self.forward_path.as_slice() + } + + /// Gets the sender of the envelope. + /// + /// # Examples + /// + /// ``` + /// use std::str::FromStr; + /// # use lettre::Address; + /// # use lettre::address::Envelope; + /// + /// let sender = Address::from_str("from@email.com").unwrap(); + /// let recipients = vec![Address::from_str("to@email.com").unwrap()]; + /// + /// let envelope = Envelope::new(Some(sender), recipients.clone()).unwrap(); + /// assert!(envelope.from().is_some()); + /// + /// let senderless = Envelope::new(None, recipients.clone()).unwrap(); + /// assert!(senderless.from().is_none()); + /// ``` + pub fn from(&self) -> Option<&Address> { + self.reverse_path.as_ref() + } +} + +#[cfg(feature = "builder")] +impl TryFrom<&Headers> for Envelope { + type Error = Error; + + fn try_from(headers: &Headers) -> Result { + let from = match headers.get::() { + // If there is a Sender, use it + Some(header::Sender(a)) => Some(a.email.clone()), + // ... else try From + None => match headers.get::() { + Some(header::From(a)) => { + let from: Vec = a.clone().into(); + if from.len() > 1 { + return Err(Error::TooManyFrom); + } + Some(from[0].email.clone()) + } + None => None, + }, + }; + + fn add_addresses_from_mailboxes( + addresses: &mut Vec
, + mailboxes: Option<&Mailboxes>, + ) { + if let Some(mailboxes) = mailboxes { + for mailbox in mailboxes.iter() { + addresses.push(mailbox.email.clone()); + } + } + } + let mut to = vec![]; + add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0)); + add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0)); + add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0)); + + Self::new(from, to) + } +} diff --git a/src/address/mod.rs b/src/address/mod.rs new file mode 100644 index 0000000..c53a75a --- /dev/null +++ b/src/address/mod.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "serde")] +mod serde; + +mod envelope; +mod types; + +pub use self::envelope::Envelope; +pub use self::types::{Address, AddressError}; diff --git a/src/address/serde.rs b/src/address/serde.rs new file mode 100644 index 0000000..342e332 --- /dev/null +++ b/src/address/serde.rs @@ -0,0 +1,112 @@ +use std::fmt::{Formatter, Result as FmtResult}; + +use serde::{ + de::{Deserializer, Error as DeError, MapAccess, Visitor}, + ser::Serializer, + Deserialize, Serialize, +}; + +use super::Address; + +impl Serialize for Address { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.as_ref()) + } +} + +impl<'de> Deserialize<'de> for Address { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + enum Field { + User, + Domain, + }; + + const FIELDS: &[&str] = &["user", "domain"]; + + impl<'de> Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult { + formatter.write_str("'user' or 'domain'") + } + + fn visit_str(self, value: &str) -> Result + where + E: DeError, + { + match value { + "user" => Ok(Field::User), + "domain" => Ok(Field::Domain), + _ => Err(DeError::unknown_field(value, FIELDS)), + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct AddressVisitor; + + impl<'de> Visitor<'de> for AddressVisitor { + type Value = Address; + + fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult { + formatter.write_str("email address string or object") + } + + fn visit_str(self, s: &str) -> Result + where + E: DeError, + { + s.parse().map_err(DeError::custom) + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut user = None; + let mut domain = None; + while let Some(key) = map.next_key()? { + match key { + Field::User => { + if user.is_some() { + return Err(DeError::duplicate_field("user")); + } + let val = map.next_value()?; + Address::check_user(val).map_err(DeError::custom)?; + user = Some(val); + } + Field::Domain => { + if domain.is_some() { + return Err(DeError::duplicate_field("domain")); + } + let val = map.next_value()?; + Address::check_domain(val).map_err(DeError::custom)?; + domain = Some(val); + } + } + } + let user: &str = user.ok_or_else(|| DeError::missing_field("user"))?; + let domain: &str = domain.ok_or_else(|| DeError::missing_field("domain"))?; + Ok(Address::new(user, domain).unwrap()) + } + } + + deserializer.deserialize_any(AddressVisitor) + } +} diff --git a/src/address.rs b/src/address/types.rs similarity index 59% rename from src/address.rs rename to src/address/types.rs index f4242c4..81afb96 100644 --- a/src/address.rs +++ b/src/address/types.rs @@ -122,7 +122,7 @@ impl Address { &self.serialized[self.at_start + 1..] } - fn check_user(user: &str) -> Result<(), AddressError> { + pub(super) fn check_user(user: &str) -> Result<(), AddressError> { if USER_RE.is_match(user) { Ok(()) } else { @@ -130,7 +130,7 @@ impl Address { } } - fn check_domain(domain: &str) -> Result<(), AddressError> { + pub(super) fn check_domain(domain: &str) -> Result<(), AddressError> { Address::check_domain_ascii(domain).or_else(|_| { domain_to_ascii(domain) .map_err(|_| AddressError::InvalidDomain) @@ -213,120 +213,6 @@ impl Display for AddressError { } } -#[cfg(feature = "serde")] -mod serde { - use crate::address::Address; - use serde::{ - de::{Deserializer, Error as DeError, MapAccess, Visitor}, - ser::Serializer, - Deserialize, Serialize, - }; - use std::fmt::{Formatter, Result as FmtResult}; - - impl Serialize for Address { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.as_ref()) - } - } - - impl<'de> Deserialize<'de> for Address { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - enum Field { - User, - Domain, - }; - - const FIELDS: &[&str] = &["user", "domain"]; - - impl<'de> Deserialize<'de> for Field { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct FieldVisitor; - - impl<'de> Visitor<'de> for FieldVisitor { - type Value = Field; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult { - formatter.write_str("'user' or 'domain'") - } - - fn visit_str(self, value: &str) -> Result - where - E: DeError, - { - match value { - "user" => Ok(Field::User), - "domain" => Ok(Field::Domain), - _ => Err(DeError::unknown_field(value, FIELDS)), - } - } - } - - deserializer.deserialize_identifier(FieldVisitor) - } - } - - struct AddressVisitor; - - impl<'de> Visitor<'de> for AddressVisitor { - type Value = Address; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult { - formatter.write_str("email address string or object") - } - - fn visit_str(self, s: &str) -> Result - where - E: DeError, - { - s.parse().map_err(DeError::custom) - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut user = None; - let mut domain = None; - while let Some(key) = map.next_key()? { - match key { - Field::User => { - if user.is_some() { - return Err(DeError::duplicate_field("user")); - } - let val = map.next_value()?; - Address::check_user(val).map_err(DeError::custom)?; - user = Some(val); - } - Field::Domain => { - if domain.is_some() { - return Err(DeError::duplicate_field("domain")); - } - let val = map.next_value()?; - Address::check_domain(val).map_err(DeError::custom)?; - domain = Some(val); - } - } - } - let user: &str = user.ok_or_else(|| DeError::missing_field("user"))?; - let domain: &str = domain.ok_or_else(|| DeError::missing_field("domain"))?; - Ok(Address::new(user, domain).unwrap()) - } - } - - deserializer.deserialize_any(AddressVisitor) - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 92fa0f6..f74de22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,7 @@ pub mod transport; #[macro_use] extern crate hyperx; +use crate::address::Envelope; use crate::error::Error; #[cfg(feature = "builder")] pub use crate::message::{ @@ -78,130 +79,6 @@ pub use crate::transport::smtp::Tokio03Connector; pub use crate::{address::Address, transport::stub::StubTransport}; #[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio03"))] use async_trait::async_trait; -#[cfg(feature = "builder")] -use std::convert::TryFrom; - -/// Simple email envelope representation -/// -/// We only accept mailboxes, and do not support source routes (as per RFC). -#[derive(PartialEq, Eq, Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Envelope { - /// The envelope recipients' addresses - /// - /// This can not be empty. - forward_path: Vec
, - /// The envelope sender address - reverse_path: Option
, -} - -impl Envelope { - /// Creates a new envelope, which may fail if `to` is empty. - /// - /// # Examples - /// - /// ``` - /// use std::str::FromStr; - /// # use lettre::{Address, Envelope}; - /// - /// let sender = Address::from_str("from@email.com").unwrap(); - /// let recipients = vec![Address::from_str("to@email.com").unwrap()]; - /// - /// let envelope = Envelope::new(Some(sender), recipients); - /// ``` - /// - /// # Errors - /// - /// If `to` has no elements in it. - pub fn new(from: Option
, to: Vec
) -> Result { - if to.is_empty() { - return Err(Error::MissingTo); - } - Ok(Envelope { - forward_path: to, - reverse_path: from, - }) - } - - /// Gets the destination addresses of the envelope. - /// - /// # Examples - /// - /// ``` - /// use std::str::FromStr; - /// # use lettre::{Address, Envelope}; - /// - /// let sender = Address::from_str("from@email.com").unwrap(); - /// let recipients = vec![Address::from_str("to@email.com").unwrap()]; - /// - /// let envelope = Envelope::new(Some(sender), recipients.clone()).unwrap(); - /// assert_eq!(envelope.to(), recipients.as_slice()); - /// ``` - pub fn to(&self) -> &[Address] { - self.forward_path.as_slice() - } - - /// Gets the sender of the envelope. - /// - /// # Examples - /// - /// ``` - /// use std::str::FromStr; - /// # use lettre::{Address, Envelope}; - /// - /// let sender = Address::from_str("from@email.com").unwrap(); - /// let recipients = vec![Address::from_str("to@email.com").unwrap()]; - /// - /// let envelope = Envelope::new(Some(sender), recipients.clone()).unwrap(); - /// assert!(envelope.from().is_some()); - /// - /// let senderless = Envelope::new(None, recipients.clone()).unwrap(); - /// assert!(senderless.from().is_none()); - /// ``` - pub fn from(&self) -> Option<&Address> { - self.reverse_path.as_ref() - } -} - -#[cfg(feature = "builder")] -impl TryFrom<&Headers> for Envelope { - type Error = Error; - - fn try_from(headers: &Headers) -> Result { - let from = match headers.get::() { - // If there is a Sender, use it - Some(header::Sender(a)) => Some(a.email.clone()), - // ... else try From - None => match headers.get::() { - Some(header::From(a)) => { - let from: Vec = a.clone().into(); - if from.len() > 1 { - return Err(Error::TooManyFrom); - } - Some(from[0].email.clone()) - } - None => None, - }, - }; - - fn add_addresses_from_mailboxes( - addresses: &mut Vec
, - mailboxes: Option<&Mailboxes>, - ) { - if let Some(mailboxes) = mailboxes { - for mailbox in mailboxes.iter() { - addresses.push(mailbox.email.clone()); - } - } - } - let mut to = vec![]; - add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0)); - add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0)); - add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0)); - - Self::new(from, to) - } -} /// Blocking Transport method for emails pub trait Transport { @@ -295,6 +172,7 @@ mod test { use super::*; use crate::message::{header, Mailbox, Mailboxes}; use hyperx::header::Headers; + use std::convert::TryFrom; #[test] fn envelope_from_headers() { diff --git a/src/message/mod.rs b/src/message/mod.rs index 8101468..13ba5e4 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -186,8 +186,9 @@ mod mimebody; mod utf8_b; use crate::{ + address::Envelope, message::header::{EmailDate, Header, Headers, MailboxesHeader}, - Envelope, Error as EmailError, + Error as EmailError, }; use std::{convert::TryFrom, time::SystemTime}; use uuid::Uuid; diff --git a/src/transport/file/mod.rs b/src/transport/file/mod.rs index 40cfcc1..8218ebf 100644 --- a/src/transport/file/mod.rs +++ b/src/transport/file/mod.rs @@ -6,7 +6,7 @@ //! //! ```rust //! use std::env::temp_dir; -//! use lettre::{Transport, Envelope, Message, FileTransport}; +//! use lettre::{Transport, Message, FileTransport}; //! //! // Write to the local temp directory //! let sender = FileTransport::new(temp_dir()); @@ -28,7 +28,7 @@ //! # #[cfg(feature = "tokio02")] //! # async fn run() { //! use std::env::temp_dir; -//! use lettre::{Tokio02Transport, Envelope, Message, FileTransport}; +//! use lettre::{Tokio02Transport, Message, FileTransport}; //! //! // Write to the local temp directory //! let sender = FileTransport::new(temp_dir()); @@ -51,7 +51,7 @@ //! # #[cfg(feature = "async-std1")] //! # async fn run() { //! use std::env::temp_dir; -//! use lettre::{AsyncStd1Transport, Envelope, Message, FileTransport}; +//! use lettre::{AsyncStd1Transport, Message, FileTransport}; //! //! // Write to the local temp directory //! let sender = FileTransport::new(temp_dir()); @@ -86,13 +86,14 @@ //! ``` pub use self::error::Error; +use crate::address::Envelope; #[cfg(feature = "async-std1")] use crate::AsyncStd1Transport; #[cfg(feature = "tokio02")] use crate::Tokio02Transport; #[cfg(feature = "tokio03")] use crate::Tokio03Transport; -use crate::{Envelope, Transport}; +use crate::Transport; #[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio03"))] use async_trait::async_trait; use std::{ diff --git a/src/transport/sendmail/mod.rs b/src/transport/sendmail/mod.rs index 94838d5..855a8a8 100644 --- a/src/transport/sendmail/mod.rs +++ b/src/transport/sendmail/mod.rs @@ -3,7 +3,7 @@ //! ## Sync example //! //! ```rust -//! use lettre::{Message, Envelope, Transport, SendmailTransport}; +//! use lettre::{Message, Transport, SendmailTransport}; //! //! let email = Message::builder() //! .from("NoBody ".parse().unwrap()) @@ -23,7 +23,7 @@ //! ```rust //! # #[cfg(feature = "tokio02")] //! # async fn run() { -//! use lettre::{Message, Envelope, Tokio02Transport, SendmailTransport}; +//! use lettre::{Message, Tokio02Transport, SendmailTransport}; //! //! let email = Message::builder() //! .from("NoBody ".parse().unwrap()) @@ -44,7 +44,7 @@ //!```rust //! # #[cfg(feature = "async-std1")] //! # async fn run() { -//! use lettre::{Message, Envelope, AsyncStd1Transport, SendmailTransport}; +//! use lettre::{Message, AsyncStd1Transport, SendmailTransport}; //! //! let email = Message::builder() //! .from("NoBody ".parse().unwrap()) @@ -61,13 +61,14 @@ //! ``` pub use self::error::Error; +use crate::address::Envelope; #[cfg(feature = "async-std1")] use crate::AsyncStd1Transport; #[cfg(feature = "tokio02")] use crate::Tokio02Transport; #[cfg(feature = "tokio03")] use crate::Tokio03Transport; -use crate::{Envelope, Transport}; +use crate::Transport; #[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio03"))] use async_trait::async_trait; use std::{ diff --git a/src/transport/smtp/client/connection.rs b/src/transport/smtp/client/connection.rs index 12bd63a..10b2b8d 100644 --- a/src/transport/smtp/client/connection.rs +++ b/src/transport/smtp/client/connection.rs @@ -6,15 +6,13 @@ use std::{ }; use super::{ClientCodec, NetworkStream, TlsParameters}; -use crate::{ - transport::smtp::{ - authentication::{Credentials, Mechanism}, - commands::*, - error::Error, - extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo}, - response::{parse_response, Response}, - }, - Envelope, +use crate::address::Envelope; +use crate::transport::smtp::{ + authentication::{Credentials, Mechanism}, + commands::*, + error::Error, + extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo}, + response::{parse_response, Response}, }; #[cfg(feature = "tracing")] diff --git a/src/transport/smtp/transport.rs b/src/transport/smtp/transport.rs index 1e1ee05..7c56ce4 100644 --- a/src/transport/smtp/transport.rs +++ b/src/transport/smtp/transport.rs @@ -8,7 +8,8 @@ use super::PoolConfig; use super::{ClientId, Credentials, Error, Mechanism, Response, SmtpConnection, SmtpInfo}; #[cfg(any(feature = "native-tls", feature = "rustls-tls"))] use super::{Tls, TlsParameters, SUBMISSIONS_PORT, SUBMISSION_PORT}; -use crate::{Envelope, Transport}; +use crate::address::Envelope; +use crate::Transport; #[allow(missing_debug_implementations)] #[derive(Clone)] diff --git a/src/transport/stub/mod.rs b/src/transport/stub/mod.rs index 24bbf4e..92359ef 100644 --- a/src/transport/stub/mod.rs +++ b/src/transport/stub/mod.rs @@ -7,7 +7,7 @@ //! testing purposes. //! //! ```rust -//! use lettre::{Message, Envelope, Transport, StubTransport}; +//! use lettre::{Message, Transport, StubTransport}; //! //! let email = Message::builder() //! .from("NoBody ".parse().unwrap()) @@ -22,11 +22,12 @@ //! assert!(result.is_ok()); //! ``` +use crate::address::Envelope; #[cfg(feature = "async-std1")] use crate::AsyncStd1Transport; #[cfg(feature = "tokio02")] use crate::Tokio02Transport; -use crate::{Envelope, Transport}; +use crate::Transport; #[cfg(any(feature = "async-std1", feature = "tokio02"))] use async_trait::async_trait; use std::{error::Error as StdError, fmt}; diff --git a/tests/transport_smtp_pool.rs b/tests/transport_smtp_pool.rs index b12b758..18154b5 100644 --- a/tests/transport_smtp_pool.rs +++ b/tests/transport_smtp_pool.rs @@ -1,6 +1,8 @@ #[cfg(all(test, feature = "smtp-transport", feature = "r2d2"))] mod test { - use lettre::{Envelope, SmtpTransport, Transport}; + use lettre::address::Envelope; + use lettre::{SmtpTransport, Transport}; + use std::{sync::mpsc, thread}; fn envelope() -> Envelope {