From 9b3f5c2f1c7c8be4d33d9a1c4010543f4affb5b6 Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Fri, 5 Dec 2014 00:49:59 +0100 Subject: [PATCH] Begin to work on email content --- Cargo.toml | 3 ++ examples/client.rs | 13 ++++-- src/client/mod.rs | 16 +++---- src/common.rs | 3 ++ src/email/address.rs | 106 ++++++++++++++++++++++++++++++++++++++++++ src/email/header.rs | 63 +++++++++++++++++++++++++ src/email/mod.rs | 108 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 11 +++-- src/tools.rs | 47 +------------------ 9 files changed, 308 insertions(+), 62 deletions(-) create mode 100644 src/email/address.rs create mode 100644 src/email/header.rs create mode 100644 src/email/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 7d8dea6..668e113 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,6 @@ license = "MIT/Apache-2.0" readme = "README.md" keywords = ["email", "smtp"] + +[dependencies] +time = "*" diff --git a/examples/client.rs b/examples/client.rs index eb67900..54dd9b0 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -7,32 +7,35 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![feature(default_type_params)] #![feature(phase)] #[phase(plugin, link)] extern crate log; + extern crate smtp; extern crate getopts; use std::io::stdin; -use std::io::net::tcp::TcpStream; use std::string::String; use std::io::net::ip::Port; use std::os; use getopts::{optopt, optflag, getopts, OptGroup, usage}; +use std::io::net::tcp::TcpStream; use smtp::client::Client; use smtp::error::SmtpResult; +//use smtp::email::Email; fn sendmail(source_address: &str, recipient_addresses: &[&str], message: &str, server: &str, port: Port, my_hostname: &str) -> SmtpResult { - let mut email_client: Client = + let mut email_client = Client::new( (server, port), Some(my_hostname), ); email_client.send_mail::( - source_address, - recipient_addresses, - message, + source_address, + recipient_addresses, + message, ) } diff --git a/src/client/mod.rs b/src/client/mod.rs index d4177d9..2ea45aa 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -11,10 +11,10 @@ use std::string::String; use std::error::FromError; -use std::io::net::ip::SocketAddr; -use std::io::net::ip::ToSocketAddr; +use std::io::net::tcp::TcpStream; +use std::io::net::ip::{SocketAddr, ToSocketAddr}; -use tools::{get_first_word, unquote_email_address}; +use tools::get_first_word; use common::{CRLF, MESSAGE_ENDING}; use response::Response; use extension::Extension; @@ -30,7 +30,7 @@ pub mod connecter; pub mod stream; /// Structure that implements the SMTP client -pub struct Client { +pub struct Client { /// TCP stream between client and server /// Value is None before connection stream: Option, @@ -71,7 +71,7 @@ macro_rules! check_command_sequence ( }) ) -impl Client { +impl Client { /// Creates a new SMTP client /// /// It does not connects to the server, but only create the `Client` @@ -86,7 +86,7 @@ impl Client { } } -impl Client { +impl Client { /// Closes the SMTP transaction if possible, and then closes the TCP session fn close_on_error(&mut self) { if self.is_connected::() { @@ -267,14 +267,14 @@ impl Client { }; self.send_command( - Command::Mail(unquote_email_address(from_address).to_string(), options) + Command::Mail(from_address.to_string(), options) ) } /// Sends a RCPT command pub fn rcpt(&mut self, to_address: &str) -> SmtpResult { self.send_command( - Command::Recipient(unquote_email_address(to_address).to_string(), None) + Command::Recipient(to_address.to_string(), None) ) } diff --git a/src/common.rs b/src/common.rs index 25af925..31bcf6c 100644 --- a/src/common.rs +++ b/src/common.rs @@ -37,5 +37,8 @@ pub static CR: &'static str = "\r"; /// Line feed pub static LF: &'static str = "\n"; +/// Colon +pub static COLON: &'static str = ":"; + /// The ending of message content pub static MESSAGE_ENDING: &'static str = "\r\n.\r\n"; diff --git a/src/email/address.rs b/src/email/address.rs new file mode 100644 index 0000000..5a5aedc --- /dev/null +++ b/src/email/address.rs @@ -0,0 +1,106 @@ +// 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" + +use std::fmt::{Show, Formatter, Result}; + +use common::SP; + +/// TODO +pub trait ToAddress { + /// TODO + 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)) + } +} + + +/// TODO +#[deriving(PartialEq,Eq,Clone)] +pub struct Address { + /// TODO + address: String, + /// TODO + alias: Option, +} + +impl Show for Address { + fn fmt(&self, f: &mut Formatter) -> Result { + f.write(match self.alias { + Some(ref alias_string) => format!("{}{}<{}>", alias_string, SP, self.address.as_slice()), + None => self.address.clone(), + }.as_bytes()) + } +} + +impl Address { + /// TODO + 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, + } + } + } + + /// TODO + 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/email/header.rs b/src/email/header.rs new file mode 100644 index 0000000..c4e10d6 --- /dev/null +++ b/src/email/header.rs @@ -0,0 +1,63 @@ +// 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 std::fmt::{Show, Formatter, Result}; + +use common::{SP, COLON}; + +/// TODO +#[deriving(PartialEq,Eq,Clone)] +pub struct Header { + /// TODO + name: String, + /// TODO + value: String, +} + +impl Show for Header { + fn fmt(&self, f: &mut Formatter) -> Result { + f.write(format!("{}{}{}{}", self.name, COLON, SP, self.value).as_bytes()) + } +} + +impl Header { + /// TODO + pub fn new(name: &str, value: &str) -> Header { + Header{name: name.to_string(), value: value.to_string()} + } +} + +// impl Str for Header { +// fn as_slice<'a>(&'a self) -> &'a str { +// self.clone().to_string().clone().as_slice() +// } +// } + +#[cfg(test)] +mod test { + use super::Header; + + #[test] + fn test_new() { + assert_eq!( + Header::new("From", "me"), + Header{name: "From".to_string(), value: "me".to_string()} + ); + } + + #[test] + fn test_fmt() { + assert_eq!( + format!("{}", Header::new("From", "me")), + "From: me".to_string() + ); + } +} diff --git a/src/email/mod.rs b/src/email/mod.rs new file mode 100644 index 0000000..bbae639 --- /dev/null +++ b/src/email/mod.rs @@ -0,0 +1,108 @@ +// 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 email + +use std::fmt::{Show, Formatter, Result}; + +use time::{now, Tm}; + +use email::header::Header; +use email::address::ToAddress; +use common::CRLF; +//use client::Client; +//use error::SmtpResult; + +pub mod header; +pub mod address; + +/// TODO +pub struct Email { + /// Array of headers + headers: Vec
, + /// Message body + body: Option, + /// TODO cc bcc to + to: Vec, + /// TODO + from: Option, +} + +impl Show for Email { + fn fmt(&self, f: &mut Formatter) -> Result { + f.write(format!("{}{}{}", self.headers, CRLF, self.body).as_bytes()) + } +} + +impl Email { + /// TODO + // pub fn send(&self, client: Client) -> SmtpResult { + // let test: Vec<&str> = self.to.iter().map(|s| s.as_slice()).collect(); + // //let to_vec: &[&str] = self.to.iter().map(|s| s.as_slice()).collect().as_slice(); + // client.send_mail( + // self.from.unwrap().as_slice(), + // test.as_slice(), + // self.to_string().as_slice(), + // ) + // } + + /// TODO + pub fn new() -> Email { + Email{headers: vec!(), body: None, to: vec!(), from: None} + } + + // TODO : standard headers method + + /// TODO + pub fn body(&mut self, body: &str) { + self.body = Some(body.to_string()); + } + + /// TODO + pub fn add_header(&mut self, header: Header) { + self.headers.push(header); + } + + /// TODO + pub fn from(&mut self, address: A) { + self.from = Some(address.to_address().get_address()); + self.headers.push( + Header::new("From", address.to_address().to_string().as_slice()) + ); + } + + /// TODO + pub fn to(&mut self, address: A) { + self.to.push(address.to_address().get_address()); + self.headers.push( + Header::new("To", address.to_address().to_string().as_slice()) + ); + } + + /// TODO + pub fn reply_to(&mut self, address: A) { + self.headers.push( + Header::new("Return-Path", address.to_address().to_string().as_slice()) + ); + } + + /// TODO + pub fn subject(&mut self, subject: &str) { + self.headers.push( + Header::new("Subject", subject) + ); + } + + /// TODO + pub fn date(&mut self) { + self.headers.push( + Header::new("Date", Tm::rfc822(&now()).to_string().as_slice()) + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 8852111..7186f2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,11 +26,12 @@ //! If you just want to send an email: //! //! ```rust,no_run +//! #![feature(default_type_params)] //! use std::io::net::tcp::TcpStream; //! use smtp::client::Client; //! use smtp::common::SMTP_PORT; //! -//! let mut email_client: Client = +//! let mut email_client = //! Client::new( //! ("localhost", SMTP_PORT), // server socket //! Some("myhost"), // my hostname (default is localhost) @@ -47,11 +48,12 @@ //! You can also send commands, here is a simple email transaction without error handling: //! //! ```rust,no_run +//! #![feature(default_type_params)] //! use std::io::net::tcp::TcpStream; //! use smtp::client::Client; //! use smtp::common::SMTP_PORT; //! -//! let mut email_client: Client = +//! let mut email_client = //! Client::new( //! ("localhost", SMTP_PORT), // server socket //! Some("myhost"), // my hostname (default is localhost) @@ -70,11 +72,13 @@ #![doc(html_root_url = "http://amousset.github.io/rust-smtp/smtp/")] #![experimental] -#![feature(phase, macro_rules, if_let)] +#![feature(phase, macro_rules, if_let, default_type_params)] #![deny(missing_docs, warnings)] #![feature(phase)] #[phase(plugin, link)] extern crate log; +extern crate time; + pub mod client; pub mod command; pub mod extension; @@ -83,3 +87,4 @@ pub mod transaction; pub mod common; pub mod error; pub mod tools; +pub mod email; diff --git a/src/tools.rs b/src/tools.rs index b84a1cc..d9a065e 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -14,32 +14,6 @@ use std::str::replace; use common::{CR, LF, CRLF}; -/// Adds quotes to emails if needed -#[inline] -pub fn quote_email_address(address: &str) -> String { - match address.len() { - 0 ... 1 => format!("<{}>", address), - _ => match (address.slice_to(1), - address.slice_from(address.len() - 1)) { - ("<", ">") => address.to_string(), - _ => format!("<{}>", address), - } - } -} - -/// Removes quotes from emails if needed -#[inline] -pub fn unquote_email_address(address: &str) -> &str { - match address.len() { - 0 ... 1 => address, - _ => match (address.slice_to(1), - address.slice_from(address.len() - 1)) { - ("<", ">") => address.slice(1, address.len() - 1), - _ => address, - } - } -} - /// Removes the trailing line return at the end of a string #[inline] pub fn remove_trailing_crlf(string: &str) -> &str { @@ -79,26 +53,7 @@ pub fn escape_dot(string: &str) -> String { #[cfg(test)] mod test { - use super::{quote_email_address, unquote_email_address, - remove_trailing_crlf, get_first_word, escape_crlf, escape_dot}; - - #[test] - fn test_quote_email_address() { - assert_eq!(quote_email_address("address").as_slice(), "
"); - assert_eq!(quote_email_address("
").as_slice(), "
"); - assert_eq!(quote_email_address("a").as_slice(), ""); - assert_eq!(quote_email_address("").as_slice(), "<>"); - } - - #[test] - fn test_unquote_email_address() { - assert_eq!(unquote_email_address("
"), "address"); - assert_eq!(unquote_email_address("address"), "address"); - assert_eq!(unquote_email_address(""), ""); - assert_eq!(unquote_email_address("a"), "a"); - assert_eq!(unquote_email_address(""), ""); - } + use super::{remove_trailing_crlf, get_first_word, escape_crlf, escape_dot}; #[test] fn test_remove_trailing_crlf() {