From 12794d36b3815f97b56784606537113c9272e62c Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Mon, 17 Jul 2017 11:30:38 +0200 Subject: [PATCH] feat(transport): Use command types for mail and rcpt --- lettre/Cargo.toml | 1 - lettre/examples/smtp.rs | 10 ++--- lettre/src/file/mod.rs | 18 ++++---- lettre/src/lib.rs | 48 ++++++++++++++------- lettre/src/sendmail/mod.rs | 20 ++++++--- lettre/src/smtp/client/mod.rs | 13 ------ lettre/src/smtp/commands.rs | 4 +- lettre/src/smtp/extension.rs | 3 ++ lettre/src/smtp/mod.rs | 69 ++++++++++++++++++------------ lettre/src/stub/mod.rs | 18 ++++---- lettre/tests/transport_file.rs | 10 ++--- lettre/tests/transport_sendmail.rs | 10 ++--- lettre/tests/transport_smtp.rs | 10 ++--- lettre/tests/transport_stub.rs | 12 +++--- lettre_email/src/email.rs | 20 ++++----- 15 files changed, 147 insertions(+), 119 deletions(-) diff --git a/lettre/Cargo.toml b/lettre/Cargo.toml index bc5b0a2..b662848 100644 --- a/lettre/Cargo.toml +++ b/lettre/Cargo.toml @@ -24,7 +24,6 @@ rust-crypto = "^0.2" serde = "^1.0" serde_json = "^1.0" serde_derive = "^1.0" -emailaddress = "^0.4" [dev-dependencies] env_logger = "^0.4" diff --git a/lettre/examples/smtp.rs b/lettre/examples/smtp.rs index e138006..232d0ae 100644 --- a/lettre/examples/smtp.rs +++ b/lettre/examples/smtp.rs @@ -1,14 +1,14 @@ extern crate lettre; -use lettre::{EmailTransport, SimpleSendableEmail}; +use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail}; use lettre::smtp::{SecurityLevel, SmtpTransportBuilder}; fn main() { let email = SimpleSendableEmail::new( - "user@localhost", - vec!["root@localhost"], - "file_id", - "Hello ß☺ example", + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "file_id".to_string(), + "Hello ß☺ example".to_string(), ); // Open a local connection on port 25 diff --git a/lettre/src/file/mod.rs b/lettre/src/file/mod.rs index 0780b65..04ce89d 100644 --- a/lettre/src/file/mod.rs +++ b/lettre/src/file/mod.rs @@ -6,15 +6,15 @@ //! use std::env::temp_dir; //! //! use lettre::file::FileEmailTransport; -//! use lettre::{SimpleSendableEmail, EmailTransport}; +//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress}; //! //! // Write to the local temp directory //! let mut sender = FileEmailTransport::new(temp_dir()); //! let email = SimpleSendableEmail::new( -//! "user@localhost", -//! vec!["root@localhost"], -//! "message_id", -//! "Hello world" +//! EmailAddress::new("user@localhost".to_string()), +//! vec![EmailAddress::new("root@localhost".to_string())], +//! "message_id".to_string(), +//! "Hello world".to_string(), //! ); //! //! let result = sender.send(email); @@ -68,10 +68,10 @@ impl EmailTransport for FileEmailTransport { let mut f = try!(File::create(file.as_path())); let simple_email = SimpleSendableEmail::new( - &email.from(), - email.to().iter().map(String::as_str).collect(), - &email.message_id(), - &email.message(), + email.from().clone(), + email.to().clone(), + email.message_id().clone(), + email.message(), ); try!(f.write_all( diff --git a/lettre/src/lib.rs b/lettre/src/lib.rs index c6e7e1c..25ba59a 100644 --- a/lettre/src/lib.rs +++ b/lettre/src/lib.rs @@ -13,7 +13,6 @@ extern crate hex; extern crate crypto; extern crate bufstream; extern crate native_tls; -extern crate emailaddress; extern crate serde_json; extern crate serde; #[macro_use] @@ -23,13 +22,32 @@ pub mod smtp; pub mod sendmail; pub mod stub; pub mod file; +use std::fmt; +use std::fmt::{Display, Formatter}; + +/// Email address +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct EmailAddress(pub String); + +impl Display for EmailAddress { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str(&self.0) + } +} + +impl EmailAddress { + /// Creates a new email address + pub fn new(address: String) -> EmailAddress { + EmailAddress(address) + } +} /// Email sendable by an SMTP client pub trait SendableEmail { /// To - fn to(&self) -> Vec; + fn to(&self) -> Vec; /// From - fn from(&self) -> String; + fn from(&self) -> EmailAddress; /// Message ID, used for logging fn message_id(&self) -> String; /// Message content @@ -48,9 +66,9 @@ pub trait EmailTransport { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SimpleSendableEmail { /// To - to: Vec, + to: Vec, /// From - from: String, + from: EmailAddress, /// Message ID message_id: String, /// Message content @@ -60,26 +78,26 @@ pub struct SimpleSendableEmail { impl SimpleSendableEmail { /// Returns a new email pub fn new( - from_address: &str, - to_addresses: Vec<&str>, - message_id: &str, - message: &str, + from_address: EmailAddress, + to_addresses: Vec, + message_id: String, + message: String, ) -> SimpleSendableEmail { SimpleSendableEmail { - from: from_address.to_string(), - to: to_addresses.iter().map(|s| s.to_string()).collect(), - message_id: message_id.to_string(), - message: message.to_string(), + from: from_address, + to: to_addresses, + message_id: message_id, + message: message, } } } impl SendableEmail for SimpleSendableEmail { - fn to(&self) -> Vec { + fn to(&self) -> Vec { self.to.clone() } - fn from(&self) -> String { + fn from(&self) -> EmailAddress { self.from.clone() } diff --git a/lettre/src/sendmail/mod.rs b/lettre/src/sendmail/mod.rs index 9bfc2a9..e42afdd 100644 --- a/lettre/src/sendmail/mod.rs +++ b/lettre/src/sendmail/mod.rs @@ -2,13 +2,13 @@ //! //! ```rust //! use lettre::sendmail::SendmailTransport; -//! use lettre::{SimpleSendableEmail, EmailTransport}; +//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress}; //! //! let email = SimpleSendableEmail::new( -//! "user@localhost", -//! vec!["root@localhost"], -//! "message_id", -//! "Hello world" +//! EmailAddress::new("user@localhost".to_string()), +//! vec![EmailAddress::new("root@localhost".to_string())], +//! "message_id".to_string(), +//! "Hello world".to_string(), //! ); //! //! let mut sender = SendmailTransport::new(); @@ -45,9 +45,17 @@ impl SendmailTransport { impl EmailTransport for SendmailTransport { fn send(&mut self, email: T) -> SendmailResult { // Spawn the sendmail command + let to_addresses: Vec = email.to().iter().map(|x| x.to_string()).collect(); let mut process = try!( Command::new(&self.command) - .args(&["-i", "-f", &email.from(), &email.to().join(" ")]) + .args( + &[ + "-i", + "-f", + &email.from().to_string(), + &to_addresses.join(" "), + ], + ) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() diff --git a/lettre/src/smtp/client/mod.rs b/lettre/src/smtp/client/mod.rs index f31354f..cba5c90 100644 --- a/lettre/src/smtp/client/mod.rs +++ b/lettre/src/smtp/client/mod.rs @@ -151,19 +151,6 @@ impl Client { self.send_server(&command.to_string(), "") } - /// Sends a MAIL command - pub fn mail(&mut self, address: &str, options: Option<&str>) -> SmtpResult { - match options { - Some(options) => self.command(&format!("MAIL FROM:<{}> {}", address, options)), - None => self.command(&format!("MAIL FROM:<{}>", address)), - } - } - - /// Sends a RCPT command - pub fn rcpt(&mut self, address: &str) -> SmtpResult { - self.command(&format!("RCPT TO:<{}>", address)) - } - /// Sends an AUTH command with the given mechanism, and handles challenge if needed pub fn auth(&mut self, mechanism: Mechanism, credentials: &Credentials) -> SmtpResult { diff --git a/lettre/src/smtp/commands.rs b/lettre/src/smtp/commands.rs index 6d21eba..48a74c8 100644 --- a/lettre/src/smtp/commands.rs +++ b/lettre/src/smtp/commands.rs @@ -1,7 +1,7 @@ //! SMTP commands +use EmailAddress; use base64; -use emailaddress::EmailAddress; use smtp::CRLF; use smtp::authentication::{Credentials, Mechanism}; use smtp::error::Error; @@ -323,7 +323,7 @@ mod test { #[test] fn test_display() { let id = ClientId::Domain("localhost".to_string()); - let email = EmailAddress::new("test@example.com").unwrap(); + let email = EmailAddress::new("test@example.com".to_string()); let mail_parameter = MailParameter::Other { keyword: "TEST".to_string(), value: Some("value".to_string()), diff --git a/lettre/src/smtp/extension.rs b/lettre/src/smtp/extension.rs index 4b82e98..0fccee4 100644 --- a/lettre/src/smtp/extension.rs +++ b/lettre/src/smtp/extension.rs @@ -165,6 +165,8 @@ pub enum MailParameter { Body(MailBodyParameter), /// `SIZE` parameter Size(usize), + /// `SMTPUTF8` parameter + SmtpUtfEight, /// Custom parameter Other { /// Parameter keyword @@ -179,6 +181,7 @@ impl Display for MailParameter { match *self { MailParameter::Body(ref value) => write!(f, "BODY={}", value), MailParameter::Size(size) => write!(f, "SIZE={}", size), + MailParameter::SmtpUtfEight => f.write_str("SMTPUTF8"), MailParameter::Other { ref keyword, value: Some(ref value), diff --git a/lettre/src/smtp/mod.rs b/lettre/src/smtp/mod.rs index cc3a229..5370321 100644 --- a/lettre/src/smtp/mod.rs +++ b/lettre/src/smtp/mod.rs @@ -18,15 +18,15 @@ //! This is the most basic example of usage: //! //! ```rust,no_run -//! use lettre::{SimpleSendableEmail, EmailTransport}; +//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress}; //! use lettre::smtp::SmtpTransportBuilder; //! use lettre::smtp::SecurityLevel; //! //! let email = SimpleSendableEmail::new( -//! "user@localhost", -//! vec!["root@localhost"], -//! "message_id", -//! "Hello world" +//! EmailAddress::new("user@localhost".to_string()), +//! vec![EmailAddress::new("root@localhost".to_string())], +//! "message_id".to_string(), +//! "Hello world".to_string(), //! ); //! //! // Open a local connection on port 25 @@ -45,14 +45,14 @@ //! SmtpTransportBuilder}; //! use lettre::smtp::authentication::{Credentials, Mechanism}; //! use lettre::smtp::SUBMISSION_PORT; -//! use lettre::{SimpleSendableEmail, EmailTransport}; +//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress}; //! use lettre::smtp::extension::ClientId; //! //! let email = SimpleSendableEmail::new( -//! "user@localhost", -//! vec!["root@localhost"], -//! "message_id", -//! "Hello world" +//! EmailAddress::new("user@localhost".to_string()), +//! vec![EmailAddress::new("root@localhost".to_string())], +//! "message_id".to_string(), +//! "Hello world".to_string(), //! ); //! //! // Connect to a remote server on a custom port @@ -89,6 +89,7 @@ //! error handling: //! //! ```rust +//! use lettre::EmailAddress; //! use lettre::smtp::SMTP_PORT; //! use lettre::smtp::client::Client; //! use lettre::smtp::client::net::NetworkStream; @@ -98,8 +99,8 @@ //! let mut email_client: Client = Client::new(); //! let _ = email_client.connect(&("localhost", SMTP_PORT), None); //! let _ = email_client.smtp_command(EhloCommand::new(ClientId::new("my_hostname".to_string()))); -//! let _ = email_client.mail("user@example.com", None); -//! let _ = email_client.rcpt("user@example.org"); +//! let _ = email_client.smtp_command(MailCommand::new(Some(EmailAddress::new("user@example.com".to_string())), vec![])); +//! let _ = email_client.smtp_command(RcptCommand::new(EmailAddress::new("user@example.org".to_string()), vec![])); //! let _ = email_client.smtp_command(DataCommand); //! let _ = email_client.message("Test email"); //! let _ = email_client.smtp_command(QuitCommand); @@ -113,7 +114,7 @@ use smtp::authentication::{Credentials, Mechanism}; use smtp::client::Client; use smtp::commands::*; use smtp::error::{Error, SmtpResult}; -use smtp::extension::{ClientId, Extension, ServerInfo}; +use smtp::extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo}; use std::net::{SocketAddr, ToSocketAddrs}; use std::time::Duration; @@ -492,27 +493,41 @@ impl EmailTransport for SmtpTransport { } // Mail - let mail_options = match ( - self.server_info.as_ref().unwrap().supports_feature( - Extension::EightBitMime, - ), - self.server_info.as_ref().unwrap().supports_feature( - Extension::SmtpUtfEight, - ), - ) { - (true, true) => Some("BODY=8BITMIME SMTPUTF8"), - (true, false) => Some("BODY=8BITMIME"), - (false, _) => None, - }; + let mut mail_options = vec![]; - try_smtp!(self.client.mail(&email.from(), mail_options), self); + if self.server_info.as_ref().unwrap().supports_feature( + Extension::EightBitMime, + ) + { + mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime)); + } + + if self.server_info.as_ref().unwrap().supports_feature( + Extension::SmtpUtfEight, + ) + { + mail_options.push(MailParameter::SmtpUtfEight); + } + + try_smtp!( + self.client.smtp_command(MailCommand::new( + Some(email.from().clone()), + mail_options, + )), + self + ); // Log the mail command info!("{}: from=<{}>", message_id, email.from()); // Recipient for to_address in &email.to() { - try_smtp!(self.client.rcpt(to_address), self); + try_smtp!( + self.client.smtp_command( + RcptCommand::new(to_address.clone(), vec![]), + ), + self + ); // Log the rcpt command info!("{}: to=<{}>", message_id, to_address); } diff --git a/lettre/src/stub/mod.rs b/lettre/src/stub/mod.rs index d91521b..f817c0b 100644 --- a/lettre/src/stub/mod.rs +++ b/lettre/src/stub/mod.rs @@ -3,13 +3,13 @@ //! //! ```rust //! use lettre::stub::StubEmailTransport; -//! use lettre::{SimpleSendableEmail, EmailTransport}; +//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress}; //! //! let email = SimpleSendableEmail::new( -//! "user@localhost", -//! vec!["root@localhost"], -//! "message_id", -//! "Hello world" +//! EmailAddress::new("user@localhost".to_string()), +//! vec![EmailAddress::new("root@localhost".to_string())], +//! "message_id".to_string(), +//! "Hello world".to_string(), //! ); //! //! let mut sender = StubEmailTransport::new_positive(); @@ -25,8 +25,8 @@ use EmailTransport; use SendableEmail; -use smtp::response::{Code, Response}; use smtp::error::{Error, SmtpResult}; +use smtp::response::{Code, Response}; use std::str::FromStr; /// This transport logs the message envelope and returns the given response @@ -38,15 +38,13 @@ pub struct StubEmailTransport { impl StubEmailTransport { /// Creates a new transport that always returns the given response pub fn new(response: Response) -> StubEmailTransport { - StubEmailTransport { - response: response, - } + StubEmailTransport { response: response } } /// Creates a new transport that always returns a success response pub fn new_positive() -> StubEmailTransport { StubEmailTransport { - response: Response::new(Code::from_str("200").unwrap(), vec!["ok".to_string()]) + response: Response::new(Code::from_str("200").unwrap(), vec!["ok".to_string()]), } } } diff --git a/lettre/tests/transport_file.rs b/lettre/tests/transport_file.rs index e7eafc6..f31b06a 100644 --- a/lettre/tests/transport_file.rs +++ b/lettre/tests/transport_file.rs @@ -1,6 +1,6 @@ extern crate lettre; -use lettre::{EmailTransport, SendableEmail, SimpleSendableEmail}; +use lettre::{EmailAddress, EmailTransport, SendableEmail, SimpleSendableEmail}; use lettre::file::FileEmailTransport; use std::env::temp_dir; @@ -12,10 +12,10 @@ use std::io::Read; fn file_transport() { let mut sender = FileEmailTransport::new(temp_dir()); let email = SimpleSendableEmail::new( - "user@localhost", - vec!["root@localhost"], - "file_id", - "Hello file", + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "file_id".to_string(), + "Hello file".to_string(), ); let result = sender.send(email.clone()); assert!(result.is_ok()); diff --git a/lettre/tests/transport_sendmail.rs b/lettre/tests/transport_sendmail.rs index ed6f5eb..9ad2079 100644 --- a/lettre/tests/transport_sendmail.rs +++ b/lettre/tests/transport_sendmail.rs @@ -1,16 +1,16 @@ extern crate lettre; -use lettre::{EmailTransport, SimpleSendableEmail}; +use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail}; use lettre::sendmail::SendmailTransport; #[test] fn sendmail_transport_simple() { let mut sender = SendmailTransport::new(); let email = SimpleSendableEmail::new( - "user@localhost", - vec!["root@localhost"], - "sendmail_id", - "Hello sendmail", + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "sendmail_id".to_string(), + "Hello sendmail".to_string(), ); let result = sender.send(email); diff --git a/lettre/tests/transport_smtp.rs b/lettre/tests/transport_smtp.rs index 8d08b1b..ba52b84 100644 --- a/lettre/tests/transport_smtp.rs +++ b/lettre/tests/transport_smtp.rs @@ -1,6 +1,6 @@ extern crate lettre; -use lettre::{EmailTransport, SimpleSendableEmail}; +use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail}; use lettre::smtp::SecurityLevel; use lettre::smtp::SmtpTransportBuilder; @@ -11,10 +11,10 @@ fn smtp_transport_simple() { .security_level(SecurityLevel::Opportunistic) .build(); let email = SimpleSendableEmail::new( - "user@localhost", - vec!["root@localhost"], - "smtp_id", - "Hello smtp", + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "smtp_id".to_string(), + "Hello smtp".to_string(), ); let result = sender.send(email); diff --git a/lettre/tests/transport_stub.rs b/lettre/tests/transport_stub.rs index 396fdc9..9a245b4 100644 --- a/lettre/tests/transport_stub.rs +++ b/lettre/tests/transport_stub.rs @@ -1,8 +1,8 @@ extern crate lettre; -use lettre::{EmailTransport, SimpleSendableEmail}; -use lettre::stub::StubEmailTransport; +use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail}; use lettre::smtp::response::{Code, Response}; +use lettre::stub::StubEmailTransport; use std::str::FromStr; #[test] @@ -13,10 +13,10 @@ fn stub_transport() { let mut sender_ko = StubEmailTransport::new(response_ko); let email = SimpleSendableEmail::new( - "user@localhost", - vec!["root@localhost"], - "stub_id", - "Hello stub", + EmailAddress::new("user@localhost".to_string()), + vec![EmailAddress::new("root@localhost".to_string())], + "stub_id".to_string(), + "Hello stub".to_string(), ); let result_ok = sender_ok.send(email.clone()).unwrap(); diff --git a/lettre_email/src/email.rs b/lettre_email/src/email.rs index 02666da..c31f789 100644 --- a/lettre_email/src/email.rs +++ b/lettre_email/src/email.rs @@ -2,7 +2,7 @@ use email_format::{Address, Header, Mailbox, MimeMessage, MimeMultipartType}; use error::Error; -use lettre::SendableEmail; +use lettre::{EmailAddress, SendableEmail}; use mime; use mime::Mime; use std::fmt; @@ -765,12 +765,12 @@ impl EmailBuilder { } impl SendableEmail for Email { - fn to(&self) -> Vec { - self.envelope.to.clone() + fn to(&self) -> Vec { + self.envelope.to.iter().map(|x| EmailAddress::new(x.clone())).collect() } - fn from(&self) -> String { - self.envelope.from.clone() + fn from(&self) -> EmailAddress { + EmailAddress::new(self.envelope.from.clone()) } fn message_id(&self) -> String { @@ -812,7 +812,7 @@ mod test { use super::{Email, EmailBuilder, Envelope, IntoEmail, SimpleEmail}; use email_format::{Header, MimeMessage}; - use lettre::SendableEmail; + use lettre::{EmailAddress, SendableEmail}; use time::now; use uuid::Uuid; @@ -962,13 +962,13 @@ mod test { .build() .unwrap(); - assert_eq!(email.from(), "sender@localhost".to_string()); + assert_eq!(email.from().to_string(), "sender@localhost".to_string()); assert_eq!( email.to(), vec![ - "user@localhost".to_string(), - "cc@localhost".to_string(), - "bcc@localhost".to_string(), + EmailAddress::new("user@localhost".to_string()), + EmailAddress::new("cc@localhost".to_string()), + EmailAddress::new("bcc@localhost".to_string()), ] ); let content = format!("{}", email);