diff --git a/src/mailer/address.rs b/src/mailer/address.rs new file mode 100644 index 0000000..6b104d9 --- /dev/null +++ b/src/mailer/address.rs @@ -0,0 +1,104 @@ +// 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::{Show, Formatter, Result}; + +use common::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 +#[deriving(PartialEq,Eq,Clone)] +pub struct Address { + /// The address + address: String, + /// The alias + 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 { + /// 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 new file mode 100644 index 0000000..210ec05 --- /dev/null +++ b/src/mailer/header.rs @@ -0,0 +1,76 @@ +// 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}; + +/// 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 +#[deriving(PartialEq,Eq,Clone)] +pub struct Header { + /// Name of the header + name: String, + /// Value of the header + 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 { + /// Creates ah `Header` + pub fn new(name: &str, value: &str) -> Header { + Header{name: name.to_string(), value: value.to_string()} + } +} + +#[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/mailer/mod.rs b/src/mailer/mod.rs new file mode 100644 index 0000000..359e707 --- /dev/null +++ b/src/mailer/mod.rs @@ -0,0 +1,195 @@ +// 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 (very incomplete) + +use std::fmt::{Show, Formatter, Result}; + +use time::{now, Tm}; + +use mailer::header::{ToHeader, Header}; +use mailer::address::ToAddress; +use common::CRLF; +use sendable_email::SendableEmail; + +pub mod header; +pub mod address; + +/// Simple email representation +#[deriving(PartialEq,Eq,Clone)] +pub struct Email { + /// Array of headers + headers: Vec
, + /// Message body + body: String, + /// The enveloppe recipients addresses + to: Vec, + /// The enveloppe sender address + from: Option, +} + +impl Show 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(header.to_string().as_slice()); + formatted_headers.push_str(CRLF); + } + f.write(format!("{}{}{}", formatted_headers, CRLF, self.body).as_bytes()) + } +} + +impl Email { + /// 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; + } + + /// Sets the email body + pub fn body(&mut self, body: &str) { + self.body = body.to_string(); + } + + /// Add a generic header + pub fn add_header(&mut self, header: A) { + self.headers.push(header.to_header()); + } + + /// 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( + Header::new("From", address.to_address().to_string().as_slice()) + ); + } + + /// 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( + Header::new("To", address.to_address().to_string().as_slice()) + ); + } + + /// 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( + Header::new("Cc", address.to_address().to_string().as_slice()) + ); + } + + /// Adds a `Reply-To` header + pub fn reply_to(&mut self, address: A) { + self.headers.push( + Header::new("Return-Path", address.to_address().to_string().as_slice()) + ); + } + + /// Adds a `Subject` header + pub fn subject(&mut self, subject: &str) { + self.headers.push( + Header::new("Subject", subject) + ); + } + + /// Adds a `Date` header with the current time + pub fn date_now(&mut self) { + self.headers.push( + Header::new("Date", Tm::rfc822(&now()).to_string().as_slice()) + ); + } + + /// Adds a `Date` header with the current time + pub fn date(&mut self, time: Tm) { + self.headers.push( + Header::new("Date", Tm::rfc822(&time).to_string().as_slice()) + ); + } +} + +impl SendableEmail for Email { + /// Return the to addresses, and fails if it is not set + fn to_addresses(&self) -> Vec { + if self.to.is_empty() { + panic!("The To field is empty") + } + self.to.clone() + } + + /// Return the from address, and fails if it is not set + fn from_address(&self) -> String { + match self.from { + Some(ref from_address) => from_address.clone(), + None => panic!("The From field is empty"), + } + } + + fn message(&self) -> String { + self.to_string() + } +} + +#[cfg(test)] +mod test { + use super::Email; + use mailer::header::Header; + + #[test] + fn test_new() { + assert_eq!( + Email::new(), + Email{headers: vec!(), body: "".to_string(), to: vec!(), from: None} + ) + } + + #[test] + fn test_body() { + let mut email = Email::new(); + email.body("test message"); + assert_eq!( + email, + Email{headers: vec!(), body: "test message".to_string(), to: vec!(), from: None} + ) + } + + #[test] + fn test_add_header() { + let mut email = Email::new(); + email.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 + } + ) + 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 + } + ) + } +}