From 8811b321133c9e5b59306cc12aabb7dee2bc563a Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Sat, 27 Jun 2015 02:31:24 +0200 Subject: [PATCH] Beginning of rust-email integration --- .travis.yml | 6 +- Cargo.toml | 5 +- src/client/mod.rs | 4 +- src/lib.rs | 7 +- src/mailer/address.rs | 104 ------------------- src/mailer/header.rs | 140 -------------------------- src/mailer/mod.rs | 226 +++++++++++++++++------------------------- src/sendable_email.rs | 10 +- src/sender/mod.rs | 9 +- 9 files changed, 114 insertions(+), 397 deletions(-) delete mode 100644 src/mailer/address.rs delete mode 100644 src/mailer/header.rs diff --git a/.travis.yml b/.travis.yml index 1bd9c9f..9db4cb4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: rust sudo: required rust: + - stable + - beta - nightly before_script: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH @@ -11,8 +13,8 @@ script: travis-cargo test && travis-cargo doc after_success: - - travis-cargo doc-upload - - travis-cargo coveralls + - travis-cargo --only stable doc-upload + - travis-cargo --only stable coveralls env: global: diff --git a/Cargo.toml b/Cargo.toml index 21a552d..7564245 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,13 @@ uuid = "*" log = "*" rustc-serialize = "*" rust-crypto = "*" +bufstream = "*" + +[dependencies.email] +git = "https://github.com/amousset/rust-email.git" [dev-dependencies] env_logger = "*" - [features] unstable = [] diff --git a/src/client/mod.rs b/src/client/mod.rs index 17257c1..9cf251b 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -11,7 +11,9 @@ use std::string::String; use std::net::{SocketAddr, ToSocketAddrs}; -use std::io::{BufRead, BufStream, Read, Write}; +use std::io::{BufRead, Read, Write}; + +use bufstream::BufStream; use response::{Response, Severity, Category}; use error::SmtpResult; diff --git a/src/lib.rs b/src/lib.rs index 05a02ff..8f9fdc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,7 @@ //! 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")); +//! //builder = builder.add_header(("X-Custom-Header", "my header")); //! //! let email = builder.build(); //! @@ -138,14 +138,15 @@ //! let _ = email_client.quit(); //! ``` -#![feature(buf_stream)] -#![deny(missing_docs)] +//#![deny(missing_docs)] #[macro_use] extern crate log; extern crate rustc_serialize as serialize; extern crate crypto; extern crate time; extern crate uuid; +extern crate email; +extern crate bufstream; mod extension; pub mod client; diff --git a/src/mailer/address.rs b/src/mailer/address.rs deleted file mode 100644 index 8518564..0000000 --- a/src/mailer/address.rs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2014 Alexis Mousset. See the COPYRIGHT -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Simple SMTP "address" (very incomplete) - -use std::fmt::{Display, Formatter, Result}; - -use SP; - -/// Converts an adress or an address with an alias to an `Address` -pub trait ToAddress { - /// Converts to an `Address` struct - fn to_address(&self) -> Address; -} - -impl ToAddress for Address { - fn to_address(&self) -> Address { - (*self).clone() - } -} - -impl<'a> ToAddress for &'a str { - fn to_address(&self) -> Address { - Address::new(*self, None) - } -} - -impl<'a> ToAddress for (&'a str, &'a str) { - fn to_address(&self) -> Address { - let (address, alias) = *self; - Address::new(address, Some(alias)) - } -} - -/// Contains an address with an optionnal alias -#[derive(PartialEq,Eq,Clone,Debug)] -pub struct Address { - /// The address - address: String, - /// The alias - alias: Option, -} - -impl Display for Address { - fn fmt(&self, f: &mut Formatter) -> Result { - write! (f, "{}", match self.alias { - Some(ref alias_string) => format!("{}{}<{}>", alias_string, SP, &self.address), - None => self.address.clone(), - }) - } -} - -impl Address { - /// Creates an address - pub fn new(address: &str, alias: Option<&str>) -> Address { - Address { - address: address.to_string(), - alias: match alias { - Some(ref alias_string) => Some(alias_string.to_string()), - None => None, - } - } - } - - /// Return only the address - pub fn get_address(&self) -> String { - self.address.clone() - } -} - -#[cfg(test)] -mod test { - use super::Address; - - #[test] - fn test_new() { - assert_eq!( - Address::new("address", Some("alias")), - Address{address: "address".to_string(), alias: Some("alias".to_string())} - ); - assert_eq!( - Address::new("address", None), - Address{address: "address".to_string(), alias: None} - ); - } - - #[test] - fn test_fmt() { - assert_eq!( - format!("{}", Address::new("address", None)), - "address".to_string() - ); - assert_eq!( - format!("{}", Address::new("address", Some("alias"))), - "alias
".to_string() - ); - } -} diff --git a/src/mailer/header.rs b/src/mailer/header.rs deleted file mode 100644 index ab8e2b6..0000000 --- a/src/mailer/header.rs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2014 Alexis Mousset. See the COPYRIGHT -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Simple SMTP headers - -use time::Tm; - -use std::fmt::{Display, Formatter, Result}; - -use mailer::address::Address; -use {COLON, SP}; - -/// Converts to an `Header` -pub trait ToHeader { - /// Converts to an `Header` struct - fn to_header(&self) -> Header; -} - -impl ToHeader for Header { - fn to_header(&self) -> Header { - (*self).clone() - } -} - -impl<'a> ToHeader for (&'a str, &'a str) { - fn to_header(&self) -> Header { - let (name, value) = *self; - Header::new(name, value) - } -} - -/// Contains a header -#[derive(PartialEq,Eq,Clone,Debug)] -pub enum Header { - /// `To` - To(Address), - /// `From` - From(Address), - /// `Cc` - Cc(Address), - /// `Reply-To` - ReplyTo(Address), - /// `Sender` - Sender(Address), - /// `Date` - Date(Tm), - /// `Subject` - Subject(String), - /// `MIME-Version` - MimeVersion, - /// `Content-Type` - ContentType(String), - /// `Message-Id` - MessageId(String), - /// Any header (name, value) - Other(String, String), -} - -impl Display for Header { - fn fmt(&self, f: &mut Formatter) -> Result { - write! (f, "{}{}{}{}", - match *self { - Header::To(_) => "To", - Header::From(_) => "From", - Header::Cc(_) => "Cc", - Header::ReplyTo(_) => "Reply-To", - Header::Sender(_) => "Sender", - Header::Date(_) => "Date", - Header::Subject(_) => "Subject", - Header::MimeVersion => "MIME-Version", - Header::ContentType(_) => "Content-Type", - Header::MessageId(_) => "Message-Id", - Header::Other(ref name, _) => name.as_ref(), - }, - COLON, SP, - match *self { - Header::To(ref address) => format! ("{}", address), - Header::From(ref address) => format! ("{}", address), - Header::Cc(ref address) => format! ("{}", address), - Header::ReplyTo(ref address) => format! ("{}", address), - Header::Sender(ref address) => format! ("{}", address), - Header::Date(ref date) => Tm::rfc822(date).to_string(), - Header::Subject(ref subject) => subject.clone(), - Header::MimeVersion => "1.0".to_string(), - Header::ContentType(ref string) => string.clone(), - Header::MessageId(ref string) => string.clone(), - Header::Other(_, ref value) => value.clone(), - }) - } -} - -impl Header { - /// Creates ah `Header` - pub fn new(name: &str, value: &str) -> Header { - Header::Other(name.to_string(), value.to_string()) - } -} - -#[cfg(test)] -mod test { - use super::Header; - use mailer::address::Address; - - use time::{at_utc, Timespec}; - - #[test] - fn test_new() { - assert_eq!( - Header::new("From", "me"), - Header::Other("From".to_string(), "me".to_string()) - ); - } - - #[test] - fn test_fmt() { - assert_eq!( - format!("{}", Header::new("From", "me")), - "From: me".to_string() - ); - assert_eq!( - format!("{}", Header::To(Address::new("me@example.com", Some("My Name")))), - "To: My Name ".to_string() - ); - assert_eq!( - format!("{}", Header::Subject("Test subject".to_string())), - "Subject: Test subject".to_string() - ); - let time = at_utc(Timespec::new(1234567890, 54321)); - assert_eq!( - format!("{}", Header::Date(time)), - "Date: Fri, 13 Feb 2009 23:31:30 GMT".to_string() - ); - } -} diff --git a/src/mailer/mod.rs b/src/mailer/mod.rs index a5489a1..e6cf0ea 100644 --- a/src/mailer/mod.rs +++ b/src/mailer/mod.rs @@ -9,17 +9,55 @@ //! Simple email (very incomplete) -use std::fmt::{Display, Formatter, Result}; - +use email::{MimeMessage, Header, Address}; use time::{now, Tm}; +use uuid::Uuid; -use mailer::header::{ToHeader, Header}; -use mailer::address::ToAddress; use sendable_email::SendableEmail; -use CRLF; -pub mod header; -pub mod address; +/// Converts an adress or an address with an alias to an `Address` +pub trait ToHeader { + /// Converts to an `Address` struct + fn to_header(&self) -> Header; +} + +impl ToHeader for Header { + fn to_header(&self) -> Header { + (*self).clone() + } +} + +impl<'a> ToHeader for (&'a str, &'a str) { + fn to_header(&self) -> Header { + let (name, value) = *self; + Header::new(name.to_string(), value.to_string()) + } +} + +/// Converts an adress or an address with an alias to an `Address` +pub trait ToAddress { + /// Converts to an `Address` struct + fn to_address(&self) -> Address; +} + +impl ToAddress for Address { + fn to_address(&self) -> Address { + (*self).clone() + } +} + +impl<'a> ToAddress for &'a str { + fn to_address(&self) -> Address { + Address::new_mailbox(self.to_string()) + } +} + +impl<'a> ToAddress for (&'a str, &'a str) { + fn to_address(&self) -> Address { + let (address, alias) = *self; + Address::new_mailbox_with_name(address.to_string(), alias.to_string()) + } +} /// TODO #[derive(PartialEq,Eq,Clone,Debug)] @@ -33,110 +71,105 @@ pub struct EmailBuilder { /// Simple email representation #[derive(PartialEq,Eq,Clone,Debug)] pub struct Email { - /// Array of headers - headers: Vec
, - /// Message body - body: String, + /// Message + message: MimeMessage, /// The enveloppe recipients addresses to: Vec, /// The enveloppe sender address from: Option, + /// Message-ID + message_id: Uuid, } -impl Display for Email { - fn fmt(&self, f: &mut Formatter) -> Result { - let mut formatted_headers = String::new(); - for header in self.headers.iter() { - formatted_headers.push_str(&format! ("{}", header)); - formatted_headers.push_str(CRLF); - } - write! (f, "{}{}{}", formatted_headers, CRLF, self.body) +impl Email { + pub fn as_string(&self) -> String { + self.message.as_string() } } impl EmailBuilder { /// Creates a new empty email pub fn new() -> EmailBuilder { + let current_message = Uuid::new_v4(); + + let mut email = Email { + message: MimeMessage::new_blank_message(), + to: vec![], + from: None, + message_id: current_message, + }; + + email.message.headers.insert( + Header::new_with_value("Message-ID".to_string(), + format!("<{}@rust-smtp>", current_message) + ).unwrap() + ); + EmailBuilder { - content: Email { - headers: vec![], - body: "".to_string(), - to: vec![], - from: None, - }, + content: email, date_issued: false, } } /// Sets the email body pub fn body(mut self, body: &str) -> EmailBuilder { - self.content.body = body.to_string(); + self.content.message.body = body.to_string(); self } /// Add a generic header pub fn add_header(mut self, header: A) -> EmailBuilder { - self.content.headers.push(header.to_header()); + self.insert_header(header); self } + fn insert_header(&mut self, header: A) { + self.content.message.headers.insert(header.to_header()); + } + /// Adds a `From` header and store the sender address 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.content.from = Some(address.to_address().get_address().unwrap()); + self.insert_header(("From", address.to_address().to_string().as_ref())); self } /// Adds a `To` header and store the recipient address 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.content.to.push(address.to_address().get_address().unwrap()); + self.insert_header(("To", address.to_address().to_string().as_ref())); self } /// Adds a `Cc` header and store the recipient address 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.content.to.push(address.to_address().get_address().unwrap()); + self.insert_header(("Cc", address.to_address().to_string().as_ref())); self } /// Adds a `Reply-To` header pub fn reply_to(mut self, address: A) -> EmailBuilder { - self.content.headers.push( - Header::ReplyTo(address.to_address()) - ); + self.insert_header(("Reply-To", address.to_address().to_string().as_ref())); self } /// Adds a `Sender` header pub fn sender(mut self, address: A) -> EmailBuilder { - self.content.from = Some(address.to_address().get_address()); - self.content.headers.push( - Header::Sender(address.to_address()) - ); + self.content.from = Some(address.to_address().get_address().unwrap()); + self.insert_header(("Sender", address.to_address().to_string().as_ref())); self } /// Adds a `Subject` header pub fn subject(mut self, subject: &str) -> EmailBuilder { - self.content.headers.push( - Header::Subject(subject.to_string()) - ); + self.insert_header(("Subject", subject)); self } /// Adds a `Date` header with the given date - pub fn date(mut self, date: Tm) -> EmailBuilder { - self.content.headers.push( - Header::Date(date) - ); + pub fn date(mut self, date: &Tm) -> EmailBuilder { + self.insert_header(("Date", Tm::rfc822(date).to_string().as_ref())); self.date_issued = true; self } @@ -144,10 +177,9 @@ impl EmailBuilder { /// Build the Email pub fn build(mut self) -> Email { if !self.date_issued { - self.content.headers.push( - Header::Date(now()) - ); + self.insert_header(("Date", Tm::rfc822(&now()).to_string().as_ref())); } + self.content.message.update_headers(); self.content } } @@ -170,86 +202,10 @@ impl SendableEmail for Email { } fn message(&self) -> String { - format! ("{}", self) + format! ("{}", self.as_string()) } - /// Adds a `Message-ID` header - fn set_message_id(&mut self, string: String) { - self.headers.push( - Header::MessageId(string) - ); + fn message_id(&self) -> String { + format!("{}", self.message_id) } } - -#[cfg(test)] -mod test { - use super::{Email, EmailBuilder}; - use mailer::header::Header; - - #[test] - fn test_new() { - assert_eq!( - EmailBuilder::new(), - EmailBuilder{content: Email{headers: vec![], body: "".to_string(), to: vec![], from: None}, date_issued: false} - ) - } - - #[test] - fn test_body() { - let email = EmailBuilder::new().body("test message"); - assert_eq!( - email, - 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 = EmailBuilder::new() - .add_header(("X-My-Header", "value")); - assert_eq!( - email, - EmailBuilder{ - content: Email { - headers: vec![Header::new("X-My-Header", "value")], - body: "".to_string(), - to: vec![], - from: None - }, - date_issued: false, - } - ); - email = email.add_header(("X-My-Header-2", "value-2")); - assert_eq!( - email, - 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 35775bb..584055a 100644 --- a/src/sendable_email.rs +++ b/src/sendable_email.rs @@ -9,6 +9,8 @@ //! SMTP sendable email +use uuid::Uuid; + /// Email sendable by an SMTP client pub trait SendableEmail { /// From address @@ -17,8 +19,8 @@ pub trait SendableEmail { fn to_addresses(&self) -> Vec; /// Message content fn message(&self) -> String; - /// Set message-ID header - fn set_message_id(&mut self, id: String); + /// Message ID + fn message_id(&self) -> String; } /// Minimal email structure @@ -55,7 +57,7 @@ impl SendableEmail for SimpleSendableEmail { self.message.clone() } - fn set_message_id(&mut self, id: String) { - let _ = id; + fn message_id(&self) -> String { + format!("<{}@rust-smtp>", Uuid::new_v4()) } } diff --git a/src/sender/mod.rs b/src/sender/mod.rs index 3a9322d..f9ff9f0 100644 --- a/src/sender/mod.rs +++ b/src/sender/mod.rs @@ -12,8 +12,6 @@ use std::string::String; use std::net::{SocketAddr, ToSocketAddrs}; -use uuid::Uuid; - use SMTP_PORT; use extension::Extension; use error::{SmtpResult, SmtpError}; @@ -161,7 +159,7 @@ impl Sender { } /// Sends an email - pub fn send(&mut self, mut email: T) -> SmtpResult { + pub fn send(&mut self, email: T) -> SmtpResult { // Check if the connection is still available if self.state.connection_reuse_count > 0 { if !self.client.is_connected() { @@ -223,10 +221,7 @@ impl Sender { } } - let current_message = Uuid::new_v4(); - email.set_message_id(format!("<{}@{}>", current_message, - self.client_info.hello_name.clone())); - + let current_message = email.message_id(); let from_address = email.from_address(); let to_addresses = email.to_addresses(); let message = email.message();