Merge branch 'master' into master
This commit is contained in:
@@ -9,7 +9,7 @@ matrix:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
|
||||
sudo: false
|
||||
sudo: required
|
||||
|
||||
cache:
|
||||
apt: true
|
||||
@@ -34,6 +34,7 @@ addons:
|
||||
|
||||
before_script:
|
||||
- smtp-sink 2525 1000&
|
||||
- sudo chgrp -R postdrop /var/spool/postfix/maildrop
|
||||
|
||||
script:
|
||||
- travis-cargo build
|
||||
|
||||
@@ -16,7 +16,8 @@ email = "^0.0"
|
||||
log = "^0.3"
|
||||
mime = "^0.2"
|
||||
openssl = "^0.9"
|
||||
rustc-serialize = "^0.3"
|
||||
base64 = "~0.5.0"
|
||||
hex = "^0.2.0"
|
||||
rust-crypto = "^0.2"
|
||||
time = "^0.1"
|
||||
uuid = { version = ">=0.4, <0.6", features = ["v4"] }
|
||||
|
||||
@@ -506,7 +506,8 @@ impl EmailBuilder {
|
||||
|
||||
/// Adds a `Subject` header
|
||||
pub fn set_subject<S: Into<String>>(&mut self, subject: S) {
|
||||
self.message.add_header(("Subject".to_string(), subject.into()));
|
||||
self.message
|
||||
.add_header(("Subject".to_string(), subject.into()));
|
||||
}
|
||||
|
||||
/// Adds a `Date` header with the given date
|
||||
@@ -517,7 +518,8 @@ impl EmailBuilder {
|
||||
|
||||
/// Adds a `Date` header with the given date
|
||||
pub fn set_date(&mut self, date: &Tm) {
|
||||
self.message.add_header(("Date", Tm::rfc822z(date).to_string()));
|
||||
self.message
|
||||
.add_header(("Date", Tm::rfc822z(date).to_string()));
|
||||
self.date_issued = true;
|
||||
}
|
||||
|
||||
@@ -552,8 +554,8 @@ impl EmailBuilder {
|
||||
/// Sets the email body to plain text content
|
||||
pub fn set_text<S: Into<String>>(&mut self, body: S) {
|
||||
self.message.set_body(body);
|
||||
self.message.add_header(("Content-Type",
|
||||
format!("{}", mime!(Text/Plain; Charset=Utf8)).as_ref()));
|
||||
self.message
|
||||
.add_header(("Content-Type", format!("{}", mime!(Text/Plain; Charset=Utf8)).as_ref()));
|
||||
}
|
||||
|
||||
/// Sets the email body to HTML content
|
||||
@@ -565,8 +567,8 @@ impl EmailBuilder {
|
||||
/// Sets the email body to HTML content
|
||||
pub fn set_html<S: Into<String>>(&mut self, body: S) {
|
||||
self.message.set_body(body);
|
||||
self.message.add_header(("Content-Type",
|
||||
format!("{}", mime!(Text/Html; Charset=Utf8)).as_ref()));
|
||||
self.message
|
||||
.add_header(("Content-Type", format!("{}", mime!(Text/Html; Charset=Utf8)).as_ref()));
|
||||
}
|
||||
|
||||
/// Sets the email content
|
||||
@@ -636,7 +638,8 @@ impl EmailBuilder {
|
||||
}
|
||||
// Add the sender header, if any.
|
||||
if let Some(ref v) = self.sender_header {
|
||||
self.message.add_header(("Sender", v.to_string().as_ref()));
|
||||
self.message
|
||||
.add_header(("Sender", v.to_string().as_ref()));
|
||||
}
|
||||
// Calculate the envelope
|
||||
let envelope = match self.envelope {
|
||||
@@ -688,25 +691,28 @@ impl EmailBuilder {
|
||||
// Add the collected addresses as mailbox-list all at once.
|
||||
// The unwraps are fine because the conversions for Vec<Address> never errs.
|
||||
if !self.to_header.is_empty() {
|
||||
self.message.add_header(Header::new_with_value("To".into(), self.to_header).unwrap());
|
||||
self.message
|
||||
.add_header(Header::new_with_value("To".into(), self.to_header).unwrap());
|
||||
}
|
||||
if !self.from_header.is_empty() {
|
||||
self.message.add_header(Header::new_with_value("From".into(), self.from_header)
|
||||
.unwrap());
|
||||
self.message
|
||||
.add_header(Header::new_with_value("From".into(), self.from_header).unwrap());
|
||||
} else {
|
||||
return Err(Error::MissingFrom);
|
||||
}
|
||||
if !self.cc_header.is_empty() {
|
||||
self.message.add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap());
|
||||
self.message
|
||||
.add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap());
|
||||
}
|
||||
if !self.reply_to_header.is_empty() {
|
||||
self.message.add_header(Header::new_with_value("Reply-To".into(),
|
||||
self.reply_to_header)
|
||||
.unwrap());
|
||||
self.message
|
||||
.add_header(Header::new_with_value("Reply-To".into(), self.reply_to_header)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
if !self.date_issued {
|
||||
self.message.add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref()));
|
||||
self.message
|
||||
.add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref()));
|
||||
}
|
||||
|
||||
self.message.add_header(("MIME-Version", "1.0"));
|
||||
@@ -828,7 +834,8 @@ mod test {
|
||||
let email_builder = SimpleEmail::default();
|
||||
let date_now = now();
|
||||
|
||||
let email = email_builder.to("user@localhost")
|
||||
let email = email_builder
|
||||
.to("user@localhost")
|
||||
.from("user@localhost")
|
||||
.cc(("cc@localhost", "Alias"))
|
||||
.reply_to("reply@localhost")
|
||||
@@ -862,14 +869,18 @@ mod test {
|
||||
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("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
|
||||
.headers
|
||||
.insert(Header::new_with_value("To".to_string(), "to@example.com".to_string())
|
||||
.unwrap());
|
||||
|
||||
email.message.body = "body".to_string();
|
||||
|
||||
@@ -883,7 +894,8 @@ mod test {
|
||||
fn test_multiple_from() {
|
||||
let email_builder = EmailBuilder::new();
|
||||
let date_now = now();
|
||||
let email = email_builder.to("anna@example.com")
|
||||
let email = email_builder
|
||||
.to("anna@example.com")
|
||||
.from("dieter@example.com")
|
||||
.from("joachim@example.com")
|
||||
.date(&date_now)
|
||||
@@ -905,7 +917,8 @@ mod test {
|
||||
let email_builder = EmailBuilder::new();
|
||||
let date_now = now();
|
||||
|
||||
let email = email_builder.to("user@localhost")
|
||||
let email = email_builder
|
||||
.to("user@localhost")
|
||||
.from("user@localhost")
|
||||
.cc(("cc@localhost", "Alias"))
|
||||
.reply_to("reply@localhost")
|
||||
@@ -932,7 +945,8 @@ mod test {
|
||||
let email_builder = EmailBuilder::new();
|
||||
let date_now = now();
|
||||
|
||||
let email = email_builder.to("user@localhost")
|
||||
let email = email_builder
|
||||
.to("user@localhost")
|
||||
.from("user@localhost")
|
||||
.cc(("cc@localhost", "Alias"))
|
||||
.bcc("bcc@localhost")
|
||||
|
||||
10
src/lib.rs
10
src/lib.rs
@@ -92,10 +92,11 @@
|
||||
//!
|
||||
//! This is the most basic example of usage:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use lettre::transport::smtp::{SmtpTransport, SmtpTransportBuilder};
|
||||
//! ```rust,no_run
|
||||
//! use lettre::transport::smtp::SmtpTransportBuilder;
|
||||
//! use lettre::email::EmailBuilder;
|
||||
//! use lettre::transport::EmailTransport;
|
||||
//! use lettre::transport::smtp::SecurityLevel;
|
||||
//!
|
||||
//! let email = EmailBuilder::new()
|
||||
//! .to("root@localhost")
|
||||
@@ -107,7 +108,7 @@
|
||||
//!
|
||||
//! // Open a local connection on port 25
|
||||
//! let mut mailer =
|
||||
//! SmtpTransportBuilder::localhost().unwrap().build();
|
||||
//! SmtpTransportBuilder::localhost().unwrap().security_level(SecurityLevel::Opportunistic).build();
|
||||
//! // Send the email
|
||||
//! let result = mailer.send(email);
|
||||
//!
|
||||
@@ -276,7 +277,8 @@
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate mime;
|
||||
extern crate rustc_serialize;
|
||||
extern crate base64;
|
||||
extern crate hex;
|
||||
extern crate crypto;
|
||||
extern crate time;
|
||||
extern crate uuid;
|
||||
|
||||
@@ -39,7 +39,8 @@ impl EmailTransport<SendmailResult> for SendmailTransport {
|
||||
.stdout(Stdio::piped())
|
||||
.spawn());
|
||||
|
||||
match process.stdin
|
||||
match process
|
||||
.stdin
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_all(email.message().as_bytes()) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use crypto::hmac::Hmac;
|
||||
use crypto::mac::Mac;
|
||||
use crypto::md5::Md5;
|
||||
use rustc_serialize::hex::ToHex;
|
||||
use hex::ToHex;
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use transport::smtp::NUL;
|
||||
@@ -101,16 +101,22 @@ mod test {
|
||||
|
||||
assert_eq!(mechanism.response("username", "password", None).unwrap(),
|
||||
"\u{0}username\u{0}password");
|
||||
assert!(mechanism.response("username", "password", Some("test")).is_err());
|
||||
assert!(mechanism
|
||||
.response("username", "password", Some("test"))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_login() {
|
||||
let mechanism = Mechanism::Login;
|
||||
|
||||
assert_eq!(mechanism.response("alice", "wonderland", Some("Username")).unwrap(),
|
||||
assert_eq!(mechanism
|
||||
.response("alice", "wonderland", Some("Username"))
|
||||
.unwrap(),
|
||||
"alice");
|
||||
assert_eq!(mechanism.response("alice", "wonderland", Some("Password")).unwrap(),
|
||||
assert_eq!(mechanism
|
||||
.response("alice", "wonderland", Some("Password"))
|
||||
.unwrap(),
|
||||
"wonderland");
|
||||
assert!(mechanism.response("username", "password", None).is_err());
|
||||
}
|
||||
@@ -119,9 +125,10 @@ mod test {
|
||||
fn test_cram_md5() {
|
||||
let mechanism = Mechanism::CramMd5;
|
||||
|
||||
assert_eq!(mechanism.response("alice",
|
||||
"wonderland",
|
||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="))
|
||||
assert_eq!(mechanism
|
||||
.response("alice",
|
||||
"wonderland",
|
||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="))
|
||||
.unwrap(),
|
||||
"alice a540ebe4ef2304070bbc3c456c1f64c0");
|
||||
assert!(mechanism.response("alice", "wonderland", None).is_err());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use bufstream::BufStream;
|
||||
use openssl::ssl::SslContext;
|
||||
|
||||
use rustc_serialize::base64::{self, FromBase64, ToBase64};
|
||||
use base64;
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
use std::io::{BufRead, Read, Write};
|
||||
@@ -204,9 +204,7 @@ impl<S: Connector + Timeout + Write + Read + Debug> Client<S> {
|
||||
if mechanism.supports_initial_response() {
|
||||
self.command(&format!("AUTH {} {}",
|
||||
mechanism,
|
||||
try!(mechanism.response(username, password, None))
|
||||
.as_bytes()
|
||||
.to_base64(base64::STANDARD)))
|
||||
base64::encode_config(try!(mechanism.response(username, password, None)).as_bytes(), base64::STANDARD)))
|
||||
} else {
|
||||
let encoded_challenge = match try!(self.command(&format!("AUTH {}", mechanism)))
|
||||
.first_word() {
|
||||
@@ -216,7 +214,7 @@ impl<S: Connector + Timeout + Write + Read + Debug> Client<S> {
|
||||
|
||||
debug!("auth encoded challenge: {}", encoded_challenge);
|
||||
|
||||
let decoded_challenge = match encoded_challenge.from_base64() {
|
||||
let decoded_challenge = match base64::decode(&encoded_challenge) {
|
||||
Ok(challenge) => {
|
||||
match String::from_utf8(challenge) {
|
||||
Ok(value) => value,
|
||||
@@ -231,11 +229,10 @@ impl<S: Connector + Timeout + Write + Read + Debug> Client<S> {
|
||||
let mut challenge_expected = 3;
|
||||
|
||||
while challenge_expected > 0 {
|
||||
let response = try!(self.command(&try!(mechanism.response(username,
|
||||
let response = try!(self.command(&base64::encode_config(&try!(mechanism.response(username,
|
||||
password,
|
||||
Some(&decoded_challenge)))
|
||||
.as_bytes()
|
||||
.to_base64(base64::STANDARD)));
|
||||
.as_bytes(), base64::STANDARD)));
|
||||
|
||||
if !response.has_code(334) {
|
||||
return Ok(response);
|
||||
@@ -265,10 +262,7 @@ impl<S: Connector + Timeout + Write + Read + Debug> Client<S> {
|
||||
}
|
||||
|
||||
try!(write!(self.stream.as_mut().unwrap(), "{}{}", string, end));
|
||||
try!(self.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.flush());
|
||||
try!(self.stream.as_mut().unwrap().flush());
|
||||
|
||||
debug!("Wrote: {}", escape_crlf(string));
|
||||
|
||||
@@ -281,19 +275,13 @@ impl<S: Connector + Timeout + Write + Read + Debug> Client<S> {
|
||||
let mut parser = ResponseParser::default();
|
||||
|
||||
let mut line = String::new();
|
||||
try!(self.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.read_line(&mut line));
|
||||
try!(self.stream.as_mut().unwrap().read_line(&mut line));
|
||||
|
||||
debug!("Read: {}", escape_crlf(line.as_ref()));
|
||||
|
||||
while try!(parser.read_line(remove_crlf(line.as_ref()).as_ref())) {
|
||||
line.clear();
|
||||
try!(self.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.read_line(&mut line));
|
||||
try!(self.stream.as_mut().unwrap().read_line(&mut line));
|
||||
}
|
||||
|
||||
let response = try!(parser.response());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Error and result type for SMTP clients
|
||||
|
||||
use self::Error::*;
|
||||
use rustc_serialize::base64::FromBase64Error;
|
||||
use base64::DecodeError;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -23,7 +23,7 @@ pub enum Error {
|
||||
/// Error parsing a response
|
||||
ResponseParsing(&'static str),
|
||||
/// Error parsing a base64 string in response
|
||||
ChallengeParsing(FromBase64Error),
|
||||
ChallengeParsing(DecodeError),
|
||||
/// Error parsing UTF8in response
|
||||
Utf8Parsing(FromUtf8Error),
|
||||
/// Internal client error
|
||||
|
||||
@@ -116,7 +116,8 @@ impl ServerInfo {
|
||||
|
||||
/// Checks if the server supports an ESMTP feature
|
||||
pub fn supports_auth_mechanism(&self, mechanism: Mechanism) -> bool {
|
||||
self.features.contains(&Extension::Authentication(mechanism))
|
||||
self.features
|
||||
.contains(&Extension::Authentication(mechanism))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +173,9 @@ mod test {
|
||||
fn test_serverinfo() {
|
||||
let response =
|
||||
Response::new(Code::new(Severity::PositiveCompletion, Category::Unspecified4, 1),
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]);
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()]);
|
||||
|
||||
let mut features = HashSet::new();
|
||||
assert!(features.insert(Extension::EightBitMime));
|
||||
|
||||
@@ -297,13 +297,14 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
}
|
||||
|
||||
if self.state.connection_reuse_count == 0 {
|
||||
try!(self.client.connect(&self.client_info.server_addr,
|
||||
match &self.client_info.security_level {
|
||||
&SecurityLevel::EncryptedWrapper => {
|
||||
Some(&self.client_info.ssl_context)
|
||||
}
|
||||
_ => None,
|
||||
}));
|
||||
try!(self.client
|
||||
.connect(&self.client_info.server_addr,
|
||||
match &self.client_info.security_level {
|
||||
&SecurityLevel::EncryptedWrapper => {
|
||||
Some(&self.client_info.ssl_context)
|
||||
}
|
||||
_ => None,
|
||||
}));
|
||||
|
||||
try!(self.client.set_timeout(self.client_info.timeout));
|
||||
|
||||
@@ -323,7 +324,8 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
(&SecurityLevel::EncryptedWrapper, _) => (),
|
||||
(_, true) => {
|
||||
try_smtp!(self.client.starttls(), self);
|
||||
try_smtp!(self.client.upgrade_tls_stream(&self.client_info.ssl_context),
|
||||
try_smtp!(self.client
|
||||
.upgrade_tls_stream(&self.client_info.ssl_context),
|
||||
self);
|
||||
|
||||
debug!("connection encrypted");
|
||||
@@ -334,10 +336,7 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
}
|
||||
|
||||
if self.client_info.credentials.is_some() {
|
||||
let (username, password) = self.client_info
|
||||
.credentials
|
||||
.clone()
|
||||
.unwrap();
|
||||
let (username, password) = self.client_info.credentials.clone().unwrap();
|
||||
|
||||
let mut found = false;
|
||||
|
||||
@@ -376,13 +375,13 @@ 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)) {
|
||||
.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,
|
||||
@@ -416,7 +415,8 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
message_id,
|
||||
self.state.connection_reuse_count,
|
||||
message.len(),
|
||||
result.as_ref()
|
||||
result
|
||||
.as_ref()
|
||||
.ok()
|
||||
.unwrap()
|
||||
.message()
|
||||
|
||||
@@ -423,7 +423,9 @@ mod test {
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()])
|
||||
.message(),
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]);
|
||||
vec!["me".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string()]);
|
||||
let empty_message: Vec<String> = vec![];
|
||||
assert_eq!(Response::new(Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
|
||||
@@ -2,11 +2,15 @@ extern crate lettre;
|
||||
|
||||
use lettre::email::EmailBuilder;
|
||||
use lettre::transport::EmailTransport;
|
||||
use lettre::transport::smtp::SecurityLevel;
|
||||
use lettre::transport::smtp::SmtpTransportBuilder;
|
||||
|
||||
#[test]
|
||||
fn smtp_transport_simple() {
|
||||
let mut sender = SmtpTransportBuilder::localhost().unwrap().build();
|
||||
let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525")
|
||||
.unwrap()
|
||||
.security_level(SecurityLevel::Opportunistic)
|
||||
.build();
|
||||
let email = EmailBuilder::new()
|
||||
.to("root@localhost")
|
||||
.from("user@localhost")
|
||||
|
||||
Reference in New Issue
Block a user