Codec from tokio smtp (#185)

feat(transport): Allow streaming emails
This commit is contained in:
Alexis Mousset
2017-08-20 00:42:45 +02:00
committed by GitHub
parent e81351bfa8
commit f07fe8687d
21 changed files with 195 additions and 205 deletions

View File

@@ -4,7 +4,7 @@ rust:
- stable
- beta
- nightly
- 1.15.0
- 1.18.0
matrix:
allow_failures:

View File

@@ -33,7 +33,7 @@ Development version:
## Install
This library requires rust 1.15 or newer.
This library requires rust 1.18 or newer.
To use this library, add the following to your `Cargo.toml`:
```toml

View File

@@ -18,7 +18,7 @@ fn main() {
.unwrap()
.build();
// Send the email
let result = mailer.send(email);
let result = mailer.send(&email);
if result.is_ok() {
println!("Email sent");

View File

@@ -3,8 +3,7 @@
use self::Error::*;
use serde_json;
use std::error::Error as StdError;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::fmt::{self, Display, Formatter};
use std::io;
/// An enum of all error kinds.

View File

@@ -17,7 +17,7 @@
//! "Hello world".to_string(),
//! );
//!
//! let result = sender.send(email);
//! let result = sender.send(&email);
//! assert!(result.is_ok());
//! ```
//! Example result in `/tmp/b7c211bc-9811-45ce-8cd9-68eab575d695.txt`:
@@ -37,9 +37,9 @@ use EmailTransport;
use SendableEmail;
use SimpleSendableEmail;
use file::error::FileResult;
use serde_json;
use std::fs::File;
use std::io::Read;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
@@ -60,18 +60,21 @@ impl FileEmailTransport {
}
}
impl EmailTransport<FileResult> for FileEmailTransport {
fn send<T: SendableEmail>(&mut self, email: T) -> FileResult {
impl<'a, T: Read + 'a> EmailTransport<'a, T, FileResult> for FileEmailTransport {
fn send<U: SendableEmail<'a, T> + 'a>(&mut self, email: &'a U) -> FileResult {
let mut file = self.path.clone();
file.push(format!("{}.txt", email.message_id()));
let mut f = File::create(file.as_path())?;
let mut message_content = String::new();
let _ = email.message().read_to_string(&mut message_content);
let simple_email = SimpleSendableEmail::new(
email.from().clone(),
email.to().clone(),
email.message_id().clone(),
email.message(),
message_content,
);
f.write_all(
@@ -80,8 +83,4 @@ impl EmailTransport<FileResult> for FileEmailTransport {
Ok(())
}
fn close(&mut self) {
()
}
}

View File

@@ -32,13 +32,10 @@ pub mod file;
#[cfg(feature = "file-transport")]
pub use file::FileEmailTransport;
pub use sendmail::SendmailTransport;
pub use smtp::ClientSecurity;
pub use smtp::SmtpTransport;
pub use smtp::{SmtpTransport, ClientSecurity};
pub use smtp::client::net::ClientTlsParameters;
use std::fmt;
use std::fmt::{Display, Formatter};
pub use stub::StubEmailTransport;
use std::fmt::{self, Display, Formatter};
use std::io::Read;
/// Email address
#[derive(PartialEq, Eq, Clone, Debug)]
@@ -59,7 +56,7 @@ impl EmailAddress {
}
/// Email sendable by an SMTP client
pub trait SendableEmail {
pub trait SendableEmail<'a, T: Read + 'a> {
/// To
fn to(&self) -> Vec<EmailAddress>;
/// From
@@ -67,15 +64,13 @@ pub trait SendableEmail {
/// Message ID, used for logging
fn message_id(&self) -> String;
/// Message content
fn message(self) -> String;
fn message(&'a self) -> Box<T>;
}
/// Transport method for emails
pub trait EmailTransport<U> {
pub trait EmailTransport<'a, U: Read + 'a, V> {
/// Sends the email
fn send<T: SendableEmail>(&mut self, email: T) -> U;
/// Close the transport explicitly
fn close(&mut self);
fn send<T: SendableEmail<'a, U> + 'a>(&mut self, email: &'a T) -> V;
}
/// Minimal email structure
@@ -89,7 +84,7 @@ pub struct SimpleSendableEmail {
/// Message ID
message_id: String,
/// Message content
message: String,
message: Vec<u8>,
}
impl SimpleSendableEmail {
@@ -104,12 +99,12 @@ impl SimpleSendableEmail {
from: from_address,
to: to_addresses,
message_id: message_id,
message: message,
message: message.into_bytes(),
}
}
}
impl SendableEmail for SimpleSendableEmail {
impl<'a> SendableEmail<'a, &'a [u8]> for SimpleSendableEmail {
fn to(&self) -> Vec<EmailAddress> {
self.to.clone()
}
@@ -122,7 +117,7 @@ impl SendableEmail for SimpleSendableEmail {
self.message_id.clone()
}
fn message(self) -> String {
self.message
fn message(&'a self) -> Box<&[u8]> {
Box::new(self.message.as_slice())
}
}

View File

@@ -1,10 +1,8 @@
//! Error and result type for sendmail transport
use self::Error::*;
use std::error::Error as StdError;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::fmt::{self, Display, Formatter};
use std::io;
/// An enum of all error kinds.

View File

@@ -12,13 +12,13 @@
//! );
//!
//! let mut sender = SendmailTransport::new();
//! let result = sender.send(email);
//! let result = sender.send(&email);
//! assert!(result.is_ok());
//! ```
use EmailTransport;
use SendableEmail;
use {EmailTransport, SendableEmail};
use sendmail::error::SendmailResult;
use std::io::Read;
use std::io::prelude::*;
use std::process::{Command, Stdio};
@@ -42,8 +42,8 @@ impl SendmailTransport {
}
}
impl EmailTransport<SendmailResult> for SendmailTransport {
fn send<T: SendableEmail>(&mut self, email: T) -> SendmailResult {
impl<'a, T: Read + 'a> EmailTransport<'a, T, SendmailResult> for SendmailTransport {
fn send<U: SendableEmail<'a, T> + 'a>(&mut self, email: &'a U) -> SendmailResult {
// Spawn the sendmail command
let to_addresses: Vec<String> = email.to().iter().map(|x| x.to_string()).collect();
let mut process = Command::new(&self.command)
@@ -59,8 +59,11 @@ impl EmailTransport<SendmailResult> for SendmailTransport {
.stdout(Stdio::piped())
.spawn()?;
let mut message_content = String::new();
let _ = email.message().read_to_string(&mut message_content);
match process.stdin.as_mut().unwrap().write_all(
email.message().as_bytes(),
message_content.as_bytes(),
) {
Ok(_) => (),
Err(error) => return Err(From::from(error)),
@@ -78,8 +81,4 @@ impl EmailTransport<SendmailResult> for SendmailTransport {
Err(From::from("The sendmail process stopped"))
}
}
fn close(&mut self) {
()
}
}

View File

@@ -10,8 +10,7 @@ use crypto::md5::Md5;
use hex::ToHex;
use smtp::NUL;
use smtp::error::Error;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::fmt::{self, Display, Formatter};
/// Accepted authentication mecanisms on an encrypted connection
/// Trying LOGIN last as it is deprecated.

View File

@@ -7,39 +7,74 @@ use smtp::client::net::{ClientTlsParameters, Connector, NetworkStream, Timeout};
use smtp::commands::*;
use smtp::error::{Error, SmtpResult};
use smtp::response::ResponseParser;
use std::fmt::Debug;
use std::fmt::Display;
use std::io;
use std::io::{BufRead, Read, Write};
use std::fmt::{Debug, Display};
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::ToSocketAddrs;
use std::string::String;
use std::time::Duration;
pub mod net;
pub mod mock;
/// Returns the string after adding a dot at the beginning of each line starting with a dot
///
/// Reference : https://tools.ietf.org/html/rfc5321#page-62 (4.5.2. Transparency)
#[inline]
fn escape_dot(string: &str) -> String {
if string.starts_with('.') {
format!(".{}", string)
} else {
string.to_string()
}.replace("\r.", "\r..")
.replace("\n.", "\n..")
/// The codec used for transparency
#[derive(Default, Debug)]
pub struct ClientCodec {
escape_count: u8,
}
impl ClientCodec {
/// Creates a new client codec
pub fn new() -> Self {
ClientCodec::default()
}
}
impl ClientCodec {
/// Adds transparency
/// TODO: replace CR and LF by CRLF
fn encode(&mut self, frame: &[u8], buf: &mut Vec<u8>) -> Result<(), Error> {
match frame.len() {
0 => {
match self.escape_count {
0 => buf.write_all(b"\r\n.\r\n")?,
1 => buf.write_all(b"\n.\r\n")?,
2 => buf.write_all(b".\r\n")?,
_ => unreachable!(),
}
self.escape_count = 0;
Ok(())
}
_ => {
let mut start = 0;
for (idx, byte) in frame.iter().enumerate() {
match self.escape_count {
0 => self.escape_count = if *byte == b'\r' { 1 } else { 0 },
1 => self.escape_count = if *byte == b'\n' { 2 } else { 0 },
2 => self.escape_count = if *byte == b'.' { 3 } else { 0 },
_ => unreachable!(),
}
if self.escape_count == 3 {
self.escape_count = 0;
buf.write_all(&frame[start..idx])?;
buf.write_all(b".")?;
start = idx;
}
}
Ok(buf.write_all(&frame[start..])?)
}
}
}
}
/// Returns the string replacing all the CRLF with "\<CRLF\>"
#[inline]
/// Used for debug displays
fn escape_crlf(string: &str) -> String {
string.replace(CRLF, "<CR><LF>")
string.replace(CRLF, "<CRLF>")
}
/// Returns the string removing all the CRLF
#[inline]
/// Used for debug displays
fn remove_crlf(string: &str) -> String {
string.replace(CRLF, "")
}
@@ -140,19 +175,8 @@ impl<S: Connector + Write + Read + Timeout + Debug> Client<S> {
self.smtp_command(NoopCommand).is_ok()
}
/// Sends an SMTP command
pub fn command(&mut self, command: &str) -> SmtpResult {
self.send_server(command, CRLF)
}
/// Sends an SMTP command
pub fn smtp_command<C: Display>(&mut self, command: C) -> SmtpResult {
self.send_server(&command.to_string(), "")
}
/// Sends an AUTH command with the given mechanism, and handles challenge if needed
pub fn auth(&mut self, mechanism: Mechanism, credentials: &Credentials) -> SmtpResult {
// TODO
let mut challenges = 10;
let mut response = self.smtp_command(
@@ -176,22 +200,48 @@ impl<S: Connector + Write + Read + Timeout + Debug> Client<S> {
}
/// Sends the message content
pub fn message(&mut self, message_content: &str) -> SmtpResult {
self.send_server(&escape_dot(message_content), MESSAGE_ENDING)
pub fn message<T: Read>(&mut self, mut message: Box<T>) -> SmtpResult {
let mut in_buf: Vec<u8> = vec![];
let mut out_buf: Vec<u8> = vec![];
let mut codec = ClientCodec::new();
let mut message_reader = BufReader::new(message.as_mut());
loop {
in_buf.clear();
out_buf.clear();
match message_reader.read(&mut in_buf)? {
0 => break,
_ => codec.encode(in_buf.as_slice(), &mut out_buf)?,
};
self.write_server(out_buf.as_slice())?;
}
/// Sends a string to the server and gets the response
fn send_server(&mut self, string: &str, end: &str) -> SmtpResult {
self.write_server(MESSAGE_ENDING.as_bytes())?;
self.get_reply()
}
/// Sends an SMTP command
pub fn smtp_command<C: Display>(&mut self, command: C) -> SmtpResult {
self.write_server(command.to_string().as_bytes())?;
self.get_reply()
}
/// Writes a string to the server
fn write_server(&mut self, string: &[u8]) -> Result<(), Error> {
if self.stream.is_none() {
return Err(From::from("Connection closed"));
}
write!(self.stream.as_mut().unwrap(), "{}{}", string, end)?;
self.stream.as_mut().unwrap().write(string)?;
self.stream.as_mut().unwrap().flush()?;
debug!("Wrote: {}", escape_crlf(string));
self.get_reply()
debug!(
"Wrote: {}",
escape_crlf(String::from_utf8_lossy(string).as_ref())
);
Ok(())
}
/// Gets the SMTP response
@@ -216,20 +266,31 @@ impl<S: Connector + Write + Read + Timeout + Debug> Client<S> {
} else {
Err(From::from(response))
}
}
}
#[cfg(test)]
mod test {
use super::{escape_crlf, escape_dot, remove_crlf};
use super::{ClientCodec, escape_crlf, remove_crlf};
#[test]
fn test_escape_dot() {
assert_eq!(escape_dot(".test"), "..test");
assert_eq!(escape_dot("\r.\n.\r\n"), "\r..\n..\r\n");
assert_eq!(escape_dot("test\r\n.test\r\n"), "test\r\n..test\r\n");
assert_eq!(escape_dot("test\r\n.\r\ntest"), "test\r\n..\r\ntest");
fn test_codec() {
let mut codec = ClientCodec::new();
let mut buf: Vec<u8> = vec![];
assert!(codec.encode(b"test\r\n", &mut buf).is_ok());
assert!(codec.encode(b".\r\n", &mut buf).is_ok());
assert!(codec.encode(b"\r\ntest", &mut buf).is_ok());
assert!(codec.encode(b"te\r\n.\r\nst", &mut buf).is_ok());
assert!(codec.encode(b"test", &mut buf).is_ok());
assert!(codec.encode(b"test.", &mut buf).is_ok());
assert!(codec.encode(b"test\n", &mut buf).is_ok());
assert!(codec.encode(b".test\n", &mut buf).is_ok());
assert!(codec.encode(b"test", &mut buf).is_ok());
assert_eq!(
String::from_utf8(buf).unwrap(),
"test\r\n..\r\n\r\ntestte\r\n..\r\nsttesttest.test\n.test\ntest"
);
}
#[test]
@@ -244,11 +305,11 @@ mod test {
#[test]
fn test_escape_crlf() {
assert_eq!(escape_crlf("\r\n"), "<CR><LF>");
assert_eq!(escape_crlf("EHLO my_name\r\n"), "EHLO my_name<CR><LF>");
assert_eq!(escape_crlf("\r\n"), "<CRLF>");
assert_eq!(escape_crlf("EHLO my_name\r\n"), "EHLO my_name<CRLF>");
assert_eq!(
escape_crlf("EHLO my_name\r\nSIZE 42\r\n"),
"EHLO my_name<CR><LF>SIZE 42<CR><LF>"
"EHLO my_name<CRLF>SIZE 42<CRLF>"
);
}
}

View File

@@ -2,8 +2,7 @@
use native_tls::{TlsConnector, TlsStream};
use smtp::client::mock::MockStream;
use std::io;
use std::io::{ErrorKind, Read, Write};
use std::io::{self, ErrorKind, Read, Write};
use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream};
use std::time::Duration;
@@ -17,7 +16,7 @@ pub struct ClientTlsParameters {
}
impl ClientTlsParameters {
/// TODO
/// Creates a `ClientTlsParameters`
pub fn new(domain: String, connector: TlsConnector) -> ClientTlsParameters {
ClientTlsParameters {
connector: connector,

View File

@@ -8,8 +8,7 @@ use smtp::error::Error;
use smtp::extension::{MailParameter, RcptParameter};
use smtp::extension::ClientId;
use smtp::response::Response;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::fmt::{self, Display, Formatter};
/// EHLO command
#[derive(PartialEq, Clone, Debug)]

View File

@@ -5,12 +5,10 @@ use smtp::error::Error;
use smtp::response::Response;
use smtp::util::XText;
use std::collections::HashSet;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::fmt::{self, Display, Formatter};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::result::Result;
/// Client identifier, the parameter to `EHLO`
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ClientId {

View File

@@ -31,7 +31,7 @@
//! let mut mailer =
//! SmtpTransport::builder_unencrypted_localhost().unwrap().build();
//! // Send the email
//! let result = mailer.send(email);
//! let result = mailer.send(&email);
//!
//! assert!(result.is_ok());
//! ```
@@ -59,7 +59,6 @@
//! .hello_name(ClientId::Domain("my.hostname.tld".to_string()))
//! // Add credentials for authentication
//! .credentials(Credentials::new("username".to_string(), "password".to_string()))
//! // FIXME security doc
//! // Enable SMTPUTF8 if the server supports it
//! .smtp_utf8(true)
//! // Configure expected authentication mechanism
@@ -67,11 +66,11 @@
//! // Enable connection reuse
//! .connection_reuse(ConnectionReuseParameters::ReuseUnlimited).build();
//!
//! let result_1 = mailer.send(email.clone());
//! let result_1 = mailer.send(&email);
//! assert!(result_1.is_ok());
//!
//! // The second email will use the same connection
//! let result_2 = mailer.send(email);
//! let result_2 = mailer.send(&email);
//! assert!(result_2.is_ok());
//!
//! // Explicitly close the SMTP transaction as we enabled connection reuse
@@ -101,11 +100,10 @@
//! 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.message(Box::new("Test email".as_bytes()));
//! let _ = email_client.smtp_command(QuitCommand);
//! ```
use EmailTransport;
use SendableEmail;
use native_tls::TlsConnector;
@@ -116,6 +114,7 @@ use smtp::client::net::ClientTlsParameters;
use smtp::commands::*;
use smtp::error::{Error, SmtpResult};
use smtp::extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo};
use std::io::Read;
use std::net::{SocketAddr, ToSocketAddrs};
use std::time::Duration;
@@ -318,7 +317,7 @@ macro_rules! try_smtp (
})
);
impl SmtpTransport {
impl<'a> SmtpTransport {
/// Simple and secure transport, should be used when possible.
/// Creates an encrypted transport over submission port, using the provided domain
/// to validate TLS certificates.
@@ -365,17 +364,6 @@ impl SmtpTransport {
}
}
/// Reset the client state
fn reset(&mut self) {
// Close the SMTP transaction if needed
self.close();
// Reset the client state
self.server_info = None;
self.state.panic = false;
self.state.connection_reuse_count = 0;
}
/// Gets the EHLO response and updates server information
pub fn get_ehlo(&mut self) -> SmtpResult {
// Extended Hello
@@ -393,12 +381,28 @@ impl SmtpTransport {
Ok(ehlo_response)
}
/// Closes the inner connection
pub fn close(&mut self) {
self.client.close();
}
impl EmailTransport<SmtpResult> for SmtpTransport {
/// Reset the client state
pub fn reset(&mut self) {
// Close the SMTP transaction if needed
self.close();
// Reset the client state
self.server_info = None;
self.state.panic = false;
self.state.connection_reuse_count = 0;
}
}
impl<'a, T: Read + 'a> EmailTransport<'a, T, SmtpResult> for SmtpTransport {
/// Sends an email
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms, cyclomatic_complexity))]
fn send<T: SendableEmail>(&mut self, email: T) -> SmtpResult {
fn send<U: SendableEmail<'a, T> + 'a>(&mut self, email: &'a U) -> SmtpResult {
// Extract email information
let message_id = email.message_id();
@@ -530,8 +534,7 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
try_smtp!(self.client.smtp_command(DataCommand), self);
// Message content
let message = email.message();
let result = self.client.message(&message);
let result = self.client.message(email.message());
if result.is_ok() {
// Increment the connection reuse counter
@@ -539,10 +542,9 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
// Log the message
info!(
"{}: conn_use={}, size={}, status=sent ({})",
"{}: conn_use={}, status=sent ({})",
message_id,
self.state.connection_reuse_count,
message.len(),
result
.as_ref()
.ok()
@@ -564,9 +566,4 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
result
}
/// Closes the inner connection
fn close(&mut self) {
self.client.close();
}
}

View File

@@ -13,7 +13,7 @@
//! );
//!
//! let mut sender = StubEmailTransport::new_positive();
//! let result = sender.send(email);
//! let result = sender.send(&email);
//! assert!(result.is_ok());
//! ```
//!
@@ -27,6 +27,7 @@ use EmailTransport;
use SendableEmail;
use smtp::error::{Error, SmtpResult};
use smtp::response::{Code, Response};
use std::io::Read;
use std::str::FromStr;
/// This transport logs the message envelope and returns the given response
@@ -52,8 +53,8 @@ impl StubEmailTransport {
/// SMTP result type
pub type StubResult = SmtpResult;
impl EmailTransport<StubResult> for StubEmailTransport {
fn send<T: SendableEmail>(&mut self, email: T) -> StubResult {
impl<'a, T: Read + 'a> EmailTransport<'a, T, StubResult> for StubEmailTransport {
fn send<U: SendableEmail<'a, T>>(&mut self, email: &'a U) -> StubResult {
info!(
"{}: from=<{}> to=<{:?}>",
@@ -67,8 +68,4 @@ impl EmailTransport<StubResult> for StubEmailTransport {
Err(Error::from(self.response.clone()))
}
}
fn close(&mut self) {
()
}
}

View File

@@ -22,7 +22,7 @@ mod test {
"file_id".to_string(),
"Hello file".to_string(),
);
let result = sender.send(email.clone());
let result = sender.send(&email);
assert!(result.is_ok());
let message_id = email.message_id();
@@ -33,8 +33,8 @@ mod test {
assert_eq!(
buffer,
"{\"to\":[\"root@localhost\"],\"from\":\"user@localhost\",\
\"message_id\":\"file_id\",\"message\":\"Hello file\"}"
"{\"to\":[\"root@localhost\"],\"from\":\"user@localhost\",\"message_id\":\
\"file_id\",\"message\":[72,101,108,108,111,32,102,105,108,101]}"
);
remove_file(file).unwrap();

View File

@@ -13,7 +13,7 @@ fn sendmail_transport_simple() {
"Hello sendmail".to_string(),
);
let result = sender.send(email);
let result = sender.send(&email);
println!("{:?}", result);
assert!(result.is_ok());
}

View File

@@ -14,6 +14,6 @@ fn smtp_transport_simple() {
"Hello smtp".to_string(),
);
let result = sender.send(email);
let result = sender.send(&email);
assert!(result.is_ok());
}

View File

@@ -19,8 +19,8 @@ fn stub_transport() {
"Hello stub".to_string(),
);
let result_ok = sender_ok.send(email.clone()).unwrap();
let result_ko = sender_ko.send(email);
let result_ok = sender_ok.send(&email).unwrap();
let result_ko = sender_ko.send(&email);
assert_eq!(result_ok, response_ok);
assert!(result_ko.is_err());

View File

@@ -20,7 +20,7 @@ fn main() {
.unwrap()
.build();
// Send the email
let result = mailer.send(email);
let result = mailer.send(&email);
if result.is_ok() {
println!("Email sent");

View File

@@ -66,8 +66,6 @@ pub use email_format::{Address, Header, Mailbox, MimeMessage, MimeMultipartType}
use error::Error;
use lettre::{EmailAddress, SendableEmail};
use mime::Mime;
use std::fmt;
use std::fmt::{Display, Formatter};
use time::{Tm, now};
use uuid::Uuid;
@@ -375,19 +373,13 @@ impl Envelope {
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Email {
/// Message
message: MimeMessage,
message: Vec<u8>,
/// Envelope
envelope: Envelope,
/// Message-ID
message_id: Uuid,
}
impl Display for Email {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.message.as_string())
}
}
impl PartBuilder {
/// Creates a new empty part
pub fn new() -> PartBuilder {
@@ -818,14 +810,14 @@ impl EmailBuilder {
}
Ok(Email {
message: self.message.build(),
message: self.message.build().as_string().into_bytes(),
envelope: envelope,
message_id: message_id,
})
}
}
impl SendableEmail for Email {
impl<'a> SendableEmail<'a, &'a [u8]> for Email {
fn to(&self) -> Vec<EmailAddress> {
self.envelope
.to
@@ -842,8 +834,8 @@ impl SendableEmail for Email {
format!("{}", self.message_id)
}
fn message(self) -> String {
self.to_string()
fn message(&'a self) -> Box<&[u8]> {
Box::new(self.message.as_slice())
}
}
@@ -875,13 +867,10 @@ pub trait ExtractableEmail {
#[cfg(test)]
mod test {
use super::{Email, EmailBuilder, Envelope, IntoEmail, SimpleEmail};
use email_format::{Header, MimeMessage};
use super::{EmailBuilder, IntoEmail, SimpleEmail};
use lettre::{EmailAddress, SendableEmail};
use time::now;
use uuid::Uuid;
#[test]
fn test_simple_email_builder() {
let email_builder = SimpleEmail::default();
@@ -900,7 +889,7 @@ mod test {
.unwrap();
assert_eq!(
format!("{}", email),
format!("{}", String::from_utf8_lossy(email.message().as_ref())),
format!(
"Subject: Hello\r\nContent-Type: text/plain; \
charset=utf-8\r\nX-test: value\r\nTo: <user@localhost>\r\nFrom: \
@@ -913,43 +902,6 @@ mod test {
);
}
#[test]
fn test_email_display() {
let current_message = Uuid::new_v4();
let mut email = Email {
message: MimeMessage::new_blank_message(),
envelope: Envelope {
to: vec![],
from: "".to_string(),
},
message_id: current_message,
};
email.message.headers.insert(
Header::new_with_value(
"Message-ID".to_string(),
format!("<{}@rust-smtp>", current_message),
).unwrap(),
);
email.message.headers.insert(
Header::new_with_value("To".to_string(), "to@example.com".to_string())
.unwrap(),
);
email.message.body = "body".to_string();
assert_eq!(
format!("{}", email),
format!(
"Message-ID: <{}@rust-smtp>\r\nTo: to@example.com\r\n\r\nbody\r\n",
current_message
)
);
assert_eq!(current_message.to_string(), email.message_id());
}
#[test]
fn test_multiple_from() {
let email_builder = EmailBuilder::new();
@@ -964,7 +916,7 @@ mod test {
.build()
.unwrap();
assert_eq!(
format!("{}", email),
format!("{}", String::from_utf8_lossy(email.message().as_ref())),
format!(
"Date: {}\r\nSubject: Invitation\r\nSender: \
<dieter@example.com>\r\nTo: <anna@example.com>\r\nFrom: \
@@ -995,7 +947,7 @@ mod test {
.unwrap();
assert_eq!(
format!("{}", email),
format!("{}", String::from_utf8_lossy(email.message().as_ref())),
format!(
"Date: {}\r\nSubject: Hello\r\nX-test: value\r\nSender: \
<sender@localhost>\r\nTo: <user@localhost>\r\nFrom: \
@@ -1036,8 +988,6 @@ mod test {
EmailAddress::new("bcc@localhost".to_string()),
]
);
let content = format!("{}", email);
assert_eq!(email.message(), content);
}
}