Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbc9764626 | ||
|
|
8811b32113 | ||
|
|
0b185352f8 | ||
|
|
72390e9bdb | ||
|
|
520db5d21d | ||
|
|
bb0c9c5c5c | ||
|
|
262f3b8d93 | ||
|
|
d1b6b33b44 | ||
|
|
4f249b319c | ||
|
|
2281b9046e | ||
|
|
a4ada1b6ef | ||
|
|
8da3a09140 | ||
|
|
6ea49d770c | ||
|
|
6a5b100098 | ||
|
|
5ed452919c | ||
|
|
30583ef1a6 | ||
|
|
60356c3b52 | ||
|
|
ed06c67258 | ||
|
|
ee8da8196a | ||
|
|
78e88d5bc6 | ||
|
|
8f49ba3b17 | ||
|
|
6d19f80842 | ||
|
|
02a9093bad | ||
|
|
d59672ef26 | ||
|
|
7f9b2c5150 |
25
.travis.yml
25
.travis.yml
@@ -1,14 +1,21 @@
|
||||
language: rust
|
||||
sudo: required
|
||||
|
||||
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
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
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="
|
||||
|
||||
21
Cargo.toml
21
Cargo.toml
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
|
||||
name = "smtp"
|
||||
version = "0.0.9"
|
||||
version = "0.0.13"
|
||||
description = "Simple SMTP client"
|
||||
readme = "README.md"
|
||||
documentation = "http://amousset.github.io/rust-smtp/smtp/"
|
||||
@@ -12,9 +12,18 @@ authors = ["Alexis Mousset <contact@amousset.me>"]
|
||||
keywords = ["email", "smtp", "mailer"]
|
||||
|
||||
[dependencies]
|
||||
time = "*"
|
||||
uuid = "*"
|
||||
log = "*"
|
||||
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 = "*"
|
||||
rustc-serialize = "*"
|
||||
rust-crypto = "*"
|
||||
|
||||
[features]
|
||||
unstable = []
|
||||
|
||||
25
README.md
25
README.md
@@ -1,14 +1,9 @@
|
||||
rust-smtp [](https://travis-ci.org/amousset/rust-smtp)
|
||||
rust-smtp [](https://travis-ci.org/amousset/rust-smtp) [](https://coveralls.io/r/amousset/rust-smtp?branch=master) [](https://crates.io/crates/smtp)
|
||||
=========
|
||||
|
||||
This library implements a simple SMTP client.
|
||||
See the [documentation](http://amousset.github.io/rust-smtp/smtp/) for more information.
|
||||
|
||||
Rust versions
|
||||
-------------
|
||||
|
||||
This library is designed for Rust 1.0.0-nightly (master).
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
@@ -19,24 +14,6 @@ To use this library, add the following to your `Cargo.toml`:
|
||||
smtp = "*"
|
||||
```
|
||||
|
||||
Otherwise, you can clone this repository and run `cargo build`.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
There is an example command-line program included:
|
||||
```sh
|
||||
$ cargo test
|
||||
$ env RUST_LOG=info cargo run --example client -- -s "My subject" -r sender@localhost recipient@localhost < email.txt
|
||||
INFO:smtp::sender: connection established to 127.0.0.1:25
|
||||
INFO:smtp::sender: 1d0467fb21b2454f90a85dd1e0eda839: from=<sender@localhost>
|
||||
INFO:smtp::sender: 1d0467fb21b2454f90a85dd1e0eda839: to=<recipient@localhost>
|
||||
INFO:smtp::sender: 1d0467fb21b2454f90a85dd1e0eda839: conn_use=1, size=1889, status=sent (2.0.0 Ok: queued as BAA9C1C0055)
|
||||
INFO:client: Email sent successfully
|
||||
```
|
||||
|
||||
Run `cargo run --example client -- -h` to get a list of available options.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
|
||||
@@ -7,150 +7,36 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![feature(core, old_io, rustc_private, collections)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate env_logger;
|
||||
extern crate smtp;
|
||||
extern crate getopts;
|
||||
|
||||
use std::old_io::stdin;
|
||||
use std::string::String;
|
||||
use std::env;
|
||||
use getopts::{optopt, optflag, getopts, OptGroup, usage};
|
||||
use std::net::TcpStream;
|
||||
|
||||
use smtp::sender::{Sender, SenderBuilder};
|
||||
use smtp::error::SmtpResult;
|
||||
use smtp::mailer::EmailBuilder;
|
||||
|
||||
fn sendmail(source_address: String, recipient_addresses: Vec<String>, message: String, subject: String,
|
||||
server: String, port: u16, my_hostname: String, number: u16) -> SmtpResult {
|
||||
fn main() {
|
||||
env_logger::init().unwrap();
|
||||
|
||||
let mut email_builder = EmailBuilder::new();
|
||||
for destination in recipient_addresses.iter() {
|
||||
email_builder = email_builder.to(destination.as_slice());
|
||||
}
|
||||
let email = email_builder.from(source_address.as_slice())
|
||||
.body(message.as_slice())
|
||||
.subject(subject.as_slice())
|
||||
|
||||
email_builder = email_builder.to("user@localhost");
|
||||
let email = email_builder.from("user@localhost")
|
||||
.body("Hello World!")
|
||||
.subject("Hello")
|
||||
.build();
|
||||
|
||||
let mut sender: Sender<TcpStream> = SenderBuilder::new((server.as_slice(), port)).hello_name(my_hostname.as_slice())
|
||||
let mut sender: Sender = SenderBuilder::localhost().hello_name("localhost")
|
||||
.enable_connection_reuse(true).build();
|
||||
|
||||
for _ in (1..number) {
|
||||
for _ in (1..5) {
|
||||
let _ = sender.send(email.clone());
|
||||
}
|
||||
let result = sender.send(email);
|
||||
sender.close();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn print_usage(description: String, _opts: &[OptGroup]) {
|
||||
println!("{}", usage(description.as_slice(), _opts));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init().unwrap();
|
||||
|
||||
let args = env::args();
|
||||
|
||||
let mut args_string = Vec::new();
|
||||
for arg in args {
|
||||
args_string.push(arg.clone());
|
||||
};
|
||||
|
||||
let program = args_string[0].clone();
|
||||
let description = format!("Usage: {0} [options...] recipients\n\n\
|
||||
This program reads a message on standard input until it reaches\
|
||||
EOF, then tries to send it using the given paramters.\n\n\
|
||||
Example: {0} -r user@example.org user@example.com < message.txt",
|
||||
program);
|
||||
|
||||
let opts = [
|
||||
optopt("n", "number", "set the number of emails to send", "NUMBER"),
|
||||
optopt("s", "subject", "set the email subject", "SUBJECT"),
|
||||
optopt("r", "reverse-path", "set the sender address", "FROM_ADDRESS"),
|
||||
optopt("p", "port", "set the port to use, default is 25", "PORT"),
|
||||
optopt("a", "server", "set the server to use, default is localhost", "SERVER"),
|
||||
optopt("m", "my-hostname", "set the hostname used by the client", "MY_HOSTNAME"),
|
||||
optflag("h", "help", "print this help menu"),
|
||||
];
|
||||
|
||||
let matches = match getopts(args_string.tail(), &opts) {
|
||||
Ok(m) => m,
|
||||
Err(f) => panic!("{}", f),
|
||||
};
|
||||
|
||||
if matches.opt_present("h") {
|
||||
print_usage(description, &opts);
|
||||
return;
|
||||
}
|
||||
|
||||
if !matches.opt_present("r") {
|
||||
println!("The sender option is required");
|
||||
print_usage(program, &opts);
|
||||
return;
|
||||
}
|
||||
|
||||
let recipients_str: &str = if !matches.free.is_empty() {
|
||||
matches.free[0].as_slice()
|
||||
} else {
|
||||
print_usage(description, &opts);
|
||||
return;
|
||||
};
|
||||
|
||||
let mut recipients = Vec::new();
|
||||
for recipient in recipients_str.split(' ') {
|
||||
recipients.push(recipient.to_string());
|
||||
}
|
||||
|
||||
let mut message = String::new();
|
||||
|
||||
let mut line = stdin().read_line();
|
||||
while line.is_ok() {
|
||||
message.push_str(line.unwrap().as_slice());
|
||||
line = stdin().read_line();
|
||||
}
|
||||
|
||||
match sendmail(
|
||||
// sender
|
||||
matches.opt_str("r").unwrap().clone(),
|
||||
// recipients
|
||||
recipients,
|
||||
// message content
|
||||
message,
|
||||
// subject
|
||||
match matches.opt_str("s") {
|
||||
Some(ref subject) => subject.clone(),
|
||||
None => "(empty subject)".to_string(),
|
||||
},
|
||||
// server
|
||||
match matches.opt_str("a") {
|
||||
Some(ref server) => server.clone(),
|
||||
None => "localhost".to_string(),
|
||||
},
|
||||
// port
|
||||
match matches.opt_str("p") {
|
||||
Some(port) => port.as_slice().parse::<u16>().unwrap(),
|
||||
None => 25,
|
||||
},
|
||||
// my hostname
|
||||
match matches.opt_str("m") {
|
||||
Some(ref my_hostname) => my_hostname.clone(),
|
||||
None => "localhost".to_string(),
|
||||
},
|
||||
// number of copies
|
||||
match matches.opt_str("n") {
|
||||
Some(ref n) => n.as_slice().parse::<u16>().unwrap(),
|
||||
None => 1,
|
||||
},
|
||||
)
|
||||
{
|
||||
match result {
|
||||
Ok(..) => info!("Email sent successfully"),
|
||||
Err(error) => error!("{}", error),
|
||||
Err(error) => error!("{:?}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,13 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_plain() {
|
||||
assert_eq!(plain("username", "password").as_slice(), "AHVzZXJuYW1lAHBhc3N3b3Jk");
|
||||
assert_eq!(plain("username", "password"), "AHVzZXJuYW1lAHBhc3N3b3Jk");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cram_md5() {
|
||||
assert_eq!(cram_md5("alice", "wonderland",
|
||||
"PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg==").as_slice(),
|
||||
"PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="),
|
||||
"YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,18 +10,18 @@
|
||||
//! SMTP client
|
||||
|
||||
use std::string::String;
|
||||
use std::error::FromError;
|
||||
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
|
||||
@@ -44,7 +44,7 @@ fn escape_crlf(string: &str) -> String {
|
||||
}
|
||||
|
||||
/// Structure that implements the SMTP client
|
||||
pub struct Client<S = TcpStream> {
|
||||
pub struct Client<S: Write + Read = SmtpStream> {
|
||||
/// TCP stream between client and server
|
||||
/// Value is None before connection
|
||||
stream: Option<BufStream<S>>,
|
||||
@@ -55,11 +55,11 @@ pub struct Client<S = TcpStream> {
|
||||
|
||||
macro_rules! return_err (
|
||||
($err: expr, $client: ident) => ({
|
||||
return Err(FromError::from_error($err))
|
||||
return Err(From::from($err))
|
||||
})
|
||||
);
|
||||
|
||||
impl<S = 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`
|
||||
@@ -71,10 +71,11 @@ impl<S = 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();
|
||||
self.stream = None;
|
||||
}
|
||||
|
||||
/// Connects to the configured server
|
||||
@@ -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)
|
||||
}
|
||||
@@ -180,7 +181,7 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
|
||||
/// Sends a string to the server and gets the response
|
||||
fn send_server(&mut self, string: &str, end: &str) -> SmtpResult {
|
||||
if self.stream.is_none() {
|
||||
return Err(FromError::from_error("Connection closed"));
|
||||
return Err(From::from("Connection closed"));
|
||||
}
|
||||
|
||||
try!(write!(self.stream.as_mut().unwrap(), "{}{}", string, end));
|
||||
@@ -198,12 +199,12 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
|
||||
|
||||
// If the string is too short to be a response code
|
||||
if line.len() < 3 {
|
||||
return Err(FromError::from_error("Could not parse reply code, line too short"));
|
||||
return Err(From::from("Could not parse reply code, line too short"));
|
||||
}
|
||||
|
||||
let (severity, category, detail) = match (line[0..1].parse::<Severity>(), line[1..2].parse::<Category>(), line[2..3].parse::<u8>()) {
|
||||
(Ok(severity), Ok(category), Ok(detail)) => (severity, category, detail),
|
||||
_ => return Err(FromError::from_error("Could not parse reply code")),
|
||||
_ => return Err(From::from("Could not parse reply code")),
|
||||
};
|
||||
|
||||
let mut message = Vec::new();
|
||||
@@ -224,7 +225,7 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
|
||||
|
||||
match response.is_positive() {
|
||||
true => Ok(response),
|
||||
false => Err(FromError::from_error(response)),
|
||||
false => Err(From::from(response)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,18 +236,18 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_escape_dot() {
|
||||
assert_eq!(escape_dot(".test").as_slice(), "..test");
|
||||
assert_eq!(escape_dot("\r.\n.\r\n").as_slice(), "\r..\n..\r\n");
|
||||
assert_eq!(escape_dot("test\r\n.test\r\n").as_slice(), "test\r\n..test\r\n");
|
||||
assert_eq!(escape_dot("test\r\n.\r\ntest").as_slice(), "test\r\n..\r\ntest");
|
||||
assert_eq!(escape_dot(".test"), "..test");
|
||||
assert_eq!(escape_dot("\r.\n.\r\n"), "\r..\n..\r\n");
|
||||
assert_eq!(escape_dot("test\r\n.test\r\n"), "test\r\n..test\r\n");
|
||||
assert_eq!(escape_dot("test\r\n.\r\ntest"), "test\r\n..\r\ntest");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_crlf() {
|
||||
assert_eq!(escape_crlf("\r\n").as_slice(), "<CR><LF>");
|
||||
assert_eq!(escape_crlf("EHLO my_name\r\n").as_slice(), "EHLO my_name<CR><LF>");
|
||||
assert_eq!(escape_crlf("\r\n"), "<CR><LF>");
|
||||
assert_eq!(escape_crlf("EHLO my_name\r\n"), "EHLO my_name<CR><LF>");
|
||||
assert_eq!(
|
||||
escape_crlf("EHLO my_name\r\nSIZE 42\r\n").as_slice(),
|
||||
escape_crlf("EHLO my_name\r\nSIZE 42\r\n"),
|
||||
"EHLO my_name<CR><LF>SIZE 42<CR><LF>"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
15
src/error.rs
15
src/error.rs
@@ -11,7 +11,6 @@
|
||||
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::error::FromError;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt;
|
||||
|
||||
@@ -19,7 +18,7 @@ use response::{Severity, Response};
|
||||
use self::SmtpError::*;
|
||||
|
||||
/// An enum of all error kinds.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub enum SmtpError {
|
||||
/// Transient error, 4xx reply code
|
||||
///
|
||||
@@ -59,14 +58,14 @@ impl Error for SmtpError {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromError<io::Error> for SmtpError {
|
||||
fn from_error(err: io::Error) -> SmtpError {
|
||||
impl From<io::Error> for SmtpError {
|
||||
fn from(err: io::Error) -> SmtpError {
|
||||
IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromError<Response> for SmtpError {
|
||||
fn from_error(response: Response) -> SmtpError {
|
||||
impl From<Response> for SmtpError {
|
||||
fn from(response: Response) -> SmtpError {
|
||||
match response.severity() {
|
||||
Severity::TransientNegativeCompletion => TransientError(response),
|
||||
Severity::PermanentNegativeCompletion => PermanentError(response),
|
||||
@@ -75,8 +74,8 @@ impl FromError<Response> for SmtpError {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromError<&'static str> for SmtpError {
|
||||
fn from_error(string: &'static str) -> SmtpError {
|
||||
impl From<&'static str> for SmtpError {
|
||||
fn from(string: &'static str) -> SmtpError {
|
||||
ClientError(string.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,9 @@ impl Extension {
|
||||
|
||||
for line in response.message() {
|
||||
if let Ok(keywords) = Extension::from_str(&line) {
|
||||
esmtp_features.push_all(&keywords);
|
||||
for keyword in keywords {
|
||||
esmtp_features.push(keyword);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
17
src/lib.rs
17
src/lib.rs
@@ -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);
|
||||
@@ -140,14 +138,15 @@
|
||||
//! let _ = email_client.quit();
|
||||
//! ```
|
||||
|
||||
#![feature(plugin, core, collections, str_words)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[macro_use] extern crate log;
|
||||
extern crate "rustc-serialize" as serialize;
|
||||
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;
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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_slice(),
|
||||
},
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -189,7 +172,7 @@ impl Response {
|
||||
pub fn first_word(&self) -> Option<String> {
|
||||
match self.message.is_empty() {
|
||||
true => None,
|
||||
false => Some(self.message[0].words().next().unwrap().to_string())
|
||||
false => Some(self.message[0].split(" ").next().unwrap().to_string()),
|
||||
}
|
||||
|
||||
}
|
||||
@@ -208,7 +191,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_severity_fmt() {
|
||||
assert_eq!(format!("{}", Severity::PositiveCompletion).as_slice(), "2");
|
||||
assert_eq!(format!("{}", Severity::PositiveCompletion), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -220,7 +203,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_category_fmt() {
|
||||
assert_eq!(format!("{}", Category::Unspecified4).as_slice(), "4");
|
||||
assert_eq!(format!("{}", Category::Unspecified4), "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -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]
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = 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 = TcpStream> {
|
||||
/// Information about the client
|
||||
client_info: SenderBuilder,
|
||||
/// Low level client
|
||||
client: Client<S>,
|
||||
client: Client<SmtpStream>,
|
||||
}
|
||||
|
||||
macro_rules! try_smtp (
|
||||
@@ -121,7 +117,7 @@ macro_rules! try_smtp (
|
||||
Err(err) => {
|
||||
if !$client.state.panic {
|
||||
$client.state.panic = true;
|
||||
$client.client.close();
|
||||
$client.reset();
|
||||
}
|
||||
return Err(err)
|
||||
},
|
||||
@@ -129,12 +125,12 @@ macro_rules! try_smtp (
|
||||
})
|
||||
);
|
||||
|
||||
impl<S = 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 = 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();
|
||||
@@ -265,10 +256,8 @@ impl<S: Connecter + Write + Read = TcpStream> Sender<S> {
|
||||
|
||||
// Log the message
|
||||
info!("{}: conn_use={}, size={}, status=sent ({})", current_message,
|
||||
self.state.connection_reuse_count, message.len(), match result.as_ref().ok().unwrap().message().as_slice() {
|
||||
[ref line, ..] => line.as_slice(),
|
||||
[] => "no response",
|
||||
}
|
||||
self.state.connection_reuse_count, message.len(),
|
||||
result.as_ref().ok().unwrap().message().iter().next().unwrap_or(&"no response".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user