Compare commits

..

19 Commits

Author SHA1 Message Date
Alexis Mousset
9d61e6ec1f 0.6.3 release 2020-10-21 21:06:20 +02:00
Pyry Kontio
7f8c13910d Bump openssl major version: 0.9 -> 0.10. Leaving the range to continue including 0.9, so this is a patch-level bump. Bump lettre patch version to 0.6.3. 2020-10-21 21:03:03 +02:00
Alexis Mousset
364e40f5d9 Bump to v0.6.2 2017-02-18 18:28:36 +01:00
Alexis Mousset
cb08bc5527 feat(all): Update uuid crate to 0.4 2017-02-18 18:24:25 +01:00
Alexis Mousset
6c7c3ba9fa feat(all): Update env_logger 2017-02-18 18:24:13 +01:00
Zack Mullaly
53e79c0ac4 feat(transport): Upgrade to OpenSSL ^0.9 2017-02-18 18:23:21 +01:00
Alexis Mousset
53f9bada4c chore(all): Bump to v0.6.1 2016-10-19 23:19:32 +02:00
Alexis Mousset
ce7d55ffa8 Merge pull request #96 from amousset/master
docs(all): Add complete documentation information to README
2016-10-19 22:32:07 +02:00
Alexis Mousset
a6ea43a842 Merge branch 'master' into master 2016-10-19 22:24:52 +02:00
Alexis Mousset
eac29768ae docs(all): Add complete documentation information to README 2016-10-19 22:23:43 +02:00
Alexis Mousset
d944aed9d3 Merge pull request #93 from amousset/master
docs(all): Force building tests before coverage computing
2016-10-19 02:33:33 +02:00
Alexis Mousset
67318ac759 docs(all): Force building tests before coverage computing 2016-10-19 02:26:25 +02:00
Alexis Mousset
90999bfc24 Merge pull request #92 from amousset/improve-doc-build
docs(all): Fix token name
2016-10-19 02:05:36 +02:00
Alexis Mousset
7635830399 Merge branch 'master' into improve-doc-build 2016-10-19 01:58:05 +02:00
Alexis Mousset
4fbd06e18b docs(all): Fix token name 2016-10-19 01:56:53 +02:00
Alexis Mousset
5247e2c2aa Merge pull request #91 from amousset/improve-doc-build
docs(all): Build seperate docs for each release
2016-10-19 01:43:20 +02:00
Alexis Mousset
8e21de8de3 docs(all): Build seperate docs for each release 2016-10-19 01:37:51 +02:00
Alexis Mousset
d976f48b7b Merge pull request #86 from ConnyOnny/master
fix(email): address-list for "To", "From" etc.
2016-10-18 00:23:47 +02:00
Constantin Berhard
9ed51a2d3d fix(email): address-list for "To", "From" etc.
Recipients etc. are accumulated by the EmailBuilder and put into the
email as one header with an address-list instead of multiple headers.
This is required by RFC 5322. Also a new test for this behaviour was
added.

fixes #85
2016-10-16 20:32:23 +02:00
10 changed files with 244 additions and 44 deletions

View File

@@ -1,9 +1,16 @@
language: rust language: rust
rust: rust:
- stable - stable
- beta - beta
- nightly - nightly
matrix:
allow_failures:
- rust: nightly
sudo: false sudo: false
cache: cache:
apt: true apt: true
pip: true pip: true
@@ -12,9 +19,11 @@ cache:
- target/debug/build - target/debug/build
- target/release/deps - target/release/deps
- target/release/build - target/release/build
install: install:
- pip install 'travis-cargo<0.2' --user - pip install 'travis-cargo<0.2' --user
- export PATH=$HOME/.local/bin:$PATH - export PATH=$HOME/.local/bin:$PATH
addons: addons:
apt: apt:
packages: packages:
@@ -22,16 +31,20 @@ addons:
- libcurl4-openssl-dev - libcurl4-openssl-dev
- libelf-dev - libelf-dev
- libdw-dev - libdw-dev
before_script: before_script:
- smtp-sink 2525 1000& - smtp-sink 2525 1000&
script: script:
- travis-cargo build - travis-cargo build
- travis-cargo test - travis-cargo test
- travis-cargo doc - travis-cargo doc
- travis-cargo --only stable doc-upload
after_success: after_success:
- ./.travis/doc.sh
- ./.travis/coverage.sh
- travis-cargo --only nightly bench - travis-cargo --only nightly bench
- travis-cargo --only stable coveralls --no-sudo
env: env:
global: global:
secure: "MaZ3TzuaAHuxmxQkfJdqRfkh7/ieScJRk0T/2yjysZhDMTYyRmp5wh/zkfW1ADuG0uc4Pqsxrsh1J9SVO7O0U5NJA8NKZi/pgiL+FHh0g4YtlHxy2xmFNB5am3Kyc+E7B4XylwTbA9S8ublVM0nvX7yX/a5fbwEUInVk2bA8fpc=" secure: "MaZ3TzuaAHuxmxQkfJdqRfkh7/ieScJRk0T/2yjysZhDMTYyRmp5wh/zkfW1ADuG0uc4Pqsxrsh1J9SVO7O0U5NJA8NKZi/pgiL+FHh0g4YtlHxy2xmFNB5am3Kyc+E7B4XylwTbA9S8ublVM0nvX7yX/a5fbwEUInVk2bA8fpc="

20
.travis/coverage.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -o errexit
if [ "$TRAVIS_RUST_VERSION" != "stable" ]; then
exit 0
fi
cargo test --no-run
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz
tar xzf master.tar.gz
mkdir kcov-master/build
cd kcov-master/build
cmake ..
make
make install DESTDIR=../tmp
cd ../..
ls target/debug
./kcov-master/tmp/usr/local/bin/kcov --coveralls-id=$TRAVIS_JOB_ID --exclude-pattern=/.cargo target/kcov target/debug/lettre-*

38
.travis/doc.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
set -o errexit
if [ "$TRAVIS_RUST_VERSION" != "stable" ] || [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
exit 0
fi
cargo clean
cargo doc --no-deps
git clone --branch gh-pages "https://$GH_TOKEN@github.com/${TRAVIS_REPO_SLUG}.git" deploy_docs
cd deploy_docs
git config user.email "contact@amousset.me"
git config user.name "Alexis Mousset"
if [ "$TRAVIS_BRANCH" == "master" ]; then
rm -rf master
mv ../target/doc ./master
echo "<meta http-equiv=refresh content=0;url=lettre/index.html>" > ./master/index.html
elif [ "$TRAVIS_TAG" != "" ]; then
rm -rf $TRAVIS_TAG
mv ../target/doc ./$TRAVIS_TAG
echo "<meta http-equiv=refresh content=0;url=lettre/index.html>" > ./$TRAVIS_TAG/index.html
latest=$(echo * | tr " " "\n" | sort -V -r | head -n1)
if [ "$TRAVIS_TAG" == "$latest" ]; then
echo "<meta http-equiv=refresh content=0;url=$latest/lettre/index.html>" > index.html
fi
else
exit 0
fi
git add -A .
git commit -m "Rebuild pages at ${TRAVIS_COMMIT}"
git push --quiet origin gh-pages

View File

@@ -1,3 +1,32 @@
### v0.6.3 (2022-1--21)
* **transport**: Allow using openssl 0.10
### v0.6.2 (2017-02-18)
#### Features
* **all**
* Update uuid crate to 0.4
* Update env-logger crate to 0.4
* Update openssl crate to 0.9
### v0.6.1 (2016-10-19)
#### Features
* **documentation**
* #91: Build seperate docs for each release
* #96: Add complete documentation information to README
#### Bugfixes
* **email**
* #85: Use address-list for "To", "From" etc.
* **tests**
* #93: Force building tests before coverage computing
### v0.6.0 (2016-05-05) ### v0.6.0 (2016-05-05)
#### Features #### Features

View File

@@ -10,7 +10,9 @@ All code must be formatted using `rustfmt`.
Each commit message consists of a header, a body and a footer. The header has a special format that includes a type, a scope and a subject: Each commit message consists of a header, a body and a footer. The header has a special format that includes a type, a scope and a subject:
```text
<type>(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer> <type>(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer>
```
Any line of the commit message cannot be longer 72 characters. Any line of the commit message cannot be longer 72 characters.

View File

@@ -1,29 +1,28 @@
[package] [package]
name = "lettre" name = "lettre"
version = "0.6.0" version = "0.6.3"
description = "Email client" description = "Email client"
readme = "README.md" readme = "README.md"
documentation = "http://lettre.github.io/lettre/" documentation = "https://lettre.github.io/lettre/"
repository = "https://github.com/lettre/lettre" repository = "https://github.com/lettre/lettre"
homepage = "http://lettre.github.io/"
license = "MIT" license = "MIT"
authors = ["Alexis Mousset <contact@amousset.me>"] authors = ["Alexis Mousset <contact@amousset.me>"]
keywords = ["email", "smtp", "mailer"] keywords = ["email", "smtp", "mailer"]
[dependencies] [dependencies]
bufstream = "0.1" bufstream = "^0.1"
email = "0.0" email = "^0.0"
log = "0.3" log = "^0.3"
mime = "0.2" mime = "^0.2"
openssl = "0.8" openssl = "> 0.9, < 0.11"
rustc-serialize = "0.3" rustc-serialize = "^0.3"
rust-crypto = "0.2" rust-crypto = "^0.2"
time = "0.1" time = "^0.1"
uuid = { version = "0.3", features = ["v4"] } uuid = { version = "^0.4", features = ["v4"] }
[dev-dependencies] [dev-dependencies]
env_logger = "0.3" env_logger = "^0.4"
[features] [features]
unstable = [] unstable = []

View File

@@ -1,13 +1,36 @@
# lettre # lettre
[![Build Status](https://travis-ci.org/lettre/lettre.svg?branch=master)](https://travis-ci.org/lettre/lettre) [![Build Status](https://travis-ci.org/lettre/lettre.svg?branch=master)](https://travis-ci.org/lettre/lettre)
[![Build status](https://ci.appveyor.com/api/projects/status/mpwglemugjtkps2d/branch/master?svg=true)](https://ci.appveyor.com/project/amousset/lettre/branch/master) [![Build status](https://ci.appveyor.com/api/projects/status/mpwglemugjtkps2d/branch/master?svg=true)](https://ci.appveyor.com/project/amousset/lettre/branch/master)
[![Coverage Status](https://coveralls.io/repos/lettre/lettre/badge.svg?branch=master&service=github)](https://coveralls.io/github/lettre/lettre?branch=master) [![Coverage Status](https://coveralls.io/repos/github/lettre/lettre/badge.svg?branch=master)](https://coveralls.io/github/lettre/lettre?branch=master)
[![Crate](https://meritbadge.herokuapp.com/lettre)](https://crates.io/crates/lettre) [![Crate](https://img.shields.io/crates/v/lettre.svg)](https://crates.io/crates/lettre)
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![Gitter](https://badges.gitter.im/lettre/lettre.svg)](https://gitter.im/lettre/lettre?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/lettre/lettre.svg)](https://gitter.im/lettre/lettre?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
This is an email library written in Rust. This is an email library written in Rust.
See the [documentation](http://lettre.github.io/lettre) for more information.
## Features
Lettre provides the following features:
* Multiple transport methods
* Unicode support (for email content and addresses)
* Secure delivery with SMTP using encryption and authentication
* Easy email builders
## Documentation
Released versions:
* [latest](https://lettre.github.io/lettre/)
* [v0.6.3](https://lettre.github.io/lettre/v0.6.3/lettre/)
* [v0.6.2](https://lettre.github.io/lettre/v0.6.2/lettre/)
* [v0.6.1](https://lettre.github.io/lettre/v0.6.1/lettre/)
* [v0.6.0](https://lettre.github.io/lettre/v0.6.0/lettre/)
* [v0.5.1](https://lettre.github.io/lettre/v0.5.1/lettre/)
Development version:
* [master](https://lettre.github.io/lettre/master/lettre/)
## Install ## Install
@@ -15,15 +38,15 @@ To use this library, add the following to your `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]
lettre = "0.5" lettre = "0.6"
``` ```
## Testing ## Testing
The tests require a mail server listening locally on port 25. The tests require an open mail server listening locally on port 25.
## License ## License
This program is distributed under the terms of the MIT license. This program is distributed under the terms of the MIT license.
See LICENSE for details. See [LICENSE](./LICENSE) for details.

View File

@@ -3,7 +3,7 @@ pub mod error;
use email::error::Error; use email::error::Error;
use email_format::{Header, Mailbox, MimeMessage, MimeMultipartType}; use email_format::{Header, Mailbox, Address, MimeMessage, MimeMultipartType};
use mime::Mime; use mime::Mime;
use std::fmt; use std::fmt;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
@@ -237,6 +237,16 @@ pub struct PartBuilder {
pub struct EmailBuilder { pub struct EmailBuilder {
/// Message /// Message
message: PartBuilder, message: PartBuilder,
/// The recipients' addresses for the mail header
to_header: Vec<Address>,
/// The sender addresses for the mail header
from_header: Vec<Address>,
/// The Cc addresses for the mail header
cc_header: Vec<Address>,
/// The Reply-To addresses for the mail header
reply_to_header: Vec<Address>,
/// The sender address for the mail header
sender_header: Option<Mailbox>,
/// The envelope recipients' addresses /// The envelope recipients' addresses
to: Vec<String>, to: Vec<String>,
/// The envelope sender address /// The envelope sender address
@@ -345,6 +355,11 @@ impl EmailBuilder {
pub fn new() -> EmailBuilder { pub fn new() -> EmailBuilder {
EmailBuilder { EmailBuilder {
message: PartBuilder::new(), message: PartBuilder::new(),
to_header: vec![],
from_header: vec![],
cc_header: vec![],
reply_to_header: vec![],
sender_header: None,
to: vec![], to: vec![],
from: None, from: None,
date_issued: false, date_issued: false,
@@ -382,8 +397,8 @@ impl EmailBuilder {
/// Adds a `From` header and stores the sender address /// Adds a `From` header and stores the sender address
pub fn add_from<A: ToMailbox>(&mut self, address: A) { pub fn add_from<A: ToMailbox>(&mut self, address: A) {
let mailbox = address.to_mailbox(); let mailbox = address.to_mailbox();
self.message.add_header(("From", mailbox.to_string().as_ref())); self.from = Some(mailbox.address.clone());
self.from = Some(mailbox.address); self.from_header.push(Address::Mailbox(mailbox));
} }
/// Adds a `To` header and stores the recipient address /// Adds a `To` header and stores the recipient address
@@ -395,8 +410,8 @@ impl EmailBuilder {
/// Adds a `To` header and stores the recipient address /// Adds a `To` header and stores the recipient address
pub fn add_to<A: ToMailbox>(&mut self, address: A) { pub fn add_to<A: ToMailbox>(&mut self, address: A) {
let mailbox = address.to_mailbox(); let mailbox = address.to_mailbox();
self.message.add_header(("To", mailbox.to_string().as_ref())); self.to.push(mailbox.address.clone());
self.to.push(mailbox.address); self.to_header.push(Address::Mailbox(mailbox));
} }
/// Adds a `Cc` header and stores the recipient address /// Adds a `Cc` header and stores the recipient address
@@ -408,8 +423,8 @@ impl EmailBuilder {
/// Adds a `Cc` header and stores the recipient address /// Adds a `Cc` header and stores the recipient address
pub fn add_cc<A: ToMailbox>(&mut self, address: A) { pub fn add_cc<A: ToMailbox>(&mut self, address: A) {
let mailbox = address.to_mailbox(); let mailbox = address.to_mailbox();
self.message.add_header(("Cc", mailbox.to_string().as_ref())); self.to.push(mailbox.address.clone());
self.to.push(mailbox.address); self.cc_header.push(Address::Mailbox(mailbox));
} }
/// Adds a `Reply-To` header /// Adds a `Reply-To` header
@@ -421,7 +436,7 @@ impl EmailBuilder {
/// Adds a `Reply-To` header /// Adds a `Reply-To` header
pub fn add_reply_to<A: ToMailbox>(&mut self, address: A) { pub fn add_reply_to<A: ToMailbox>(&mut self, address: A) {
let mailbox = address.to_mailbox(); let mailbox = address.to_mailbox();
self.message.add_header(("Reply-To", mailbox.to_string().as_ref())); self.reply_to_header.push(Address::Mailbox(mailbox));
} }
/// Adds a `Sender` header /// Adds a `Sender` header
@@ -433,8 +448,8 @@ impl EmailBuilder {
/// Adds a `Sender` header /// Adds a `Sender` header
pub fn set_sender<A: ToMailbox>(&mut self, address: A) { pub fn set_sender<A: ToMailbox>(&mut self, address: A) {
let mailbox = address.to_mailbox(); let mailbox = address.to_mailbox();
self.message.add_header(("Sender", mailbox.to_string().as_ref())); self.from = Some(mailbox.address.clone());
self.from = Some(mailbox.address); self.sender_header = Some(mailbox);
} }
/// Adds a `Subject` header /// Adds a `Subject` header
@@ -544,6 +559,38 @@ impl EmailBuilder {
if self.to.is_empty() { if self.to.is_empty() {
return Err(Error::MissingTo); return Err(Error::MissingTo);
} }
// If there are multiple addresses in "From", the "Sender" is required.
if self.from_header.len() >= 2 && self.sender_header.is_none() {
// So, we must find something to put as Sender.
for possible_sender in self.from_header.iter() {
// Only a mailbox can be used as sender, not Address::Group.
if let &Address::Mailbox(ref mbx) = possible_sender {
self.sender_header = Some(mbx.clone());
break;
}
}
// Address::Group is not yet supported, so the line below will never panic.
// If groups are supported one day, add another Error for this case
// and return it here, if sender_header is still None at this point.
assert!(self.sender_header.is_some());
}
// Add the sender header, if any.
if let Some(v) = self.sender_header {
self.message.add_header(("Sender", v.to_string().as_ref()));
}
// Add the collected addresses as mailbox-list all at once.
// The unwraps are fine because the conversions for Vec<Address> never errs.
self.message.add_header(Header::new_with_value("To".into(), self.to_header).unwrap());
self.message.add_header(Header::new_with_value("From".into(), self.from_header).unwrap());
if !self.cc_header.is_empty() {
self.message.add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap());
}
if !self.reply_to_header.is_empty() {
self.message.add_header(Header::new_with_value("Reply-To".into(),
self.reply_to_header)
.unwrap());
}
if !self.date_issued { if !self.date_issued {
self.message.add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref())); self.message.add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref()));
@@ -678,6 +725,27 @@ mod test {
assert_eq!(current_message.to_string(), email.message_id()); assert_eq!(current_message.to_string(), email.message_id());
} }
#[test]
fn test_multiple_from() {
let email_builder = EmailBuilder::new();
let date_now = now();
let email = email_builder.to("anna@example.com")
.from("dieter@example.com")
.from("joachim@example.com")
.date(&date_now)
.subject("Invitation")
.body("We invite you!")
.build()
.unwrap();
assert_eq!(format!("{}", email),
format!("Date: {}\r\nSubject: Invitation\r\nSender: \
<dieter@example.com>\r\nTo: <anna@example.com>\r\nFrom: \
<dieter@example.com>, <joachim@example.com>\r\nMIME-Version: \
1.0\r\nMessage-ID: <{}.lettre@localhost>\r\n\r\nWe invite you!\r\n",
date_now.rfc822z(),
email.message_id()));
}
#[test] #[test]
fn test_simple_email_builder() { fn test_simple_email_builder() {
let email_builder = EmailBuilder::new(); let email_builder = EmailBuilder::new();
@@ -696,10 +764,10 @@ mod test {
.unwrap(); .unwrap();
assert_eq!(format!("{}", email), assert_eq!(format!("{}", email),
format!("To: <user@localhost>\r\nFrom: <user@localhost>\r\nCc: \"Alias\" \ format!("Date: {}\r\nSubject: Hello\r\nX-test: value\r\nSender: \
<cc@localhost>\r\nReply-To: <reply@localhost>\r\nSender: \ <sender@localhost>\r\nTo: <user@localhost>\r\nFrom: \
<sender@localhost>\r\nDate: {}\r\nSubject: Hello\r\nX-test: \ <user@localhost>\r\nCc: \"Alias\" <cc@localhost>\r\nReply-To: \
value\r\nMIME-Version: 1.0\r\nMessage-ID: \ <reply@localhost>\r\nMIME-Version: 1.0\r\nMessage-ID: \
<{}.lettre@localhost>\r\n\r\nHello World!\r\n", <{}.lettre@localhost>\r\n\r\nHello World!\r\n",
date_now.rfc822z(), date_now.rfc822z(),
email.message_id())); email.message_id()));

View File

@@ -1,7 +1,6 @@
//! A trait to represent a stream //! A trait to represent a stream
use openssl::ssl::{Ssl, SslContext, SslStream};
use openssl::ssl::{SslContext, SslStream};
use std::fmt; use std::fmt;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::io; use std::io;
@@ -24,9 +23,13 @@ impl Connector for NetworkStream {
match ssl_context { match ssl_context {
Some(context) => { Some(context) => {
match SslStream::connect(context, tcp_stream) { match Ssl::new(context) {
Ok(stream) => Ok(NetworkStream::Ssl(stream)), Ok(ssl) => {
Err(err) => Err(io::Error::new(ErrorKind::Other, err)), ssl.connect(tcp_stream)
.map(|s| NetworkStream::Ssl(s))
.map_err(|e| io::Error::new(ErrorKind::Other, e))
}
Err(e) => Err(io::Error::new(ErrorKind::Other, e)),
} }
} }
None => Ok(NetworkStream::Plain(tcp_stream)), None => Ok(NetworkStream::Plain(tcp_stream)),
@@ -37,9 +40,14 @@ impl Connector for NetworkStream {
*self = match *self { *self = match *self {
NetworkStream::Plain(ref mut stream) => { NetworkStream::Plain(ref mut stream) => {
match SslStream::connect(ssl_context, stream.try_clone().unwrap()) { match Ssl::new(ssl_context) {
Ok(ssl_stream) => NetworkStream::Ssl(ssl_stream), Ok(ssl) => {
Err(err) => return Err(io::Error::new(ErrorKind::Other, err)), match ssl.connect(stream.try_clone().unwrap()) {
Ok(ssl_stream) => NetworkStream::Ssl(ssl_stream),
Err(err) => return Err(io::Error::new(ErrorKind::Other, err)),
}
}
Err(e) => return Err(io::Error::new(ErrorKind::Other, e)),
} }
} }
NetworkStream::Ssl(_) => return Ok(()), NetworkStream::Ssl(_) => return Ok(()),

View File

@@ -98,7 +98,7 @@ impl SmtpTransportBuilder {
Some(addr) => { Some(addr) => {
Ok(SmtpTransportBuilder { Ok(SmtpTransportBuilder {
server_addr: addr, server_addr: addr,
ssl_context: SslContext::new(SslMethod::Tlsv1).unwrap(), ssl_context: SslContext::builder(SslMethod::tls()).unwrap().build(),
security_level: SecurityLevel::Opportunistic, security_level: SecurityLevel::Opportunistic,
smtp_utf8: false, smtp_utf8: false,
credentials: None, credentials: None,