Compare commits

..

22 Commits

Author SHA1 Message Date
Alexis Mousset
cbc9764626 version 0.0.13 2015-06-27 11:12:19 +02:00
Alexis Mousset
8811b32113 Beginning of rust-email integration 2015-06-27 02:31:24 +02:00
Alexis Mousset
0b185352f8 Fix GH_TOKEN 2015-06-12 23:48:51 +02:00
Alexis Mousset
72390e9bdb Add empty unstable feature 2015-06-12 23:33:01 +02:00
Alexis Mousset
520db5d21d Test to fix travis-cargo 2015-06-12 22:55:22 +02:00
Alexis Mousset
bb0c9c5c5c Add travis-cargo to master 2015-06-12 22:17:46 +02:00
Alexis Mousset
262f3b8d93 Merge pull request #18 from nevdelap/master
Add #![feature(buf_stream)] to compile with current nightly.
2015-06-05 14:38:20 +02:00
Nev Delap
d1b6b33b44 Removed stable and beta builds. 2015-06-05 22:13:54 +10:00
Nev Delap
4f249b319c Add stable and beta builds. 2015-06-05 22:10:43 +10:00
Nev Delap
2281b9046e Add #![feature(buf_stream)] to compile with current nightly. 2015-06-05 22:05:10 +10:00
Alexis Mousset
a4ada1b6ef Remove uneeded use 2015-05-06 21:39:22 +02:00
Alexis Mousset
8da3a09140 Beta build status 2015-05-06 15:48:50 +02:00
Alexis Mousset
6ea49d770c Try travis-cargo 2015-05-06 15:48:15 +02:00
Alexis Mousset
6a5b100098 Do not build the doc on nightly 2015-05-06 14:45:29 +02:00
Alexis Mousset
5ed452919c Build on nightly only 2015-05-06 14:44:59 +02:00
Alexis Mousset
30583ef1a6 Build on beta only 2015-05-06 14:44:40 +02:00
Alexis Mousset
60356c3b52 Test on rust 1.0.0-beta.4 2015-05-06 14:26:09 +02:00
Alexis Mousset
ed06c67258 Test on rust 1.0.0-beta.3 2015-04-28 12:31:47 +02:00
Alexis Mousset
ee8da8196a Start refactoring streams to introduce a mock server 2015-04-22 21:52:03 +02:00
Alexis Mousset
78e88d5bc6 test beta2 on travis 2015-04-22 12:07:53 +02:00
Alexis Mousset
8f49ba3b17 add crates.io badge 2015-04-18 23:00:30 +02:00
Alexis Mousset
6d19f80842 Add tests for responses with empty message 2015-04-10 19:27:49 +02:00
13 changed files with 179 additions and 459 deletions

View File

@@ -1,18 +1,21 @@
language: rust
sudo: required
rust:
- stable
- beta
- nightly
- 1.0.0-beta
after_success: |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
cargo doc &&
echo '<meta http-equiv=refresh content=0;url=smtp/index.html>' > target/doc/index.html &&
sudo pip install ghp-import &&
ghp-import -n target/doc &&
git push -fq https://${TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
before_script:
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
script:
- |
travis-cargo build &&
travis-cargo test &&
travis-cargo doc
after_success:
- travis-cargo --only stable doc-upload
- travis-cargo --only stable coveralls
env:
global:
secure: m8BXCWDfBh0Hkajp7+KdSFP65q3gUuJWJSb+W6BtAzWNxIuAwTgtm3Ja3gwwlko+XSOO4IG8+p+ApnduK1znWO+mslhUC7qr8oxIWRS3CzvSr3YeEwfVbWOz34/+I5cGh91ie8pbqxP+buMPVj078W3Cr/STzHhS2ZdQpjR/L8c=
secure: "MaZ3TzuaAHuxmxQkfJdqRfkh7/ieScJRk0T/2yjysZhDMTYyRmp5wh/zkfW1ADuG0uc4Pqsxrsh1J9SVO7O0U5NJA8NKZi/pgiL+FHh0g4YtlHxy2xmFNB5am3Kyc+E7B4XylwTbA9S8ublVM0nvX7yX/a5fbwEUInVk2bA8fpc="

View File

@@ -1,7 +1,7 @@
[package]
name = "smtp"
version = "0.0.11"
version = "0.0.13"
description = "Simple SMTP client"
readme = "README.md"
documentation = "http://amousset.github.io/rust-smtp/smtp/"
@@ -12,11 +12,18 @@ authors = ["Alexis Mousset <contact@amousset.me>"]
keywords = ["email", "smtp", "mailer"]
[dependencies]
time = "*"
uuid = "*"
log = "*"
rustc-serialize = "*"
rust-crypto = "*"
time = "0.1"
uuid = "0.1"
log = "0.3"
rustc-serialize = "0.3"
rust-crypto = "0.2"
bufstream = "0.1"
[dependencies.email]
git = "https://github.com/amousset/rust-email.git"
[dev-dependencies]
env_logger = "*"
[features]
unstable = []

View File

@@ -1,4 +1,4 @@
rust-smtp [![Build Status](https://travis-ci.org/amousset/rust-smtp.svg?branch=master)](https://travis-ci.org/amousset/rust-smtp)
rust-smtp [![Build Status](https://travis-ci.org/amousset/rust-smtp.svg?branch=master)](https://travis-ci.org/amousset/rust-smtp) [![Coverage Status](https://coveralls.io/repos/amousset/rust-smtp/badge.svg?branch=master)](https://coveralls.io/r/amousset/rust-smtp?branch=master) [![](https://meritbadge.herokuapp.com/smtp)](https://crates.io/crates/smtp)
=========
This library implements a simple SMTP client.

View File

@@ -12,8 +12,6 @@ extern crate log;
extern crate env_logger;
extern crate smtp;
use std::net::TcpStream;
use smtp::sender::{Sender, SenderBuilder};
use smtp::mailer::EmailBuilder;
@@ -28,7 +26,7 @@ fn main() {
.subject("Hello")
.build();
let mut sender: Sender<TcpStream> = SenderBuilder::localhost().hello_name("localhost")
let mut sender: Sender = SenderBuilder::localhost().hello_name("localhost")
.enable_connection_reuse(true).build();
for _ in (1..5) {

View File

@@ -10,17 +10,18 @@
//! SMTP client
use std::string::String;
use std::net::TcpStream;
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;
use client::connecter::Connecter;
use client::net::{Connector, SmtpStream};
use client::authentication::{plain, cram_md5};
use {CRLF, MESSAGE_ENDING};
pub mod connecter;
pub mod net;
mod authentication;
/// Returns the string after adding a dot at the beginning of each line starting with a dot
@@ -43,7 +44,7 @@ fn escape_crlf(string: &str) -> String {
}
/// Structure that implements the SMTP client
pub struct Client<S: Write + Read = TcpStream> {
pub struct Client<S: Write + Read = SmtpStream> {
/// TCP stream between client and server
/// Value is None before connection
stream: Option<BufStream<S>>,
@@ -58,7 +59,7 @@ macro_rules! return_err (
})
);
impl<S: Write + Read = TcpStream> Client<S> {
impl<S: Write + Read = SmtpStream> Client<S> {
/// Creates a new SMTP client
///
/// It does not connects to the server, but only creates the `Client`
@@ -70,7 +71,7 @@ impl<S: Write + Read = TcpStream> Client<S> {
}
}
impl<S: Connecter + Write + Read = TcpStream> Client<S> {
impl<S: Connector + Write + Read = SmtpStream> Client<S> {
/// Closes the SMTP transaction if possible
pub fn close(&mut self) {
let _ = self.quit();
@@ -85,7 +86,7 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
}
// Try to connect
self.stream = Some(BufStream::new(try!(Connecter::connect(&self.server_addr))));
self.stream = Some(BufStream::new(try!(Connector::connect(&self.server_addr))));
self.get_reply()
}
@@ -172,7 +173,7 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
self.command(&format!("AUTH CRAM-MD5 {}", cram_md5(username, password, &encoded_challenge)))
}
/// Sends the message content and close
/// Sends the message content
pub fn message(&mut self, message_content: &str) -> SmtpResult {
self.send_server(&escape_dot(message_content), MESSAGE_ENDING)
}

View File

@@ -7,20 +7,24 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A trait to represent a connected stream
//! A trait to represent a stream
use std::io;
use std::net::SocketAddr;
use std::net::TcpStream;
/// A trait for the concept of opening a stream
pub trait Connecter {
pub trait Connector {
/// Opens a connection to the given IP socket
fn connect(addr: &SocketAddr) -> io::Result<Self>;
}
impl Connecter for TcpStream {
fn connect(addr: &SocketAddr) -> io::Result<TcpStream> {
impl Connector for SmtpStream {
fn connect(addr: &SocketAddr) -> io::Result<SmtpStream> {
TcpStream::connect(addr)
}
}
/// Represents an atual SMTP network stream
//Used later for ssl
pub type SmtpStream = TcpStream;

View File

@@ -40,7 +40,6 @@
//! ```rust,no_run
//! use smtp::sender::{Sender, SenderBuilder};
//! use smtp::mailer::EmailBuilder;
//! use std::net::TcpStream;
//!
//! // Create an email
//! let email = EmailBuilder::new()
@@ -53,7 +52,7 @@
//! .build();
//!
//! // Open a local connection on port 25
//! let mut sender: Sender<TcpStream> = SenderBuilder::localhost().build();
//! let mut sender = SenderBuilder::localhost().build();
//! // Send the email
//! let result = sender.send(email);
//!
@@ -65,7 +64,6 @@
//! ```rust,no_run
//! use smtp::sender::{Sender, SenderBuilder};
//! use smtp::mailer::EmailBuilder;
//! use std::net::TcpStream;
//!
//! let mut builder = EmailBuilder::new();
//! builder = builder.to(("user@example.org", "Alias name"));
@@ -81,7 +79,7 @@
//! let email = builder.build();
//!
//! // Connect to a remote server on a custom port
//! let mut sender: Sender<TcpStream> = SenderBuilder::new(("server.tld", 10025))
//! let mut sender = SenderBuilder::new(("server.tld", 10025))
//! // Set the name sent during EHLO/HELO, default is `localhost`
//! .hello_name("my.hostname.tld")
//! // Add credentials for authentication
@@ -107,7 +105,6 @@
//! ```rust,no_run
//! use smtp::sender::{Sender, SenderBuilder};
//! use smtp::sendable_email::SimpleSendableEmail;
//! use std::net::TcpStream;
//!
//! // Create a minimal email
//! let email = SimpleSendableEmail::new(
@@ -116,7 +113,7 @@
//! "Hello world !"
//! );
//!
//! let mut sender: Sender<TcpStream> = SenderBuilder::localhost().build();
//! let mut sender = SenderBuilder::localhost().build();
//! let result = sender.send(email);
//! assert!(result.is_ok());
//! ```
@@ -127,10 +124,11 @@
//!
//! ```rust,no_run
//! use smtp::client::Client;
//! use smtp::client::net::SmtpStream;
//! use smtp::SMTP_PORT;
//! use std::net::TcpStream;
//!
//! let mut email_client: Client<TcpStream> = Client::new(("localhost", SMTP_PORT));
//! let mut email_client: Client<SmtpStream> = Client::new(("localhost", SMTP_PORT));
//! let _ = email_client.connect();
//! let _ = email_client.ehlo("my_hostname");
//! let _ = email_client.mail("user@example.com", None);
@@ -147,6 +145,8 @@ 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;

View File

@@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<String>,
}
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 <address>".to_string()
);
}
}

View File

@@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 <me@example.com>".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()
);
}
}

View File

@@ -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,106 @@ pub struct EmailBuilder {
/// Simple email representation
#[derive(PartialEq,Eq,Clone,Debug)]
pub struct Email {
/// Array of headers
headers: Vec<Header>,
/// Message body
body: String,
/// Message
message: MimeMessage,
/// The enveloppe recipients addresses
to: Vec<String>,
/// The enveloppe sender address
from: Option<String>,
/// 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 {
/// Displays the formatted email content
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<A: ToHeader>(mut self, header: A) -> EmailBuilder {
self.content.headers.push(header.to_header());
self.insert_header(header);
self
}
fn insert_header<A: ToHeader>(&mut self, header: A) {
self.content.message.headers.insert(header.to_header());
}
/// Adds a `From` header and store the sender address
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.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<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.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<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.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<A: ToAddress>(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<A: ToAddress>(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 +178,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 +203,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
}

View File

@@ -118,23 +118,6 @@ pub struct Response {
message: Vec<String>
}
impl Display for Response {
fn fmt(&self, f: &mut Formatter) -> Result {
let code = self.code();
for line in self.message[..-1].iter() {
let _ = write!(f, "{}-{}",
code,
line
);
}
write!(f, "{} {}",
code,
self.message[-1]
)
}
}
impl Response {
/// Creates a new `Response`
pub fn new(severity: Severity, category: Category, detail: u8, message: Vec<String>) -> Response {
@@ -236,6 +219,17 @@ mod test {
detail: 1,
message: vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()],
});
assert_eq!(Response::new(
"2".parse::<Severity>().unwrap(),
"4".parse::<Category>().unwrap(),
1,
vec![]
), Response {
severity: Severity::PositiveCompletion,
category: Category::Unspecified4,
detail: 1,
message: vec![],
});
}
#[test]
@@ -262,6 +256,13 @@ mod test {
1,
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
).message(), vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]);
let empty_message: Vec<String> = vec![];
assert_eq!(Response::new(
"2".parse::<Severity>().unwrap(),
"4".parse::<Category>().unwrap(),
1,
vec![]
).message(), empty_message);
}
#[test]

View File

@@ -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<String>;
/// 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())
}
}

View File

@@ -10,11 +10,7 @@
//! Sends an email using the client
use std::string::String;
use std::net::TcpStream;
use std::net::{SocketAddr, ToSocketAddrs};
use std::io::{Read, Write};
use uuid::Uuid;
use SMTP_PORT;
use extension::Extension;
@@ -22,7 +18,7 @@ use error::{SmtpResult, SmtpError};
use sendable_email::SendableEmail;
use sender::server_info::ServerInfo;
use client::Client;
use client::connecter::Connecter;
use client::net::SmtpStream;
mod server_info;
@@ -87,7 +83,7 @@ impl SenderBuilder {
/// Build the SMTP client
///
/// It does not connects to the server, but only creates the `Sender`
pub fn build<S: Connecter + Read + Write>(self) -> Sender<S> {
pub fn build(self) -> Sender {
Sender::new(self)
}
}
@@ -102,7 +98,7 @@ struct State {
}
/// Structure that implements the high level SMTP client
pub struct Sender<S: Write + Read = TcpStream> {
pub struct Sender {
/// Information about the server
/// Value is None before HELO/EHLO
server_info: Option<ServerInfo>,
@@ -111,7 +107,7 @@ pub struct Sender<S: Write + Read = TcpStream> {
/// Information about the client
client_info: SenderBuilder,
/// Low level client
client: Client<S>,
client: Client<SmtpStream>,
}
macro_rules! try_smtp (
@@ -129,12 +125,12 @@ macro_rules! try_smtp (
})
);
impl<S: Write + Read = TcpStream> Sender<S> {
impl Sender {
/// Creates a new SMTP client
///
/// It does not connects to the server, but only creates the `Sender`
pub fn new(builder: SenderBuilder) -> Sender<S> {
let client: Client<S> = Client::new(builder.server_addr);
pub fn new(builder: SenderBuilder) -> Sender {
let client: Client<SmtpStream> = Client::new(builder.server_addr);
Sender{
client: client,
server_info: None,
@@ -145,9 +141,7 @@ impl<S: Write + Read = TcpStream> Sender<S> {
},
}
}
}
impl<S: Connecter + Write + Read = TcpStream> Sender<S> {
/// Reset the client state
fn reset(&mut self) {
// Close the SMTP transaction if needed
@@ -165,7 +159,7 @@ impl<S: Connecter + Write + Read = TcpStream> Sender<S> {
}
/// Sends an email
pub fn send<T: SendableEmail>(&mut self, mut email: T) -> SmtpResult {
pub fn send<T: SendableEmail>(&mut self, email: T) -> SmtpResult {
// Check if the connection is still available
if self.state.connection_reuse_count > 0 {
if !self.client.is_connected() {
@@ -227,10 +221,7 @@ impl<S: Connecter + Write + Read = TcpStream> Sender<S> {
}
}
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();