diff --git a/examples/client.rs b/examples/client.rs index 5fc55f7..e08e9a4 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -7,7 +7,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(core, old_io, rustc_private, env, collections)] +#![feature(core, old_io, rustc_private, collections)] #[macro_use] extern crate log; @@ -23,19 +23,19 @@ use getopts::{optopt, optflag, getopts, OptGroup, usage}; use smtp::client::ClientBuilder; use smtp::error::SmtpResult; -use smtp::mailer::Email; +use smtp::mailer::EmailBuilder; fn sendmail(source_address: String, recipient_addresses: Vec, message: String, subject: String, server: String, port: Port, my_hostname: String, number: u16) -> SmtpResult { - let mut email = Email::new(); + let mut email_builder = EmailBuilder::new(); for destination in recipient_addresses.iter() { - email.to(destination.as_slice()); + email_builder = email_builder.to(destination.as_slice()); } - email.from(source_address.as_slice()); - email.body(message.as_slice()); - email.subject(subject.as_slice()); - email.date_now(); + let email = email_builder.from(source_address.as_slice()) + .body(message.as_slice()) + .subject(subject.as_slice()) + .build(); let mut client = ClientBuilder::new((server.as_slice(), port)).hello_name(my_hostname) .enable_connection_reuse(true).build(); diff --git a/src/client/mod.rs b/src/client/mod.rs index d325f66..9e60127 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -330,7 +330,7 @@ impl Client { self.server_info = Some( ServerInfo{ name: get_first_word(result.message.as_ref().unwrap().as_slice()).to_string(), - esmtp_features: vec!(), + esmtp_features: vec![], } ); Ok(result) @@ -384,7 +384,7 @@ impl Client { /// Sends an AUTH command with PLAIN mecanism fn auth_plain(&mut self, username: &str, password: &str) -> SmtpResult { - let auth_string = format!("{}{}{}{}{}", "", NUL, username, NUL, password); + let auth_string = format!("{}{}{}{}", NUL, username, NUL, password); self.command(format!("AUTH PLAIN {}", auth_string.as_bytes().to_base64(base64::STANDARD)).as_slice(), [235].iter()) } diff --git a/src/client/server_info.rs b/src/client/server_info.rs index 9d7ebf6..11c2df3 100644 --- a/src/client/server_info.rs +++ b/src/client/server_info.rs @@ -63,7 +63,7 @@ mod test { }), "name with [EightBitMime]".to_string()); assert_eq!(format!("{}", ServerInfo{ name: "name".to_string(), - esmtp_features: vec!() + esmtp_features: vec![] }), "name with no supported features".to_string()); } diff --git a/src/extension.rs b/src/extension.rs index dcd9d28..ee9e35e 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -45,11 +45,11 @@ impl Extension { fn from_str(s: &str) -> Result, &'static str> { let splitted : Vec<&str> = s.split(' ').collect(); match (splitted[0], splitted.len()) { - ("8BITMIME", 1) => Ok(vec!(EightBitMime)), - ("SMTPUTF8", 1) => Ok(vec!(SmtpUtfEight)), - ("STARTTLS", 1) => Ok(vec!(StartTls)), + ("8BITMIME", 1) => Ok(vec![EightBitMime]), + ("SMTPUTF8", 1) => Ok(vec![SmtpUtfEight]), + ("STARTTLS", 1) => Ok(vec![StartTls]), ("AUTH", _) => { - let mut mecanisms: Vec = vec!(); + let mut mecanisms: Vec = vec![]; for &mecanism in &splitted[1..] { match mecanism { "PLAIN" => mecanisms.push(PlainAuthentication), @@ -83,24 +83,24 @@ mod test { #[test] fn test_from_str() { - assert_eq!(Extension::from_str("8BITMIME"), Ok(vec!(Extension::EightBitMime))); - assert_eq!(Extension::from_str("AUTH PLAIN"), Ok(vec!(Extension::PlainAuthentication))); - assert_eq!(Extension::from_str("AUTH PLAIN LOGIN CRAM-MD5"), Ok(vec!(Extension::PlainAuthentication, Extension::CramMd5Authentication))); - assert_eq!(Extension::from_str("AUTH CRAM-MD5 PLAIN"), Ok(vec!(Extension::CramMd5Authentication, Extension::PlainAuthentication))); - assert_eq!(Extension::from_str("AUTH DIGEST-MD5 PLAIN CRAM-MD5"), Ok(vec!(Extension::PlainAuthentication, Extension::CramMd5Authentication))); + assert_eq!(Extension::from_str("8BITMIME"), Ok(vec![Extension::EightBitMime])); + assert_eq!(Extension::from_str("AUTH PLAIN"), Ok(vec![Extension::PlainAuthentication])); + assert_eq!(Extension::from_str("AUTH PLAIN LOGIN CRAM-MD5"), Ok(vec![Extension::PlainAuthentication, Extension::CramMd5Authentication])); + assert_eq!(Extension::from_str("AUTH CRAM-MD5 PLAIN"), Ok(vec![Extension::CramMd5Authentication, Extension::PlainAuthentication])); + assert_eq!(Extension::from_str("AUTH DIGEST-MD5 PLAIN CRAM-MD5"), Ok(vec![Extension::PlainAuthentication, Extension::CramMd5Authentication])); } #[test] fn test_parse_esmtp_response() { assert_eq!(Extension::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 SIZE 42"), - vec!(Extension::EightBitMime)); + vec![Extension::EightBitMime]); assert_eq!(Extension::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 AUTH PLAIN CRAM-MD5\r\n250 UNKNON 42"), - vec!(Extension::EightBitMime, Extension::PlainAuthentication, Extension::CramMd5Authentication)); + vec![Extension::EightBitMime, Extension::PlainAuthentication, Extension::CramMd5Authentication]); assert_eq!(Extension::parse_esmtp_response("me\r\n250-9BITMIME\r\n250 SIZE a"), - vec!()); + vec![]); assert_eq!(Extension::parse_esmtp_response("me\r\n250-SIZE 42\r\n250 SIZE 43"), - vec!()); + vec![]); assert_eq!(Extension::parse_esmtp_response(""), - vec!()); + vec![]); } } diff --git a/src/lib.rs b/src/lib.rs index 9e94f91..37ac573 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,16 +9,20 @@ //! # Rust SMTP client //! -//! The client should tend to follow [RFC 5321](https://tools.ietf.org/html/rfc5321), but is still -//! a work in progress. +//! This client should tend to follow [RFC 5321](https://tools.ietf.org/html/rfc5321), but is still +//! a work in progress. It is designed to efficiently send emails from a rust application to a +//! relay email server. //! -//! It should eventually implement the following extensions : +//! It implements the following extensions : //! //! * 8BITMIME ([RFC 6152](https://tools.ietf.org/html/rfc6152)) -//! * SMTPUTF8 ([RFC 6531](http://tools.ietf.org/html/rfc6531)) -//! * STARTTLS ([RFC 2487](http://tools.ietf.org/html/rfc2487)) //! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954)) //! +//! It will eventually implement the following extensions : +//! +//! * STARTTLS ([RFC 2487](http://tools.ietf.org/html/rfc2487)) +//! * SMTPUTF8 ([RFC 6531](http://tools.ietf.org/html/rfc6531)) +//! //! ## Usage //! //! ### Simple example @@ -27,17 +31,17 @@ //! //! ```rust,no_run //! use smtp::client::ClientBuilder; -//! use smtp::mailer::Email; +//! use smtp::mailer::EmailBuilder; //! //! // Create an email -//! let mut email = Email::new(); -//! // Addresses can be specified by the couple (email, alias) -//! email.to(("user@example.org", "Firstname Lastname")); -//! // ... or by an address only -//! email.from("user@example.com"); -//! email.subject("Hello world"); -//! email.body("Hi, Hello world."); -//! email.date_now(); +//! let email = EmailBuilder::new() +//! // Addresses can be specified by the couple (email, alias) +//! .to(("user@example.org", "Firstname Lastname")) +//! // ... or by an address only +//! .from("user@example.com") +//! .subject("Hi, Hello world") +//! .body("Hello world.") +//! .build(); //! //! // Open a local connection on port 25 //! let mut client = ClientBuilder::localhost().build(); @@ -51,26 +55,29 @@ //! //! ```rust,no_run //! use smtp::client::ClientBuilder; -//! use smtp::mailer::Email; +//! use smtp::mailer::EmailBuilder; //! -//! let mut email = Email::new(); -//! email.to(("user@example.org", "Alias name")); -//! email.cc(("user@example.net", "Alias name")); -//! email.from("no-reply@example.com"); -//! email.from("no-reply@example.eu"); -//! email.sender("no-reply@example.com"); -//! email.subject("Hello world"); -//! email.body("Hi, Hello world."); -//! email.reply_to("contact@example.com"); -//! email.add_header(("X-Custom-Header", "my header")); -//! email.date_now(); +//! let mut builder = EmailBuilder::new(); +//! builder = builder.to(("user@example.org", "Alias name")); +//! builder = builder.cc(("user@example.net", "Alias name")); +//! builder = builder.from("no-reply@example.com"); +//! builder = builder.from("no-reply@example.eu"); +//! builder = builder.sender("no-reply@example.com"); +//! builder = builder.subject("Hello world"); +//! builder = builder.body("Hi, Hello world."); +//! builder = builder.reply_to("contact@example.com"); +//! builder = builder.add_header(("X-Custom-Header", "my header")); +//! +//! let email = builder.build(); //! //! // Connect to a remote server on a custom port //! let mut client = ClientBuilder::new(("server.tld", 10025)) -//! // Set the name sent during EHLO/HELO, default is `localhost` -//! .hello_name("my.hostname.tld".to_string()) -//! // Enable connection reuse -//! .enable_connection_reuse(true).build(); +//! // Set the name sent during EHLO/HELO, default is `localhost` +//! .hello_name("my.hostname.tld".to_string()) +//! // Add credentials for authentication +//! .credentials("username".to_string(), "password".to_string()) +//! // Enable connection reuse +//! .enable_connection_reuse(true).build(); //! let result_1 = client.send(email.clone()); //! assert!(result_1.is_ok()); //! // The second email will use the same connection diff --git a/src/mailer/mod.rs b/src/mailer/mod.rs index a655b09..a25e5aa 100644 --- a/src/mailer/mod.rs +++ b/src/mailer/mod.rs @@ -21,6 +21,15 @@ use sendable_email::SendableEmail; pub mod header; pub mod address; +/// TODO +#[derive(PartialEq,Eq,Clone,Debug)] +pub struct EmailBuilder { + /// Email content + content: Email, + /// Date issued + date_issued: bool, +} + /// Simple email representation #[derive(PartialEq,Eq,Clone,Debug)] pub struct Email { @@ -45,87 +54,100 @@ impl Display for Email { } } -impl Email { +impl EmailBuilder { /// Creates a new empty email - pub fn new() -> Email { - Email{headers: vec!(), body: "".to_string(), to: vec!(), from: None} - } - - /// Clear the email content - pub fn clear(&mut self) { - self.headers.clear(); - self.body = "".to_string(); - self.to.clear(); - self.from = None; + pub fn new() -> EmailBuilder { + EmailBuilder { + content: Email { + headers: vec![], + body: "".to_string(), + to: vec![], + from: None, + }, + date_issued: false, + } } /// Sets the email body - pub fn body(&mut self, body: &str) { - self.body = body.to_string(); + pub fn body(mut self, body: &str) -> EmailBuilder { + self.content.body = body.to_string(); + self } /// Add a generic header - pub fn add_header(&mut self, header: A) { - self.headers.push(header.to_header()); + pub fn add_header(mut self, header: A) -> EmailBuilder { + self.content.headers.push(header.to_header()); + self } /// Adds a `From` header and store the sender address - pub fn from(&mut self, address: A) { - self.from = Some(address.to_address().get_address()); - self.headers.push( + pub fn from(mut self, address: A) -> EmailBuilder { + self.content.from = Some(address.to_address().get_address()); + self.content.headers.push( Header::From(address.to_address()) ); + self } /// Adds a `To` header and store the recipient address - pub fn to(&mut self, address: A) { - self.to.push(address.to_address().get_address()); - self.headers.push( + pub fn to(mut self, address: A) -> EmailBuilder { + self.content.to.push(address.to_address().get_address()); + self.content.headers.push( Header::To(address.to_address()) ); + self } /// Adds a `Cc` header and store the recipient address - pub fn cc(&mut self, address: A) { - self.to.push(address.to_address().get_address()); - self.headers.push( + pub fn cc(mut self, address: A) -> EmailBuilder { + self.content.to.push(address.to_address().get_address()); + self.content.headers.push( Header::Cc(address.to_address()) ); + self } /// Adds a `Reply-To` header - pub fn reply_to(&mut self, address: A) { - self.headers.push( + pub fn reply_to(mut self, address: A) -> EmailBuilder { + self.content.headers.push( Header::ReplyTo(address.to_address()) ); + self } /// Adds a `Sender` header - pub fn sender(&mut self, address: A) { - self.headers.push( + pub fn sender(mut self, address: A) -> EmailBuilder { + self.content.headers.push( Header::Sender(address.to_address()) ); + self } /// Adds a `Subject` header - pub fn subject(&mut self, subject: &str) { - self.headers.push( + pub fn subject(mut self, subject: &str) -> EmailBuilder { + self.content.headers.push( Header::Subject(subject.to_string()) ); - } - - /// Adds a `Date` header with the current date - pub fn date_now(&mut self) { - self.headers.push( - Header::Date(now()) - ); + self } /// Adds a `Date` header with the given date - pub fn date(&mut self, date: Tm) { - self.headers.push( + pub fn date(mut self, date: Tm) -> EmailBuilder { + self.content.headers.push( Header::Date(date) ); + self.date_issued = true; + self + } + + /// Build the Email + pub fn build(mut self) -> Email { + if !self.date_issued { + self.content.headers.push( + Header::Date(now()) + ); + } + self.content } } @@ -160,50 +182,73 @@ impl SendableEmail for Email { #[cfg(test)] mod test { - use super::Email; + use super::{Email, EmailBuilder}; use mailer::header::Header; #[test] fn test_new() { assert_eq!( - Email::new(), - Email{headers: vec!(), body: "".to_string(), to: vec!(), from: None} + EmailBuilder::new(), + EmailBuilder{content: Email{headers: vec![], body: "".to_string(), to: vec![], from: None}, date_issued: false} ) } #[test] fn test_body() { - let mut email = Email::new(); - email.body("test message"); + let email = EmailBuilder::new().body("test message"); assert_eq!( email, - Email{headers: vec!(), body: "test message".to_string(), to: vec!(), from: None} + EmailBuilder{content: Email {headers: vec![], body: "test message".to_string(), to: vec![], from: None}, date_issued: false} ) } #[test] fn test_add_header() { - let mut email = Email::new(); - email.add_header(("X-My-Header", "value")); + let mut email = EmailBuilder::new() + .add_header(("X-My-Header", "value")); assert_eq!( email, - Email{ - headers: vec!(Header::new("X-My-Header", "value")), - body: "".to_string(), - to: vec!(), - from: None + EmailBuilder{ + content: Email { + headers: vec![Header::new("X-My-Header", "value")], + body: "".to_string(), + to: vec![], + from: None + }, + date_issued: false, } ); - email.add_header(("X-My-Header-2", "value-2")); + email = email.add_header(("X-My-Header-2", "value-2")); assert_eq!( email, - Email{ - headers: vec!(Header::new("X-My-Header", "value"), - Header::new("X-My-Header-2", "value-2")), - body: "".to_string(), - to: vec!(), - from: None + EmailBuilder{ + content: Email { + headers: vec![Header::new("X-My-Header", "value"), + Header::new("X-My-Header-2", "value-2")], + body: "".to_string(), + to: vec![], + from: None + }, + date_issued: false, + } + ); + email = email.add_header(("X-My-Header-3", "value-3")).add_header(("X-My-Header-4", "value-4")); + assert_eq!( + email, + EmailBuilder{ + content: Email { + headers: vec![Header::new("X-My-Header", "value"), + Header::new("X-My-Header-2", "value-2"), + Header::new("X-My-Header-3", "value-3"), + Header::new("X-My-Header-4", "value-4")], + body: "".to_string(), + to: vec![], + from: None + }, + date_issued: false, } ); } + + // TODO test Email } diff --git a/src/sendable_email.rs b/src/sendable_email.rs index 6e5d199..35775bb 100644 --- a/src/sendable_email.rs +++ b/src/sendable_email.rs @@ -36,7 +36,7 @@ impl SimpleSendableEmail { pub fn new(from_address: &str, to_address: &str, message: &str) -> SimpleSendableEmail { SimpleSendableEmail { from: from_address.to_string(), - to: vec!(to_address.to_string()), + to: vec![to_address.to_string()], message: message.to_string(), } }