Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af20cfa8ff | ||
|
|
7a9f9111a5 | ||
|
|
89c0be219d | ||
|
|
6ee7fdb3d1 | ||
|
|
b7ac3a897f | ||
|
|
c436716277 | ||
|
|
eabdb960b0 | ||
|
|
59ba9e84dc | ||
|
|
e569c030bc | ||
|
|
72aea756fa | ||
|
|
655ae6d2ff | ||
|
|
150536d242 | ||
|
|
7d707fab25 | ||
|
|
4ec34987f8 | ||
|
|
7f3680f125 | ||
|
|
67566c2152 | ||
|
|
d863a7677e | ||
|
|
c1fe40479b | ||
|
|
8d03545062 | ||
|
|
489a6e892e | ||
|
|
b3fe1e0f65 | ||
|
|
3612ffca7a | ||
|
|
7940ad6c15 | ||
|
|
5ffb169bc9 | ||
|
|
ea0bb256cd | ||
|
|
9f177047f8 | ||
|
|
48eb859804 | ||
|
|
8d9877233d | ||
|
|
09f61a9fc9 | ||
|
|
40e749a04a | ||
|
|
4efb560bc8 | ||
|
|
500c4fb39d | ||
|
|
d488910010 | ||
|
|
4155e44dbd | ||
|
|
401118ee68 | ||
|
|
e6dd9d5a46 | ||
|
|
c8187c4a7c | ||
|
|
8f211c88a8 | ||
|
|
62df24c5b1 | ||
|
|
7ac43b73c3 | ||
|
|
3c91c065d6 | ||
|
|
9e30e7185e | ||
|
|
4da9e16bfc | ||
|
|
2977eb0509 | ||
|
|
2884da8f90 | ||
|
|
31a7504d54 | ||
|
|
9a93feea96 | ||
|
|
f102f321d3 | ||
|
|
1ba47e473c | ||
|
|
3acf21a316 | ||
|
|
544894def9 | ||
|
|
f74fb4f89c | ||
|
|
085998c730 | ||
|
|
d3d7c4b44e |
@@ -1,10 +1,10 @@
|
|||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
- TARGET: i686-pc-windows-msvc
|
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
- TARGET: i686-pc-windows-gnu
|
|
||||||
install:
|
install:
|
||||||
|
- ps: Start-FileDownload 'http://slproweb.com/download/Win64OpenSSL-1_0_2d.exe'
|
||||||
|
- ps: Start-Process "Win64OpenSSL-1_0_2d.exe" -ArgumentList "/silent /verysilent /sp- /suppressmsgboxes" -Wait
|
||||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" -FileName "rust-nightly.exe"
|
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" -FileName "rust-nightly.exe"
|
||||||
- ps: .\rust-nightly.exe /VERYSILENT /NORESTART /DIR="C:\rust" | Out-Null
|
- ps: .\rust-nightly.exe /VERYSILENT /NORESTART /DIR="C:\rust" | Out-Null
|
||||||
- ps: $env:PATH="$env:PATH;C:\rust\bin"
|
- ps: $env:PATH="$env:PATH;C:\rust\bin"
|
||||||
@@ -12,4 +12,4 @@ install:
|
|||||||
- cargo -vV
|
- cargo -vV
|
||||||
build: false
|
build: false
|
||||||
test_script:
|
test_script:
|
||||||
- cargo test --verbose --no-default-features
|
- env OPENSSL_LIB_DIR=C:/OpenSSL-Win64 OPENSSL_INCLUDE_DIR=C:/OpenSSL-Win64/include cargo build --verbose
|
||||||
|
|||||||
42
.travis.yml
42
.travis.yml
@@ -1,21 +1,37 @@
|
|||||||
language: rust
|
language: rust
|
||||||
sudo: required
|
|
||||||
|
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
|
sudo: false
|
||||||
|
cache:
|
||||||
|
apt: true
|
||||||
|
pip: true
|
||||||
|
directories:
|
||||||
|
- target/debug/deps
|
||||||
|
- target/debug/build
|
||||||
|
- target/release/deps
|
||||||
|
- target/release/build
|
||||||
|
install:
|
||||||
|
- pip install 'travis-cargo<0.2' --user
|
||||||
|
- export PATH=$HOME/.local/bin:$PATH
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- postfix
|
||||||
|
- libcurl4-openssl-dev
|
||||||
|
- libelf-dev
|
||||||
|
- libdw-dev
|
||||||
before_script:
|
before_script:
|
||||||
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
|
- smtp-sink 2525 1000&
|
||||||
script:
|
script:
|
||||||
- |
|
- travis-cargo build
|
||||||
travis-cargo build &&
|
- travis-cargo test
|
||||||
travis-cargo test &&
|
- travis-cargo doc
|
||||||
travis-cargo doc
|
|
||||||
after_success:
|
after_success:
|
||||||
- travis-cargo --only stable doc-upload
|
- travis-cargo --only nightly bench
|
||||||
- travis-cargo --only stable coveralls
|
- travis-cargo --only stable coveralls --no-sudo
|
||||||
|
- travis-cargo --only stable doc-upload
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
secure: "MaZ3TzuaAHuxmxQkfJdqRfkh7/ieScJRk0T/2yjysZhDMTYyRmp5wh/zkfW1ADuG0uc4Pqsxrsh1J9SVO7O0U5NJA8NKZi/pgiL+FHh0g4YtlHxy2xmFNB5am3Kyc+E7B4XylwTbA9S8ublVM0nvX7yX/a5fbwEUInVk2bA8fpc="
|
secure: "MaZ3TzuaAHuxmxQkfJdqRfkh7/ieScJRk0T/2yjysZhDMTYyRmp5wh/zkfW1ADuG0uc4Pqsxrsh1J9SVO7O0U5NJA8NKZi/pgiL+FHh0g4YtlHxy2xmFNB5am3Kyc+E7B4XylwTbA9S8ublVM0nvX7yX/a5fbwEUInVk2bA8fpc="
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
|
|
||||||
name = "lettre"
|
name = "lettre"
|
||||||
version = "0.4.0"
|
version = "0.5.1"
|
||||||
description = "Email client"
|
description = "Email client"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
documentation = "http://lettre.github.io/lettre/"
|
documentation = "http://lettre.github.io/lettre/"
|
||||||
@@ -18,7 +18,7 @@ rustc-serialize = "0.3"
|
|||||||
rust-crypto = "0.2"
|
rust-crypto = "0.2"
|
||||||
bufstream = "0.1"
|
bufstream = "0.1"
|
||||||
email = "0.0"
|
email = "0.0"
|
||||||
openssl = "0.6"
|
openssl = "0.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.3"
|
env_logger = "0.3"
|
||||||
|
|||||||
@@ -11,9 +11,14 @@ To use this library, add the following to your `Cargo.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lettre = "0.4"
|
lettre = "0.5"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Testing
|
||||||
|
-------
|
||||||
|
|
||||||
|
The tests require a mail server listening locally on port 25.
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|||||||
44
benches/transport_smtp.rs
Normal file
44
benches/transport_smtp.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#![feature(test)]
|
||||||
|
|
||||||
|
extern crate lettre;
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use lettre::transport::smtp::SmtpTransportBuilder;
|
||||||
|
use lettre::transport::EmailTransport;
|
||||||
|
use lettre::email::EmailBuilder;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_simple_send(b: &mut test::Bencher) {
|
||||||
|
let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525").unwrap().build();
|
||||||
|
b.iter(|| {
|
||||||
|
let email = EmailBuilder::new()
|
||||||
|
.to("root@localhost")
|
||||||
|
.from("user@localhost")
|
||||||
|
.body("Hello World!")
|
||||||
|
.subject("Hello")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let result = sender.send(email);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_reuse_send(b: &mut test::Bencher) {
|
||||||
|
let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525")
|
||||||
|
.unwrap()
|
||||||
|
.connection_reuse(true)
|
||||||
|
.build();
|
||||||
|
b.iter(|| {
|
||||||
|
let email = EmailBuilder::new()
|
||||||
|
.to("root@localhost")
|
||||||
|
.from("user@localhost")
|
||||||
|
.body("Hello World!")
|
||||||
|
.subject("Hello")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let result = sender.send(email);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
});
|
||||||
|
sender.close()
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate lettre;
|
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use lettre::transport::smtp::SmtpTransportBuilder;
|
|
||||||
use lettre::transport::EmailTransport;
|
|
||||||
use lettre::mailer::Mailer;
|
|
||||||
use lettre::email::EmailBuilder;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env_logger::init().unwrap();
|
|
||||||
|
|
||||||
let sender = SmtpTransportBuilder::localhost().unwrap().hello_name("localhost")
|
|
||||||
.connection_reuse(true).build();
|
|
||||||
let mailer = Arc::new(Mutex::new(Mailer::new(sender)));
|
|
||||||
|
|
||||||
let mut threads = Vec::new();
|
|
||||||
for _ in 1..5 {
|
|
||||||
|
|
||||||
let th_mailer = mailer.clone();
|
|
||||||
threads.push(thread::spawn(move || {
|
|
||||||
|
|
||||||
let email = EmailBuilder::new()
|
|
||||||
.to("user@localhost")
|
|
||||||
.from("user@localhost")
|
|
||||||
.body("Hello World!")
|
|
||||||
.subject("Hello")
|
|
||||||
.build().unwrap();
|
|
||||||
|
|
||||||
let _ = th_mailer.lock().unwrap().send(email);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for thread in threads {
|
|
||||||
let _ = thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
let email = EmailBuilder::new()
|
|
||||||
.to("user@localhost")
|
|
||||||
.from("user@localhost")
|
|
||||||
.body("Hello World!")
|
|
||||||
.subject("Hello Bis")
|
|
||||||
.build().unwrap();
|
|
||||||
|
|
||||||
let mut mailer = mailer.lock().unwrap();
|
|
||||||
let result = mailer.send(email);
|
|
||||||
mailer.close();
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(..) => info!("Email sent successfully"),
|
|
||||||
Err(error) => error!("{:?}", error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
reorder_imports = true
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
//! Simple email (very incomplete)
|
//! Simple email (very incomplete)
|
||||||
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use email_format::{MimeMessage, Header, Mailbox};
|
use email_format::{Header, Mailbox, MimeMessage};
|
||||||
use time::{now, Tm};
|
use time::{Tm, now};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// Converts an adress or an address with an alias to a `Address`
|
/// Converts an adress or an address with an alias to a `Address`
|
||||||
@@ -165,10 +165,10 @@ impl EmailBuilder {
|
|||||||
/// Build the Email
|
/// Build the Email
|
||||||
pub fn build(mut self) -> Result<Email, &'static str> {
|
pub fn build(mut self) -> Result<Email, &'static str> {
|
||||||
if self.from.is_none() {
|
if self.from.is_none() {
|
||||||
return Err("No from address")
|
return Err("No from address");
|
||||||
}
|
}
|
||||||
if self.to.is_empty() {
|
if self.to.is_empty() {
|
||||||
return Err("No to address")
|
return Err("No to address");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.date_issued {
|
if !self.date_issued {
|
||||||
@@ -219,10 +219,10 @@ pub struct SimpleSendableEmail {
|
|||||||
|
|
||||||
impl SimpleSendableEmail {
|
impl SimpleSendableEmail {
|
||||||
/// Returns a new email
|
/// Returns a new email
|
||||||
pub fn new(from_address: &str, to_address: &str, message: &str) -> SimpleSendableEmail {
|
pub fn new(from_address: &str, to_address: Vec<String>, message: &str) -> SimpleSendableEmail {
|
||||||
SimpleSendableEmail {
|
SimpleSendableEmail {
|
||||||
from: from_address.to_string(),
|
from: from_address.to_string(),
|
||||||
to: vec![to_address.to_string()],
|
to: to_address,
|
||||||
message: message.to_string(),
|
message: message.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,9 +269,9 @@ mod test {
|
|||||||
use time::now;
|
use time::now;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use email_format::{MimeMessage, Header};
|
use email_format::{Header, MimeMessage};
|
||||||
|
|
||||||
use super::{SendableEmail, EmailBuilder, Email};
|
use super::{Email, EmailBuilder, SendableEmail};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_email_display() {
|
fn test_email_display() {
|
||||||
|
|||||||
157
src/lib.rs
157
src/lib.rs
@@ -1,36 +1,39 @@
|
|||||||
//! # Rust email client
|
//! Lettre is a mailer written in Rust. It provides a simple email builder and several transports.
|
||||||
//!
|
//!
|
||||||
//! This client should tend to follow [RFC 5321](https://tools.ietf.org/html/rfc5321), but is still
|
//! ## Architecture
|
||||||
//! a work in progress. It is designed to efficiently send emails from an application to a
|
//!
|
||||||
//! relay email server, as it relies as much as possible on the relay server for sanity and RFC
|
//! This mailer is divided into:
|
||||||
|
//!
|
||||||
|
//! * An `email` part: builds the email message
|
||||||
|
//! * A `transport` part: contains the available transports for your emails. To be sendable, the
|
||||||
|
//! emails have to implement `SendableEmail`.
|
||||||
|
//!
|
||||||
|
//! ## SMTP transport
|
||||||
|
//!
|
||||||
|
//! This SMTP follows [RFC
|
||||||
|
//! 5321](https://tools.ietf.org/html/rfc5321), but is still
|
||||||
|
//! a work in progress. It is designed to efficiently send emails from an
|
||||||
|
//! application to a
|
||||||
|
//! relay email server, as it relies as much as possible on the relay server
|
||||||
|
//! for sanity and RFC
|
||||||
//! compliance checks.
|
//! compliance checks.
|
||||||
//!
|
//!
|
||||||
//! It implements the following extensions:
|
//! It implements the following extensions:
|
||||||
//!
|
//!
|
||||||
//! * 8BITMIME ([RFC 6152](https://tools.ietf.org/html/rfc6152))
|
//! * 8BITMIME ([RFC 6152](https://tools.ietf.org/html/rfc6152))
|
||||||
//! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954)) with PLAIN and CRAM-MD5 mecanisms
|
//! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954)) with PLAIN and
|
||||||
|
//! CRAM-MD5 mechanisms
|
||||||
//! * STARTTLS ([RFC 2487](http://tools.ietf.org/html/rfc2487))
|
//! * STARTTLS ([RFC 2487](http://tools.ietf.org/html/rfc2487))
|
||||||
//! * SMTPUTF8 ([RFC 6531](http://tools.ietf.org/html/rfc6531))
|
//! * SMTPUTF8 ([RFC 6531](http://tools.ietf.org/html/rfc6531))
|
||||||
//!
|
//!
|
||||||
//! ## Architecture
|
|
||||||
//!
|
|
||||||
//! This client is divided into three main parts:
|
|
||||||
//!
|
|
||||||
//! * transport: a low level SMTP client providing all SMTP commands
|
|
||||||
//! * mailer: a high level SMTP client providing an easy method to send emails
|
|
||||||
//! * email: generates the email to be sent with the sender
|
|
||||||
//!
|
|
||||||
//! ## Usage
|
|
||||||
//!
|
|
||||||
//! ### Simple example
|
//! ### Simple example
|
||||||
//!
|
//!
|
||||||
//! This is the most basic example of usage:
|
//! This is the most basic example of usage:
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust
|
||||||
//! use lettre::transport::smtp::{SmtpTransport, SmtpTransportBuilder};
|
//! use lettre::transport::smtp::{SmtpTransport, SmtpTransportBuilder};
|
||||||
//! use lettre::email::EmailBuilder;
|
//! use lettre::email::EmailBuilder;
|
||||||
//! use lettre::transport::EmailTransport;
|
//! use lettre::transport::EmailTransport;
|
||||||
//! use lettre::mailer::Mailer;
|
|
||||||
//!
|
//!
|
||||||
//! // Create an email
|
//! // Create an email
|
||||||
//! let email = EmailBuilder::new()
|
//! let email = EmailBuilder::new()
|
||||||
@@ -43,7 +46,8 @@
|
|||||||
//! .build().unwrap();
|
//! .build().unwrap();
|
||||||
//!
|
//!
|
||||||
//! // Open a local connection on port 25
|
//! // Open a local connection on port 25
|
||||||
//! let mut mailer = Mailer::new(SmtpTransportBuilder::localhost().unwrap().build());
|
//! let mut mailer =
|
||||||
|
//! SmtpTransportBuilder::localhost().unwrap().build();
|
||||||
//! // Send the email
|
//! // Send the email
|
||||||
//! let result = mailer.send(email);
|
//! let result = mailer.send(email);
|
||||||
//!
|
//!
|
||||||
@@ -54,11 +58,11 @@
|
|||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use lettre::email::EmailBuilder;
|
//! use lettre::email::EmailBuilder;
|
||||||
//! use lettre::transport::smtp::{SecurityLevel, SmtpTransport, SmtpTransportBuilder};
|
//! use lettre::transport::smtp::{SecurityLevel, SmtpTransport,
|
||||||
//! use lettre::transport::smtp::authentication::Mecanism;
|
//! SmtpTransportBuilder};
|
||||||
|
//! use lettre::transport::smtp::authentication::Mechanism;
|
||||||
//! use lettre::transport::smtp::SUBMISSION_PORT;
|
//! use lettre::transport::smtp::SUBMISSION_PORT;
|
||||||
//! use lettre::transport::EmailTransport;
|
//! use lettre::transport::EmailTransport;
|
||||||
//! use lettre::mailer::Mailer;
|
|
||||||
//!
|
//!
|
||||||
//! let mut builder = EmailBuilder::new();
|
//! let mut builder = EmailBuilder::new();
|
||||||
//! builder = builder.to(("user@example.org", "Alias name"));
|
//! builder = builder.to(("user@example.org", "Alias name"));
|
||||||
@@ -74,7 +78,8 @@
|
|||||||
//! let email = builder.build().unwrap();
|
//! let email = builder.build().unwrap();
|
||||||
//!
|
//!
|
||||||
//! // Connect to a remote server on a custom port
|
//! // Connect to a remote server on a custom port
|
||||||
//! let mut mailer = Mailer::new(SmtpTransportBuilder::new(("server.tld", SUBMISSION_PORT)).unwrap()
|
//! let mut mailer = SmtpTransportBuilder::new(("server.tld",
|
||||||
|
//! SUBMISSION_PORT)).unwrap()
|
||||||
//! // Set the name sent during EHLO/HELO, default is `localhost`
|
//! // Set the name sent during EHLO/HELO, default is `localhost`
|
||||||
//! .hello_name("my.hostname.tld")
|
//! .hello_name("my.hostname.tld")
|
||||||
//! // Add credentials for authentication
|
//! // Add credentials for authentication
|
||||||
@@ -84,10 +89,10 @@
|
|||||||
//! .security_level(SecurityLevel::AlwaysEncrypt)
|
//! .security_level(SecurityLevel::AlwaysEncrypt)
|
||||||
//! // Enable SMTPUTF8 is the server supports it
|
//! // Enable SMTPUTF8 is the server supports it
|
||||||
//! .smtp_utf8(true)
|
//! .smtp_utf8(true)
|
||||||
//! // Configure accepted authetication mecanisms
|
//! // Configure accepted authetication mechanisms
|
||||||
//! .authentication_mecanisms(vec![Mecanism::CramMd5])
|
//! .authentication_mechanisms(vec![Mechanism::CramMd5])
|
||||||
//! // Enable connection reuse
|
//! // Enable connection reuse
|
||||||
//! .connection_reuse(true).build());
|
//! .connection_reuse(true).build();
|
||||||
//!
|
//!
|
||||||
//! let result_1 = mailer.send(email.clone());
|
//! let result_1 = mailer.send(email.clone());
|
||||||
//! assert!(result_1.is_ok());
|
//! assert!(result_1.is_ok());
|
||||||
@@ -100,39 +105,18 @@
|
|||||||
//! mailer.close();
|
//! mailer.close();
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### Using the client directly
|
|
||||||
//!
|
|
||||||
//! If you just want to send an email without using `Email` to provide headers:
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
|
||||||
//! use lettre::email::SimpleSendableEmail;
|
|
||||||
//! use lettre::transport::smtp::{SmtpTransport, SmtpTransportBuilder};
|
|
||||||
//! use lettre::transport::EmailTransport;
|
|
||||||
//! use lettre::mailer::Mailer;
|
|
||||||
//!
|
|
||||||
//! // Create a minimal email
|
|
||||||
//! let email = SimpleSendableEmail::new(
|
|
||||||
//! "test@example.com",
|
|
||||||
//! "test@example.org",
|
|
||||||
//! "Hello world !"
|
|
||||||
//! );
|
|
||||||
//!
|
|
||||||
//! let mut mailer = Mailer::new(SmtpTransportBuilder::localhost().unwrap().build());
|
|
||||||
//! let result = mailer.send(email);
|
|
||||||
//! assert!(result.is_ok());
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ### Lower level
|
//! ### Lower level
|
||||||
//!
|
//!
|
||||||
//! You can also send commands, here is a simple email transaction without error handling:
|
//! You can also send commands, here is a simple email transaction without
|
||||||
|
//! error handling:
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust
|
||||||
//! use lettre::transport::smtp::SMTP_PORT;
|
//! use lettre::transport::smtp::SMTP_PORT;
|
||||||
//! use lettre::transport::smtp::client::Client;
|
//! use lettre::transport::smtp::client::Client;
|
||||||
//! use lettre::transport::smtp::client::net::NetworkStream;
|
//! use lettre::transport::smtp::client::net::NetworkStream;
|
||||||
//!
|
//!
|
||||||
//! let mut email_client: Client<NetworkStream> = Client::new();
|
//! let mut email_client: Client<NetworkStream> = Client::new();
|
||||||
//! let _ = email_client.connect(&("localhost", SMTP_PORT));
|
//! let _ = email_client.connect(&("localhost", SMTP_PORT), None);
|
||||||
//! let _ = email_client.ehlo("my_hostname");
|
//! let _ = email_client.ehlo("my_hostname");
|
||||||
//! let _ = email_client.mail("user@example.com", None);
|
//! let _ = email_client.mail("user@example.com", None);
|
||||||
//! let _ = email_client.rcpt("user@example.org");
|
//! let _ = email_client.rcpt("user@example.org");
|
||||||
@@ -140,12 +124,80 @@
|
|||||||
//! let _ = email_client.message("Test email");
|
//! let _ = email_client.message("Test email");
|
||||||
//! let _ = email_client.quit();
|
//! let _ = email_client.quit();
|
||||||
//! ```
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Stub transport
|
||||||
|
//!
|
||||||
|
//! The stub transport only logs message envelope and drops the content. It can be useful for
|
||||||
|
//! testing purposes.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use lettre::transport::stub::StubEmailTransport;
|
||||||
|
//! use lettre::transport::EmailTransport;
|
||||||
|
//! use lettre::email::EmailBuilder;
|
||||||
|
//!
|
||||||
|
//! let mut sender = StubEmailTransport;
|
||||||
|
//! let email = EmailBuilder::new()
|
||||||
|
//! .to("root@localhost")
|
||||||
|
//! .from("user@localhost")
|
||||||
|
//! .body("Hello World!")
|
||||||
|
//! .subject("Hello")
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap();
|
||||||
|
//! let result = sender.send(email);
|
||||||
|
//! assert!(result.is_ok());
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Will log the line:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! b7c211bc-9811-45ce-8cd9-68eab575d695: from=<user@localhost> to=<root@localhost>
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## File transport
|
||||||
|
//!
|
||||||
|
//! The file transport writes the emails to the given directory. The name of the file will be
|
||||||
|
//! `message_id.txt`.
|
||||||
|
//! It can be useful for testing purposes.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use std::env::temp_dir;
|
||||||
|
//!
|
||||||
|
//! use lettre::transport::file::FileEmailTransport;
|
||||||
|
//! use lettre::transport::EmailTransport;
|
||||||
|
//! use lettre::email::{EmailBuilder, SendableEmail};
|
||||||
|
//!
|
||||||
|
//! // Write to the local temp directory
|
||||||
|
//! let mut sender = FileEmailTransport::new(temp_dir());
|
||||||
|
//! let email = EmailBuilder::new()
|
||||||
|
//! .to("root@localhost")
|
||||||
|
//! .from("user@localhost")
|
||||||
|
//! .body("Hello World!")
|
||||||
|
//! .subject("Hello")
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap();
|
||||||
|
//!
|
||||||
|
//! let result = sender.send(email);
|
||||||
|
//! assert!(result.is_ok());
|
||||||
|
//! ```
|
||||||
|
//! Example result in `/tmp/b7c211bc-9811-45ce-8cd9-68eab575d695.txt`:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! b7c211bc-9811-45ce-8cd9-68eab575d695: from=<user@localhost> to=<root@localhost>
|
||||||
|
//! To: <root@localhost>
|
||||||
|
//! From: <user@localhost>
|
||||||
|
//! Subject: Hello
|
||||||
|
//! Date: Sat, 31 Oct 2015 13:42:19 +0100
|
||||||
|
//! Message-ID: <b7c211bc-9811-45ce-8cd9-68eab575d695.lettre@localhost>
|
||||||
|
//!
|
||||||
|
//! Hello World!
|
||||||
|
//! ```
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
#![deny(missing_docs, unsafe_code, unstable_features)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate rustc_serialize as serialize;
|
extern crate rustc_serialize;
|
||||||
extern crate crypto;
|
extern crate crypto;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
@@ -155,4 +207,3 @@ extern crate openssl;
|
|||||||
|
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
pub mod email;
|
pub mod email;
|
||||||
pub mod mailer;
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
//! TODO
|
|
||||||
|
|
||||||
use transport::EmailTransport;
|
|
||||||
use email::SendableEmail;
|
|
||||||
use transport::error::EmailResult;
|
|
||||||
|
|
||||||
/// TODO
|
|
||||||
pub struct Mailer<T: EmailTransport> {
|
|
||||||
transport: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: EmailTransport> Mailer<T> {
|
|
||||||
/// TODO
|
|
||||||
pub fn new(transport: T) -> Mailer<T> {
|
|
||||||
Mailer { transport: transport }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TODO
|
|
||||||
pub fn send<S: SendableEmail>(&mut self, email: S) -> EmailResult {
|
|
||||||
self.transport.send(email.to_addresses(),
|
|
||||||
email.from_address(),
|
|
||||||
email.message(),
|
|
||||||
email.message_id())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TODO
|
|
||||||
pub fn close(&mut self) {
|
|
||||||
self.transport.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use transport::smtp::response::{Severity, Response};
|
use transport::smtp::response::{Response, Severity};
|
||||||
use serialize::base64::FromBase64Error;
|
use rustc_serialize::base64::FromBase64Error;
|
||||||
use self::Error::*;
|
use self::Error::*;
|
||||||
|
|
||||||
/// An enum of all error kinds.
|
/// An enum of all error kinds.
|
||||||
@@ -83,8 +83,3 @@ impl From<&'static str> for Error {
|
|||||||
|
|
||||||
/// SMTP result type
|
/// SMTP result type
|
||||||
pub type EmailResult = Result<Response, Error>;
|
pub type EmailResult = Result<Response, Error>;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|||||||
53
src/transport/file/mod.rs
Normal file
53
src/transport/file/mod.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//! This transport creates a file for each email, containing the enveloppe information and the email
|
||||||
|
//! itself.
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
use transport::EmailTransport;
|
||||||
|
use transport::error::EmailResult;
|
||||||
|
use transport::smtp::response::Response;
|
||||||
|
use transport::smtp::response::{Category, Code, Severity};
|
||||||
|
use email::SendableEmail;
|
||||||
|
|
||||||
|
/// Writes the content and the enveloppe information to a file
|
||||||
|
pub struct FileEmailTransport {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileEmailTransport {
|
||||||
|
/// Creates a new transport to the given directory
|
||||||
|
pub fn new<P: AsRef<Path>>(path: P) -> FileEmailTransport {
|
||||||
|
let mut path_buf = PathBuf::new();
|
||||||
|
path_buf.push(path);
|
||||||
|
FileEmailTransport { path: path_buf }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmailTransport for FileEmailTransport {
|
||||||
|
fn send<T: SendableEmail>(&mut self, email: T) -> EmailResult {
|
||||||
|
let mut file = self.path.clone();
|
||||||
|
file.push(format!("{}.txt", email.message_id()));
|
||||||
|
|
||||||
|
let mut f = try!(File::create(file.as_path()));
|
||||||
|
|
||||||
|
let log_line = format!("{}: from=<{}> to=<{}>\n",
|
||||||
|
email.message_id(),
|
||||||
|
email.from_address(),
|
||||||
|
email.to_addresses().join("> to=<"));
|
||||||
|
|
||||||
|
try!(f.write_all(log_line.as_bytes()));
|
||||||
|
try!(f.write_all(format!("{}", email.message()).as_bytes()));
|
||||||
|
|
||||||
|
info!("{} status=<written>", log_line);
|
||||||
|
|
||||||
|
Ok(Response::new(Code::new(Severity::PositiveCompletion, Category::MailSystem, 0),
|
||||||
|
vec![format!("Ok: email written to {}",
|
||||||
|
file.to_str().unwrap_or("non-UTF-8 path"))]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
//! TODO
|
//! Represents an Email transport
|
||||||
pub mod smtp;
|
pub mod smtp;
|
||||||
pub mod stub;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod stub;
|
||||||
|
pub mod file;
|
||||||
|
|
||||||
use transport::error::EmailResult;
|
use transport::error::EmailResult;
|
||||||
|
use email::SendableEmail;
|
||||||
|
|
||||||
/// Transport method for emails
|
/// Transport method for emails
|
||||||
pub trait EmailTransport {
|
pub trait EmailTransport {
|
||||||
/// Sends the email
|
/// Sends the email
|
||||||
fn send(&mut self,
|
fn send<T: SendableEmail>(&mut self, email: T) -> EmailResult;
|
||||||
to_addresses: Vec<String>,
|
|
||||||
from_address: String,
|
|
||||||
message: String,
|
|
||||||
message_id: String)
|
|
||||||
-> EmailResult;
|
|
||||||
/// Close the transport explicitely
|
/// Close the transport explicitely
|
||||||
fn close(&mut self);
|
fn close(&mut self);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
//! Provides authentication mecanisms
|
//! Provides authentication mechanisms
|
||||||
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use serialize::base64::{self, ToBase64, FromBase64};
|
use rustc_serialize::base64::{self, FromBase64, ToBase64};
|
||||||
use serialize::hex::ToHex;
|
use rustc_serialize::hex::ToHex;
|
||||||
use crypto::hmac::Hmac;
|
use crypto::hmac::Hmac;
|
||||||
use crypto::md5::Md5;
|
use crypto::md5::Md5;
|
||||||
use crypto::mac::Mac;
|
use crypto::mac::Mac;
|
||||||
@@ -12,34 +12,34 @@ use crypto::mac::Mac;
|
|||||||
use transport::smtp::NUL;
|
use transport::smtp::NUL;
|
||||||
use transport::error::Error;
|
use transport::error::Error;
|
||||||
|
|
||||||
/// Represents authentication mecanisms
|
/// Represents authentication mechanisms
|
||||||
#[derive(PartialEq,Eq,Copy,Clone,Hash,Debug)]
|
#[derive(PartialEq,Eq,Copy,Clone,Hash,Debug)]
|
||||||
pub enum Mecanism {
|
pub enum Mechanism {
|
||||||
/// PLAIN authentication mecanism
|
/// PLAIN authentication mechanism
|
||||||
/// RFC 4616: https://tools.ietf.org/html/rfc4616
|
/// RFC 4616: https://tools.ietf.org/html/rfc4616
|
||||||
Plain,
|
Plain,
|
||||||
/// CRAM-MD5 authentication mecanism
|
/// CRAM-MD5 authentication mechanism
|
||||||
/// RFC 2195: https://tools.ietf.org/html/rfc2195
|
/// RFC 2195: https://tools.ietf.org/html/rfc2195
|
||||||
CramMd5,
|
CramMd5,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Mecanism {
|
impl Display for Mechanism {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f,
|
write!(f,
|
||||||
"{}",
|
"{}",
|
||||||
match *self {
|
match *self {
|
||||||
Mecanism::Plain => "PLAIN",
|
Mechanism::Plain => "PLAIN",
|
||||||
Mecanism::CramMd5 => "CRAM-MD5",
|
Mechanism::CramMd5 => "CRAM-MD5",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mecanism {
|
impl Mechanism {
|
||||||
/// Does the mecanism supports initial response
|
/// Does the mechanism supports initial response
|
||||||
pub fn supports_initial_response(&self) -> bool {
|
pub fn supports_initial_response(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Mecanism::Plain => true,
|
Mechanism::Plain => true,
|
||||||
Mecanism::CramMd5 => false,
|
Mechanism::CramMd5 => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,18 +51,20 @@ impl Mecanism {
|
|||||||
challenge: Option<&str>)
|
challenge: Option<&str>)
|
||||||
-> Result<String, Error> {
|
-> Result<String, Error> {
|
||||||
match *self {
|
match *self {
|
||||||
Mecanism::Plain => {
|
Mechanism::Plain => {
|
||||||
match challenge {
|
match challenge {
|
||||||
Some(_) => Err(Error::ClientError("This mecanism does not expect a challenge")),
|
Some(_) =>
|
||||||
|
Err(Error::ClientError("This mechanism does not expect a challenge")),
|
||||||
None => Ok(format!("{}{}{}{}", NUL, username, NUL, password)
|
None => Ok(format!("{}{}{}{}", NUL, username, NUL, password)
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
.to_base64(base64::STANDARD)),
|
.to_base64(base64::STANDARD)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mecanism::CramMd5 => {
|
Mechanism::CramMd5 => {
|
||||||
let encoded_challenge = match challenge {
|
let encoded_challenge = match challenge {
|
||||||
Some(challenge) => challenge,
|
Some(challenge) => challenge,
|
||||||
None => return Err(Error::ClientError("This mecanism does expect a challenge")),
|
None =>
|
||||||
|
return Err(Error::ClientError("This mechanism does expect a challenge")),
|
||||||
};
|
};
|
||||||
|
|
||||||
let decoded_challenge = match encoded_challenge.from_base64() {
|
let decoded_challenge = match encoded_challenge.from_base64() {
|
||||||
@@ -83,27 +85,28 @@ impl Mecanism {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::Mecanism;
|
use super::Mechanism;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_plain() {
|
fn test_plain() {
|
||||||
let mecanism = Mecanism::Plain;
|
let mechanism = Mechanism::Plain;
|
||||||
|
|
||||||
assert_eq!(mecanism.response("username", "password", None).unwrap(),
|
assert_eq!(mechanism.response("username", "password", None).unwrap(),
|
||||||
"AHVzZXJuYW1lAHBhc3N3b3Jk");
|
"AHVzZXJuYW1lAHBhc3N3b3Jk");
|
||||||
assert!(mecanism.response("username", "password", Some("test")).is_err());
|
assert!(mechanism.response("username", "password", Some("test")).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cram_md5() {
|
fn test_cram_md5() {
|
||||||
let mecanism = Mecanism::CramMd5;
|
let mechanism = Mechanism::CramMd5;
|
||||||
|
|
||||||
assert_eq!(mecanism.response("alice",
|
assert_eq!(mechanism.response("alice",
|
||||||
"wonderland",
|
"wonderland",
|
||||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="))
|
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=\
|
||||||
.unwrap(),
|
="))
|
||||||
|
.unwrap(),
|
||||||
"YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=");
|
"YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=");
|
||||||
assert!(mecanism.response("alice", "wonderland", Some("tést")).is_err());
|
assert!(mechanism.response("alice", "wonderland", Some("tést")).is_err());
|
||||||
assert!(mecanism.response("alice", "wonderland", None).is_err());
|
assert!(mechanism.response("alice", "wonderland", None).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
use std::io::{BufRead, Read, Write};
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::io::{BufRead, Read, Write};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use bufstream::BufStream;
|
use bufstream::BufStream;
|
||||||
use openssl::ssl::SslContext;
|
use openssl::ssl::SslContext;
|
||||||
|
|
||||||
|
use transport::error::{EmailResult, Error};
|
||||||
use transport::smtp::response::ResponseParser;
|
use transport::smtp::response::ResponseParser;
|
||||||
use transport::smtp::authentication::Mecanism;
|
use transport::smtp::authentication::Mechanism;
|
||||||
use transport::error::{Error, EmailResult};
|
|
||||||
use transport::smtp::client::net::{Connector, NetworkStream};
|
use transport::smtp::client::net::{Connector, NetworkStream};
|
||||||
use transport::smtp::{CRLF, MESSAGE_ENDING};
|
use transport::smtp::{CRLF, MESSAGE_ENDING};
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@ fn escape_dot(string: &str) -> String {
|
|||||||
} else {
|
} else {
|
||||||
string.to_string()
|
string.to_string()
|
||||||
}
|
}
|
||||||
.replace("\r.", "\r..")
|
.replace("\r.", "\r..")
|
||||||
.replace("\n.", "\n..")
|
.replace("\n.", "\n..")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the string replacing all the CRLF with "\<CRLF\>"
|
/// Returns the string replacing all the CRLF with "\<CRLF\>"
|
||||||
@@ -80,7 +80,6 @@ impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
|
|||||||
|
|
||||||
/// Upgrades the underlying connection to SSL/TLS
|
/// Upgrades the underlying connection to SSL/TLS
|
||||||
pub fn upgrade_tls_stream(&mut self, ssl_context: &SslContext) -> io::Result<()> {
|
pub fn upgrade_tls_stream(&mut self, ssl_context: &SslContext) -> io::Result<()> {
|
||||||
//let current_stream = self.stream.clone();
|
|
||||||
if self.stream.is_some() {
|
if self.stream.is_some() {
|
||||||
self.stream.as_mut().unwrap().get_mut().upgrade_tls(ssl_context)
|
self.stream.as_mut().unwrap().get_mut().upgrade_tls(ssl_context)
|
||||||
} else {
|
} else {
|
||||||
@@ -89,7 +88,10 @@ impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Connects to the configured server
|
/// Connects to the configured server
|
||||||
pub fn connect<A: ToSocketAddrs>(&mut self, addr: &A) -> EmailResult {
|
pub fn connect<A: ToSocketAddrs>(&mut self,
|
||||||
|
addr: &A,
|
||||||
|
ssl_context: Option<&SslContext>)
|
||||||
|
-> EmailResult {
|
||||||
// Connect should not be called when the client is already connected
|
// Connect should not be called when the client is already connected
|
||||||
if self.stream.is_some() {
|
if self.stream.is_some() {
|
||||||
return_err!("The connection is already established", self);
|
return_err!("The connection is already established", self);
|
||||||
@@ -103,7 +105,7 @@ impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Try to connect
|
// Try to connect
|
||||||
self.set_stream(try!(Connector::connect(&server_addr, None)));
|
self.set_stream(try!(Connector::connect(&server_addr, ssl_context)));
|
||||||
|
|
||||||
self.get_reply()
|
self.get_reply()
|
||||||
}
|
}
|
||||||
@@ -174,13 +176,13 @@ impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
|
|||||||
self.command("RSET")
|
self.command("RSET")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends an AUTH command with the given mecanism
|
/// Sends an AUTH command with the given mechanism
|
||||||
pub fn auth(&mut self, mecanism: Mecanism, username: &str, password: &str) -> EmailResult {
|
pub fn auth(&mut self, mechanism: Mechanism, username: &str, password: &str) -> EmailResult {
|
||||||
|
|
||||||
if mecanism.supports_initial_response() {
|
if mechanism.supports_initial_response() {
|
||||||
self.command(&format!("AUTH {} {}",
|
self.command(&format!("AUTH {} {}",
|
||||||
mecanism,
|
mechanism,
|
||||||
try!(mecanism.response(username, password, None))))
|
try!(mechanism.response(username, password, None))))
|
||||||
} else {
|
} else {
|
||||||
let encoded_challenge = match try!(self.command("AUTH CRAM-MD5")).first_word() {
|
let encoded_challenge = match try!(self.command("AUTH CRAM-MD5")).first_word() {
|
||||||
Some(challenge) => challenge,
|
Some(challenge) => challenge,
|
||||||
@@ -189,9 +191,9 @@ impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
|
|||||||
|
|
||||||
debug!("CRAM challenge: {}", encoded_challenge);
|
debug!("CRAM challenge: {}", encoded_challenge);
|
||||||
|
|
||||||
let cram_response = try!(mecanism.response(username,
|
let cram_response = try!(mechanism.response(username,
|
||||||
password,
|
password,
|
||||||
Some(&encoded_challenge)));
|
Some(&encoded_challenge)));
|
||||||
|
|
||||||
self.command(&format!("{}", cram_response))
|
self.command(&format!("{}", cram_response))
|
||||||
}
|
}
|
||||||
@@ -247,7 +249,7 @@ impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::{escape_dot, remove_crlf, escape_crlf};
|
use super::{escape_crlf, escape_dot, remove_crlf};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_escape_dot() {
|
fn test_escape_dot() {
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
//! A trait to represent a stream
|
//! A trait to represent a stream
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Read, Write, ErrorKind};
|
use std::io::{ErrorKind, Read, Write};
|
||||||
use std::net::SocketAddr;
|
use std::net::{SocketAddr, TcpStream};
|
||||||
use std::net::TcpStream;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
|
||||||
use openssl::ssl::{SslContext, SslStream};
|
use openssl::ssl::{SslContext, SslStream};
|
||||||
|
|
||||||
/// A trait for the concept of opening a stream
|
/// A trait for the concept of opening a stream
|
||||||
pub trait Connector {
|
pub trait Connector: Sized {
|
||||||
/// Opens a connection to the given IP socket
|
/// Opens a connection to the given IP socket
|
||||||
fn connect(addr: &SocketAddr, ssl_context: Option<&SslContext>) -> io::Result<Self>;
|
fn connect(addr: &SocketAddr, ssl_context: Option<&SslContext>) -> io::Result<Self>;
|
||||||
/// Upgrades to TLS connection
|
/// Upgrades to TLS connection
|
||||||
@@ -22,7 +21,7 @@ impl Connector for NetworkStream {
|
|||||||
let tcp_stream = try!(TcpStream::connect(addr));
|
let tcp_stream = try!(TcpStream::connect(addr));
|
||||||
|
|
||||||
match ssl_context {
|
match ssl_context {
|
||||||
Some(context) => match SslStream::new(&context, tcp_stream) {
|
Some(context) => match SslStream::connect_generic(context, tcp_stream) {
|
||||||
Ok(stream) => Ok(NetworkStream::Ssl(stream)),
|
Ok(stream) => Ok(NetworkStream::Ssl(stream)),
|
||||||
Err(err) => Err(io::Error::new(ErrorKind::Other, err)),
|
Err(err) => Err(io::Error::new(ErrorKind::Other, err)),
|
||||||
},
|
},
|
||||||
@@ -32,7 +31,7 @@ impl Connector for NetworkStream {
|
|||||||
|
|
||||||
fn upgrade_tls(&mut self, ssl_context: &SslContext) -> io::Result<()> {
|
fn upgrade_tls(&mut self, ssl_context: &SslContext) -> io::Result<()> {
|
||||||
*self = match self.clone() {
|
*self = match self.clone() {
|
||||||
NetworkStream::Plain(stream) => match SslStream::new(ssl_context, stream) {
|
NetworkStream::Plain(stream) => match SslStream::connect_generic(ssl_context, stream) {
|
||||||
Ok(ssl_stream) => NetworkStream::Ssl(ssl_stream),
|
Ok(ssl_stream) => NetworkStream::Ssl(ssl_stream),
|
||||||
Err(err) => return Err(io::Error::new(ErrorKind::Other, err)),
|
Err(err) => return Err(io::Error::new(ErrorKind::Other, err)),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
//! ESMTP features
|
//! ESMTP features
|
||||||
|
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use transport::smtp::response::Response;
|
|
||||||
use transport::error::Error;
|
use transport::error::Error;
|
||||||
use transport::smtp::authentication::Mecanism;
|
use transport::smtp::response::Response;
|
||||||
|
use transport::smtp::authentication::Mechanism;
|
||||||
|
|
||||||
/// Supported ESMTP keywords
|
/// Supported ESMTP keywords
|
||||||
#[derive(PartialEq,Eq,Hash,Clone,Debug)]
|
#[derive(PartialEq,Eq,Hash,Clone,Debug)]
|
||||||
@@ -24,8 +24,8 @@ pub enum Extension {
|
|||||||
///
|
///
|
||||||
/// RFC 2487: https://tools.ietf.org/html/rfc2487
|
/// RFC 2487: https://tools.ietf.org/html/rfc2487
|
||||||
StartTls,
|
StartTls,
|
||||||
/// AUTH mecanism
|
/// AUTH mechanism
|
||||||
Authentication(Mecanism),
|
Authentication(Mechanism),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Extension {
|
impl Display for Extension {
|
||||||
@@ -34,7 +34,7 @@ impl Display for Extension {
|
|||||||
Extension::EightBitMime => write!(f, "{}", "8BITMIME"),
|
Extension::EightBitMime => write!(f, "{}", "8BITMIME"),
|
||||||
Extension::SmtpUtfEight => write!(f, "{}", "SMTPUTF8"),
|
Extension::SmtpUtfEight => write!(f, "{}", "SMTPUTF8"),
|
||||||
Extension::StartTls => write!(f, "{}", "STARTTLS"),
|
Extension::StartTls => write!(f, "{}", "STARTTLS"),
|
||||||
Extension::Authentication(ref mecanism) => write!(f, "{} {}", "AUTH", mecanism),
|
Extension::Authentication(ref mechanism) => write!(f, "{} {}", "AUTH", mechanism),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,13 +88,13 @@ impl ServerInfo {
|
|||||||
features.insert(Extension::StartTls);
|
features.insert(Extension::StartTls);
|
||||||
}
|
}
|
||||||
"AUTH" => {
|
"AUTH" => {
|
||||||
for &mecanism in &splitted[1..] {
|
for &mechanism in &splitted[1..] {
|
||||||
match mecanism {
|
match mechanism {
|
||||||
"PLAIN" => {
|
"PLAIN" => {
|
||||||
features.insert(Extension::Authentication(Mecanism::Plain));
|
features.insert(Extension::Authentication(Mechanism::Plain));
|
||||||
}
|
}
|
||||||
"CRAM-MD5" => {
|
"CRAM-MD5" => {
|
||||||
features.insert(Extension::Authentication(Mecanism::CramMd5));
|
features.insert(Extension::Authentication(Mechanism::CramMd5));
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
@@ -116,8 +116,8 @@ impl ServerInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the server supports an ESMTP feature
|
/// Checks if the server supports an ESMTP feature
|
||||||
pub fn supports_auth_mecanism(&self, mecanism: Mecanism) -> bool {
|
pub fn supports_auth_mechanism(&self, mechanism: Mechanism) -> bool {
|
||||||
self.features.contains(&Extension::Authentication(mecanism))
|
self.features.contains(&Extension::Authentication(mechanism))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,15 +125,15 @@ impl ServerInfo {
|
|||||||
mod test {
|
mod test {
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use super::{ServerInfo, Extension};
|
use super::{Extension, ServerInfo};
|
||||||
use transport::smtp::authentication::Mecanism;
|
use transport::smtp::authentication::Mechanism;
|
||||||
use transport::smtp::response::{Code, Response, Severity, Category};
|
use transport::smtp::response::{Category, Code, Response, Severity};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_extension_fmt() {
|
fn test_extension_fmt() {
|
||||||
assert_eq!(format!("{}", Extension::EightBitMime),
|
assert_eq!(format!("{}", Extension::EightBitMime),
|
||||||
"8BITMIME".to_string());
|
"8BITMIME".to_string());
|
||||||
assert_eq!(format!("{}", Extension::Authentication(Mecanism::Plain)),
|
assert_eq!(format!("{}", Extension::Authentication(Mechanism::Plain)),
|
||||||
"AUTH PLAIN".to_string());
|
"AUTH PLAIN".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ mod test {
|
|||||||
"name with no supported features".to_string());
|
"name with no supported features".to_string());
|
||||||
|
|
||||||
let mut plain = HashSet::new();
|
let mut plain = HashSet::new();
|
||||||
assert!(plain.insert(Extension::Authentication(Mecanism::Plain)));
|
assert!(plain.insert(Extension::Authentication(Mechanism::Plain)));
|
||||||
|
|
||||||
assert_eq!(format!("{}",
|
assert_eq!(format!("{}",
|
||||||
ServerInfo {
|
ServerInfo {
|
||||||
@@ -190,7 +190,7 @@ mod test {
|
|||||||
|
|
||||||
assert!(server_info.supports_feature(&Extension::EightBitMime));
|
assert!(server_info.supports_feature(&Extension::EightBitMime));
|
||||||
assert!(!server_info.supports_feature(&Extension::StartTls));
|
assert!(!server_info.supports_feature(&Extension::StartTls));
|
||||||
assert!(!server_info.supports_auth_mecanism(Mecanism::CramMd5));
|
assert!(!server_info.supports_auth_mechanism(Mechanism::CramMd5));
|
||||||
|
|
||||||
let response2 = Response::new(Code::new(Severity::PositiveCompletion,
|
let response2 = Response::new(Code::new(Severity::PositiveCompletion,
|
||||||
Category::Unspecified4,
|
Category::Unspecified4,
|
||||||
@@ -202,8 +202,8 @@ mod test {
|
|||||||
|
|
||||||
let mut features2 = HashSet::new();
|
let mut features2 = HashSet::new();
|
||||||
assert!(features2.insert(Extension::EightBitMime));
|
assert!(features2.insert(Extension::EightBitMime));
|
||||||
assert!(features2.insert(Extension::Authentication(Mecanism::Plain)));
|
assert!(features2.insert(Extension::Authentication(Mechanism::Plain)));
|
||||||
assert!(features2.insert(Extension::Authentication(Mecanism::CramMd5)));
|
assert!(features2.insert(Extension::Authentication(Mechanism::CramMd5)));
|
||||||
|
|
||||||
let server_info2 = ServerInfo {
|
let server_info2 = ServerInfo {
|
||||||
name: "me".to_string(),
|
name: "me".to_string(),
|
||||||
@@ -213,8 +213,8 @@ mod test {
|
|||||||
assert_eq!(ServerInfo::from_response(&response2).unwrap(), server_info2);
|
assert_eq!(ServerInfo::from_response(&response2).unwrap(), server_info2);
|
||||||
|
|
||||||
assert!(server_info2.supports_feature(&Extension::EightBitMime));
|
assert!(server_info2.supports_feature(&Extension::EightBitMime));
|
||||||
assert!(server_info2.supports_auth_mecanism(Mecanism::Plain));
|
assert!(server_info2.supports_auth_mechanism(Mechanism::Plain));
|
||||||
assert!(server_info2.supports_auth_mecanism(Mecanism::CramMd5));
|
assert!(server_info2.supports_auth_mechanism(Mechanism::CramMd5));
|
||||||
assert!(!server_info2.supports_feature(&Extension::StartTls));
|
assert!(!server_info2.supports_feature(&Extension::StartTls));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
use std::string::String;
|
use std::string::String;
|
||||||
use std::net::{SocketAddr, ToSocketAddrs};
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
|
|
||||||
use openssl::ssl::{SslMethod, SslContext};
|
use openssl::ssl::{SslContext, SslMethod};
|
||||||
|
|
||||||
use email::SendableEmail;
|
|
||||||
use transport::smtp::extension::{Extension, ServerInfo};
|
|
||||||
use transport::error::{EmailResult, Error};
|
use transport::error::{EmailResult, Error};
|
||||||
|
use transport::smtp::extension::{Extension, ServerInfo};
|
||||||
use transport::smtp::client::Client;
|
use transport::smtp::client::Client;
|
||||||
use transport::smtp::authentication::Mecanism;
|
use transport::smtp::authentication::Mechanism;
|
||||||
use transport::EmailTransport;
|
use transport::EmailTransport;
|
||||||
|
use email::SendableEmail;
|
||||||
|
|
||||||
pub mod extension;
|
pub mod extension;
|
||||||
pub mod authentication;
|
pub mod authentication;
|
||||||
@@ -18,7 +18,8 @@ pub mod response;
|
|||||||
pub mod client;
|
pub mod client;
|
||||||
|
|
||||||
// Registrated port numbers:
|
// Registrated port numbers:
|
||||||
// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
|
// https://www.iana.
|
||||||
|
// org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
|
||||||
|
|
||||||
/// Default smtp port
|
/// Default smtp port
|
||||||
pub static SMTP_PORT: u16 = 25;
|
pub static SMTP_PORT: u16 = 25;
|
||||||
@@ -46,9 +47,17 @@ pub static NUL: &'static str = "\0";
|
|||||||
/// TLS security level
|
/// TLS security level
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SecurityLevel {
|
pub enum SecurityLevel {
|
||||||
/// Only send an email on encrypted connection
|
/// Use a TLS wrapped connection
|
||||||
|
///
|
||||||
|
/// Non RFC-compliant, should only be used if the server does not support STARTTLS.
|
||||||
|
EncryptedWrapper,
|
||||||
|
/// Only send an email on encrypted connection (with STARTTLS)
|
||||||
|
///
|
||||||
|
/// Recommended mode, prevents MITM when used with verified certificates.
|
||||||
AlwaysEncrypt,
|
AlwaysEncrypt,
|
||||||
/// Use TLS when available
|
/// Use TLS when available (with STARTTLS)
|
||||||
|
///
|
||||||
|
/// Default mode.
|
||||||
Opportunistic,
|
Opportunistic,
|
||||||
/// Never use TLS
|
/// Never use TLS
|
||||||
NeverEncrypt,
|
NeverEncrypt,
|
||||||
@@ -74,8 +83,8 @@ pub struct SmtpTransportBuilder {
|
|||||||
security_level: SecurityLevel,
|
security_level: SecurityLevel,
|
||||||
/// Enable UTF8 mailboxes in enveloppe or headers
|
/// Enable UTF8 mailboxes in enveloppe or headers
|
||||||
smtp_utf8: bool,
|
smtp_utf8: bool,
|
||||||
/// List of authentication mecanism, sorted by priority
|
/// List of authentication mechanism, sorted by priority
|
||||||
authentication_mecanisms: Vec<Mecanism>,
|
authentication_mechanisms: Vec<Mechanism>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder for the SMTP SmtpTransport
|
/// Builder for the SMTP SmtpTransport
|
||||||
@@ -94,7 +103,7 @@ impl SmtpTransportBuilder {
|
|||||||
connection_reuse_count_limit: 100,
|
connection_reuse_count_limit: 100,
|
||||||
connection_reuse: false,
|
connection_reuse: false,
|
||||||
hello_name: "localhost".to_string(),
|
hello_name: "localhost".to_string(),
|
||||||
authentication_mecanisms: vec![Mecanism::CramMd5, Mecanism::Plain],
|
authentication_mechanisms: vec![Mechanism::CramMd5, Mechanism::Plain],
|
||||||
}),
|
}),
|
||||||
None => Err(From::from("Could nor resolve hostname")),
|
None => Err(From::from("Could nor resolve hostname")),
|
||||||
}
|
}
|
||||||
@@ -111,13 +120,29 @@ impl SmtpTransportBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Require SSL/TLS using STARTTLS
|
/// Set the security level for SSL/TLS
|
||||||
pub fn security_level(mut self, level: SecurityLevel) -> SmtpTransportBuilder {
|
pub fn security_level(mut self, level: SecurityLevel) -> SmtpTransportBuilder {
|
||||||
self.security_level = level;
|
self.security_level = level;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Require SSL/TLS using STARTTLS
|
/// Require SSL/TLS using STARTTLS
|
||||||
|
///
|
||||||
|
/// Incompatible with `ssl_wrapper()``
|
||||||
|
pub fn encrypt(mut self) -> SmtpTransportBuilder {
|
||||||
|
self.security_level = SecurityLevel::AlwaysEncrypt;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Require SSL/TLS using STARTTLS
|
||||||
|
///
|
||||||
|
/// Incompatible with `encrypt()`
|
||||||
|
pub fn ssl_wrapper(mut self) -> SmtpTransportBuilder {
|
||||||
|
self.security_level = SecurityLevel::EncryptedWrapper;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable SMTPUTF8 if the server supports it
|
||||||
pub fn smtp_utf8(mut self, enabled: bool) -> SmtpTransportBuilder {
|
pub fn smtp_utf8(mut self, enabled: bool) -> SmtpTransportBuilder {
|
||||||
self.smtp_utf8 = enabled;
|
self.smtp_utf8 = enabled;
|
||||||
self
|
self
|
||||||
@@ -147,9 +172,9 @@ impl SmtpTransportBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the authentication mecanisms
|
/// Set the authentication mechanisms
|
||||||
pub fn authentication_mecanisms(mut self, mecanisms: Vec<Mecanism>) -> SmtpTransportBuilder {
|
pub fn authentication_mechanisms(mut self, mechanisms: Vec<Mechanism>) -> SmtpTransportBuilder {
|
||||||
self.authentication_mecanisms = mecanisms;
|
self.authentication_mechanisms = mechanisms;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,12 +269,14 @@ impl SmtpTransport {
|
|||||||
|
|
||||||
impl EmailTransport for SmtpTransport {
|
impl EmailTransport for SmtpTransport {
|
||||||
/// Sends an email
|
/// Sends an email
|
||||||
fn send(&mut self,
|
fn send<T: SendableEmail>(&mut self, email: T) -> EmailResult {
|
||||||
to_addresses: Vec<String>,
|
|
||||||
from_address: String,
|
// Extract email information
|
||||||
message: String,
|
let message_id = email.message_id();
|
||||||
message_id: String)
|
let from_address = email.from_address();
|
||||||
-> EmailResult {
|
let to_addresses = email.to_addresses();
|
||||||
|
let message = email.message();
|
||||||
|
|
||||||
// Check if the connection is still available
|
// Check if the connection is still available
|
||||||
if self.state.connection_reuse_count > 0 {
|
if self.state.connection_reuse_count > 0 {
|
||||||
if !self.client.is_connected() {
|
if !self.client.is_connected() {
|
||||||
@@ -257,9 +284,13 @@ impl EmailTransport for SmtpTransport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a usable connection, test if the server answers and hello has been sent
|
|
||||||
if self.state.connection_reuse_count == 0 {
|
if self.state.connection_reuse_count == 0 {
|
||||||
try!(self.client.connect(&self.client_info.server_addr));
|
try!(self.client.connect(&self.client_info.server_addr,
|
||||||
|
match &self.client_info.security_level {
|
||||||
|
&SecurityLevel::EncryptedWrapper =>
|
||||||
|
Some(&self.client_info.ssl_context),
|
||||||
|
_ => None,
|
||||||
|
}));
|
||||||
|
|
||||||
// Log the connection
|
// Log the connection
|
||||||
info!("connection established to {}", self.client_info.server_addr);
|
info!("connection established to {}", self.client_info.server_addr);
|
||||||
@@ -272,6 +303,7 @@ impl EmailTransport for SmtpTransport {
|
|||||||
return Err(From::from("Could not encrypt connection, aborting")),
|
return Err(From::from("Could not encrypt connection, aborting")),
|
||||||
(&SecurityLevel::Opportunistic, false) => (),
|
(&SecurityLevel::Opportunistic, false) => (),
|
||||||
(&SecurityLevel::NeverEncrypt, _) => (),
|
(&SecurityLevel::NeverEncrypt, _) => (),
|
||||||
|
(&SecurityLevel::EncryptedWrapper, _) => (),
|
||||||
(_, true) => {
|
(_, true) => {
|
||||||
try_smtp!(self.client.starttls(), self);
|
try_smtp!(self.client.starttls(), self);
|
||||||
try_smtp!(self.client.upgrade_tls_stream(&self.client_info.ssl_context),
|
try_smtp!(self.client.upgrade_tls_stream(&self.client_info.ssl_context),
|
||||||
@@ -289,29 +321,29 @@ impl EmailTransport for SmtpTransport {
|
|||||||
|
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
|
|
||||||
for mecanism in self.client_info.authentication_mecanisms.clone() {
|
for mechanism in self.client_info.authentication_mechanisms.clone() {
|
||||||
if self.server_info.as_ref().unwrap().supports_auth_mecanism(mecanism) {
|
if self.server_info.as_ref().unwrap().supports_auth_mechanism(mechanism) {
|
||||||
found = true;
|
found = true;
|
||||||
try_smtp!(self.client.auth(mecanism, &username, &password), self);
|
try_smtp!(self.client.auth(mechanism, &username, &password), self);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
info!("No supported authentication mecanisms available");
|
info!("No supported authentication mechanisms available");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mail
|
// Mail
|
||||||
let mail_options = match (self.server_info
|
let mail_options = match (self.server_info
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.supports_feature(&Extension::EightBitMime),
|
.supports_feature(&Extension::EightBitMime),
|
||||||
self.server_info
|
self.server_info
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.supports_feature(&Extension::EightBitMime)) {
|
.supports_feature(&Extension::SmtpUtfEight)) {
|
||||||
(true, true) => Some("BODY=8BITMIME SMTPUTF8"),
|
(true, true) => Some("BODY=8BITMIME SMTPUTF8"),
|
||||||
(true, false) => Some("BODY=8BITMIME"),
|
(true, false) => Some("BODY=8BITMIME"),
|
||||||
(false, _) => None,
|
(false, _) => None,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//! SMTP response, containing a mandatory return code and an optional text message
|
//! SMTP response, containing a mandatory return code and an optional text
|
||||||
|
//! message
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::fmt::{Display, Formatter, Result};
|
use std::fmt::{Display, Formatter, Result};
|
||||||
@@ -275,7 +276,7 @@ impl Response {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::{Severity, Category, Response, ResponseParser, Code};
|
use super::{Category, Code, Response, ResponseParser, Severity};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_severity_from_str() {
|
fn test_severity_from_str() {
|
||||||
|
|||||||
@@ -1,26 +1,21 @@
|
|||||||
//! TODO
|
//! This transport is a stub that only logs the message, and always returns
|
||||||
|
//! succes
|
||||||
|
|
||||||
use transport::error::EmailResult;
|
use transport::error::EmailResult;
|
||||||
use transport::smtp::response::Response;
|
use transport::smtp::response::{Category, Code, Response, Severity};
|
||||||
use transport::EmailTransport;
|
use transport::EmailTransport;
|
||||||
use transport::smtp::response::{Code, Category, Severity};
|
use email::SendableEmail;
|
||||||
|
|
||||||
/// TODO
|
/// This transport does nothing exept logging the message enveloppe
|
||||||
pub struct StubEmailTransport;
|
pub struct StubEmailTransport;
|
||||||
|
|
||||||
impl EmailTransport for StubEmailTransport {
|
impl EmailTransport for StubEmailTransport {
|
||||||
fn send(&mut self,
|
fn send<T: SendableEmail>(&mut self, email: T) -> EmailResult {
|
||||||
to_addresses: Vec<String>,
|
|
||||||
from_address: String,
|
|
||||||
message: String,
|
|
||||||
message_id: String)
|
|
||||||
-> EmailResult {
|
|
||||||
|
|
||||||
let _ = message;
|
info!("{}: from=<{}> to=<{:?}>",
|
||||||
info!("message '{}': from '{}' to '{:?}'",
|
email.message_id(),
|
||||||
message_id,
|
email.from_address(),
|
||||||
from_address,
|
email.to_addresses());
|
||||||
to_addresses);
|
|
||||||
Ok(Response::new(Code::new(Severity::PositiveCompletion, Category::MailSystem, 0),
|
Ok(Response::new(Code::new(Severity::PositiveCompletion, Category::MailSystem, 0),
|
||||||
vec!["Ok: email logged".to_string()]))
|
vec!["Ok: email logged".to_string()]))
|
||||||
}
|
}
|
||||||
|
|||||||
17
tests/lib.rs
17
tests/lib.rs
@@ -1,14 +1,5 @@
|
|||||||
// Copyright 2014 Alexis Mousset. See the COPYRIGHT
|
extern crate lettre;
|
||||||
// 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.
|
|
||||||
|
|
||||||
#[test]
|
mod transport_smtp;
|
||||||
|
mod transport_stub;
|
||||||
fn foo() {
|
mod transport_file;
|
||||||
assert!(true);
|
|
||||||
}
|
|
||||||
|
|||||||
37
tests/transport_file.rs
Normal file
37
tests/transport_file.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
extern crate lettre;
|
||||||
|
|
||||||
|
use std::env::temp_dir;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::fs::remove_file;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use lettre::transport::file::FileEmailTransport;
|
||||||
|
use lettre::transport::EmailTransport;
|
||||||
|
use lettre::email::{EmailBuilder, SendableEmail};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_transport() {
|
||||||
|
let mut sender = FileEmailTransport::new(temp_dir());
|
||||||
|
let email = EmailBuilder::new()
|
||||||
|
.to("root@localhost")
|
||||||
|
.from("user@localhost")
|
||||||
|
.body("Hello World!")
|
||||||
|
.subject("Hello")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let result = sender.send(email.clone());
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let message_id = email.message_id();
|
||||||
|
let file = format!("{}/{}.txt", temp_dir().to_str().unwrap(), message_id);
|
||||||
|
let mut f = File::open(file.clone()).unwrap();
|
||||||
|
let mut buffer = String::new();
|
||||||
|
let _ = f.read_to_string(&mut buffer);
|
||||||
|
|
||||||
|
assert_eq!(buffer,
|
||||||
|
format!("{}: from=<user@localhost> to=<root@localhost>\n{}",
|
||||||
|
message_id,
|
||||||
|
email.message()));
|
||||||
|
|
||||||
|
remove_file(file).unwrap();
|
||||||
|
}
|
||||||
19
tests/transport_smtp.rs
Normal file
19
tests/transport_smtp.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
extern crate lettre;
|
||||||
|
|
||||||
|
use lettre::transport::smtp::SmtpTransportBuilder;
|
||||||
|
use lettre::transport::EmailTransport;
|
||||||
|
use lettre::email::EmailBuilder;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_sender() {
|
||||||
|
let mut sender = SmtpTransportBuilder::localhost().unwrap().build();
|
||||||
|
let email = EmailBuilder::new()
|
||||||
|
.to("root@localhost")
|
||||||
|
.from("user@localhost")
|
||||||
|
.body("Hello World!")
|
||||||
|
.subject("Hello")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let result = sender.send(email);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
19
tests/transport_stub.rs
Normal file
19
tests/transport_stub.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
extern crate lettre;
|
||||||
|
|
||||||
|
use lettre::transport::stub::StubEmailTransport;
|
||||||
|
use lettre::transport::EmailTransport;
|
||||||
|
use lettre::email::EmailBuilder;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stub_transport() {
|
||||||
|
let mut sender = StubEmailTransport;
|
||||||
|
let email = EmailBuilder::new()
|
||||||
|
.to("root@localhost")
|
||||||
|
.from("user@localhost")
|
||||||
|
.body("Hello World!")
|
||||||
|
.subject("Hello")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let result = sender.send(email);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user