Email builder
This commit is contained in:
@@ -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<String>, 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();
|
||||
|
||||
@@ -330,7 +330,7 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
|
||||
|
||||
/// 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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ impl Extension {
|
||||
fn from_str(s: &str) -> Result<Vec<Extension>, &'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<Extension> = vec!();
|
||||
let mut mecanisms: Vec<Extension> = 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![]);
|
||||
}
|
||||
}
|
||||
|
||||
67
src/lib.rs
67
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
|
||||
|
||||
@@ -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<A: ToHeader>(&mut self, header: A) {
|
||||
self.headers.push(header.to_header());
|
||||
pub fn add_header<A: ToHeader>(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<A: ToAddress>(&mut self, address: A) {
|
||||
self.from = Some(address.to_address().get_address());
|
||||
self.headers.push(
|
||||
pub fn from<A: ToAddress>(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<A: ToAddress>(&mut self, address: A) {
|
||||
self.to.push(address.to_address().get_address());
|
||||
self.headers.push(
|
||||
pub fn to<A: ToAddress>(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<A: ToAddress>(&mut self, address: A) {
|
||||
self.to.push(address.to_address().get_address());
|
||||
self.headers.push(
|
||||
pub fn cc<A: ToAddress>(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<A: ToAddress>(&mut self, address: A) {
|
||||
self.headers.push(
|
||||
pub fn reply_to<A: ToAddress>(mut self, address: A) -> EmailBuilder {
|
||||
self.content.headers.push(
|
||||
Header::ReplyTo(address.to_address())
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a `Sender` header
|
||||
pub fn sender<A: ToAddress>(&mut self, address: A) {
|
||||
self.headers.push(
|
||||
pub fn sender<A: ToAddress>(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
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user