diff --git a/.travis.yml b/.travis.yml index 30a30d3..561c46a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 3ebaa91..5f57987 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/email/mod.rs b/src/email/mod.rs index 5e94119..dca15ea 100644 --- a/src/email/mod.rs +++ b/src/email/mod.rs @@ -506,7 +506,8 @@ impl EmailBuilder { /// Adds a `Subject` header pub fn set_subject>(&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>(&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>(&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
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") diff --git a/src/lib.rs b/src/lib.rs index f9524b0..e48cce2 100644 --- a/src/lib.rs +++ b/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; diff --git a/src/transport/sendmail/mod.rs b/src/transport/sendmail/mod.rs index 309a7e6..c013c9f 100644 --- a/src/transport/sendmail/mod.rs +++ b/src/transport/sendmail/mod.rs @@ -39,7 +39,8 @@ impl EmailTransport for SendmailTransport { .stdout(Stdio::piped()) .spawn()); - match process.stdin + match process + .stdin .as_mut() .unwrap() .write_all(email.message().as_bytes()) { diff --git a/src/transport/smtp/authentication.rs b/src/transport/smtp/authentication.rs index e7ac401..145c35c 100644 --- a/src/transport/smtp/authentication.rs +++ b/src/transport/smtp/authentication.rs @@ -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()); diff --git a/src/transport/smtp/client/mod.rs b/src/transport/smtp/client/mod.rs index 69c98a2..8b5df5c 100644 --- a/src/transport/smtp/client/mod.rs +++ b/src/transport/smtp/client/mod.rs @@ -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 Client { 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 Client { 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 Client { 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 Client { } 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 Client { 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()); diff --git a/src/transport/smtp/error.rs b/src/transport/smtp/error.rs index 942fb8e..4a0a29e 100644 --- a/src/transport/smtp/error.rs +++ b/src/transport/smtp/error.rs @@ -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 diff --git a/src/transport/smtp/extension.rs b/src/transport/smtp/extension.rs index 2954e77..113dbdd 100644 --- a/src/transport/smtp/extension.rs +++ b/src/transport/smtp/extension.rs @@ -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)); diff --git a/src/transport/smtp/mod.rs b/src/transport/smtp/mod.rs index 62737e2..a4504d6 100644 --- a/src/transport/smtp/mod.rs +++ b/src/transport/smtp/mod.rs @@ -297,13 +297,14 @@ impl EmailTransport 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 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 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 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 for SmtpTransport { message_id, self.state.connection_reuse_count, message.len(), - result.as_ref() + result + .as_ref() .ok() .unwrap() .message() diff --git a/src/transport/smtp/response.rs b/src/transport/smtp/response.rs index d1421b6..28471b5 100644 --- a/src/transport/smtp/response.rs +++ b/src/transport/smtp/response.rs @@ -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 = vec![]; assert_eq!(Response::new(Code { severity: "2".parse::().unwrap(), diff --git a/tests/transport_smtp.rs b/tests/transport_smtp.rs index 44f3d48..56ae8dd 100644 --- a/tests/transport_smtp.rs +++ b/tests/transport_smtp.rs @@ -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")