@@ -4,7 +4,7 @@ rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
- 1.15.0
|
||||
- 1.18.0
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())?;
|
||||
}
|
||||
|
||||
self.write_server(MESSAGE_ENDING.as_bytes())?;
|
||||
self.get_reply()
|
||||
}
|
||||
|
||||
/// Sends a string to the server and gets the response
|
||||
fn send_server(&mut self, string: &str, end: &str) -> SmtpResult {
|
||||
/// 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>"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// 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 EmailTransport<SmtpResult> for SmtpTransport {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user