Merge pull request #169 from amousset/email-type

feat(transport): Use command types for mail and rcpt
This commit is contained in:
Alexis Mousset
2017-07-17 12:04:40 +02:00
committed by GitHub
15 changed files with 147 additions and 119 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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<FileResult> 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(

View File

@@ -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<String>;
fn to(&self) -> Vec<EmailAddress>;
/// 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<U> {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimpleSendableEmail {
/// To
to: Vec<String>,
to: Vec<EmailAddress>,
/// 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<EmailAddress>,
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<String> {
fn to(&self) -> Vec<EmailAddress> {
self.to.clone()
}
fn from(&self) -> String {
fn from(&self) -> EmailAddress {
self.from.clone()
}

View File

@@ -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<SendmailResult> for SendmailTransport {
fn send<T: SendableEmail>(&mut self, email: T) -> SendmailResult {
// Spawn the sendmail command
let to_addresses: Vec<String> = 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()

View File

@@ -151,19 +151,6 @@ impl<S: Connector + Write + Read + Timeout + Debug> Client<S> {
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 {

View File

@@ -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()),

View File

@@ -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),

View File

@@ -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<NetworkStream> = 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<SmtpResult> 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);
}

View File

@@ -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()]),
}
}
}

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();

View File

@@ -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<String> {
self.envelope.to.clone()
fn to(&self) -> Vec<EmailAddress> {
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);