Compare commits

..

9 Commits

Author SHA1 Message Date
Alexis Mousset
47cad567b0 Prepare 0.10.0-rc.3 (#629) 2021-05-22 19:52:38 +02:00
Alexis Mousset
b0e2fc9bca fix(transport-smtp): Fix transparency codec (#627)
It fails to add transparency when a period is preceded by two
successive CRLF.

Co-authored-by: Paolo Barbolini <paolo@paolo565.org>
2021-05-22 19:41:29 +02:00
Alexis Mousset
1d8249165c Makes more things private and add missing docs (#621) 2021-05-19 18:51:03 +02:00
Alexis Mousset
98fc0cb2f3 Prepare 0.10.0-rc.2 (#624) 2021-05-18 18:12:10 +02:00
Alexis Mousset
0439bab874 fix(builder): Don't include Bcc headers in formatted messages (#623)
fixes #622
2021-05-18 18:03:20 +02:00
Alexis Mousset
504fc51b26 Prepare 0.10.0-rc.1 (#620) 2021-05-14 17:48:42 +02:00
Paolo Barbolini
d54343cf00 Remove Part from the public API (#619) 2021-05-14 17:27:03 +02:00
Alexis Mousset
904789ac3d feat(builder): Add helper methods for attachments and text (#618) 2021-05-14 16:59:08 +02:00
Paolo Barbolini
94cae6df0d Drop tokio 0.2 support (#617) 2021-05-12 19:09:30 +02:00
31 changed files with 249 additions and 723 deletions

View File

@@ -5,7 +5,7 @@
Several breaking changes were made between 0.9 and 0.10, but changes should be straightforward:
* MSRV is now 1.45.2
* MSRV is now 1.46
* The `lettre_email` crate has been merged into `lettre`. To migrate, replace `lettre_email` with `lettre::message`
and make sure to enable the `builder` feature (it's enabled by default).
* `SendableEmail` has been renamed to `Email` and `EmailBuilder::build()` produces it directly. To migrate,
@@ -14,7 +14,7 @@ Several breaking changes were made between 0.9 and 0.10, but changes should be s
#### Features
* Add `tokio` 0.2 and 1.0 support
* Add `tokio` 1 support
* Add `rustls` support
* Add `async-std` support. NOTE: native-tls isn't supported when using async-std for the smtp transport.
* Allow enabling multiple SMTP authentication mechanisms

View File

@@ -1,7 +1,7 @@
[package]
name = "lettre"
# remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge)
version = "0.10.0-beta.4"
version = "0.10.0-rc.3"
description = "Email client"
readme = "README.md"
homepage = "https://lettre.rs"
@@ -57,9 +57,6 @@ async-std = { version = "1.8", optional = true, features = ["unstable"] }
async-rustls = { version = "0.2", optional = true }
## tokio
tokio02_crate = { package = "tokio", version = "0.2.7", features = ["fs", "process", "tcp", "dns", "io-util"], optional = true }
tokio02_native_tls_crate = { package = "tokio-native-tls", version = "0.1", optional = true }
tokio02_rustls = { package = "tokio-rustls", version = "0.15", optional = true }
tokio1_crate = { package = "tokio", version = "1", features = ["fs", "process", "net", "io-util"], optional = true }
tokio1_native_tls_crate = { package = "tokio-native-tls", version = "0.3", optional = true }
tokio1_rustls = { package = "tokio-rustls", version = "0.22", optional = true }
@@ -69,7 +66,6 @@ criterion = "0.3"
tracing-subscriber = "0.2.10"
glob = "0.3"
walkdir = "2"
tokio02_crate = { package = "tokio", version = "0.2.7", features = ["macros", "rt-threaded"] }
tokio1_crate = { package = "tokio", version = "1", features = ["macros", "rt-multi-thread"] }
async-std = { version = "1.8", features = ["attributes"] }
serde_json = "1"
@@ -95,9 +91,6 @@ rustls-tls = ["webpki", "webpki-roots", "rustls"]
async-std1 = ["async-std", "async-trait", "futures-io", "futures-util"]
#async-std1-native-tls = ["async-std1", "native-tls", "async-native-tls"]
async-std1-rustls-tls = ["async-std1", "rustls-tls", "async-rustls"]
tokio02 = ["tokio02_crate", "async-trait", "futures-io", "futures-util"]
tokio02-native-tls = ["tokio02", "native-tls", "tokio02_native_tls_crate"]
tokio02-rustls-tls = ["tokio02", "rustls-tls", "tokio02_rustls"]
tokio1 = ["tokio1_crate", "async-trait", "futures-io", "futures-util"]
tokio1-native-tls = ["tokio1", "native-tls", "tokio1_native_tls_crate"]
tokio1-rustls-tls = ["tokio1", "rustls-tls", "tokio1_rustls"]
@@ -130,14 +123,6 @@ required-features = ["smtp-transport", "native-tls", "builder"]
name = "smtp_selfsigned"
required-features = ["smtp-transport", "native-tls", "builder"]
[[example]]
name = "tokio02_smtp_tls"
required-features = ["smtp-transport", "tokio02", "tokio02-native-tls", "builder"]
[[example]]
name = "tokio02_smtp_starttls"
required-features = ["smtp-transport", "tokio02", "tokio02-native-tls", "builder"]
[[example]]
name = "tokio1_smtp_tls"
required-features = ["smtp-transport", "tokio1", "tokio1-native-tls", "builder"]

View File

@@ -28,8 +28,8 @@
</div>
<div align="center">
<a href="https://deps.rs/crate/lettre/0.10.0-beta.4">
<img src="https://deps.rs/crate/lettre/0.10.0-beta.4/status.svg"
<a href="https://deps.rs/crate/lettre/0.10.0-rc.3">
<img src="https://deps.rs/crate/lettre/0.10.0-rc.3/status.svg"
alt="dependency status" />
</a>
</div>
@@ -37,10 +37,15 @@
---
**NOTE**: this readme refers to the 0.10 version of lettre, which is
still being worked on. The master branch and the alpha releases will see
API breaking changes and some features may be missing or incomplete until
the stable 0.10.0 release is out.
Use the [`v0.9.x`](https://github.com/lettre/lettre/tree/v0.9.x) branch for stable releases.
in release candidate state. Use the [`v0.9.x`](https://github.com/lettre/lettre/tree/v0.9.x)
branch for the previous stable release.
0.10 is already widely used and is already thought to be more reliable than 0.9, so it should generally be used
for new projects.
We'd love to hear your feedback about 0.10 design and APIs before final release!
Start a [discussion](https://github.com/lettre/lettre/discussions) in the repository, whether for
feedback or if you need help or advice using or upgrading lettre 0.10.
---
@@ -66,7 +71,7 @@ To use this library, add the following to your `Cargo.toml`:
```toml
[dependencies]
lettre = "0.10.0-beta.4"
lettre = "0.10.0-rc.3"
```
```rust,no_run

View File

@@ -14,7 +14,7 @@ This folder contains examples showing how to use lettre in your own projects.
- [smtp_starttls.rs] - Send an email over SMTP with STARTTLS and authenticating with username and password.
- [smtp_selfsigned.rs] - Send an email over SMTP encrypted with TLS using a self-signed certificate and authenticating with username and password.
- The [smtp_tls.rs] and [smtp_starttls.rs] examples also feature `async`hronous implementations powered by [Tokio](https://tokio.rs/).
These files are prefixed with `tokio02_`, `tokio1_` or `asyncstd1_`.
These files are prefixed with `tokio1_` or `asyncstd1_`.
[basic_html.rs]: ./basic_html.rs
[maud_html.rs]: ./maud_html.rs

View File

@@ -1,37 +0,0 @@
// This line is only to make it compile from lettre's examples folder,
// since it uses Rust 2018 crate renaming to import tokio.
// Won't be needed in user's code.
use tokio02_crate as tokio;
use lettre::{
transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncTransport, Message,
Tokio02Executor,
};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new async year")
.body(String::from("Be happy with async!"))
.unwrap();
let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
// Open a remote connection to gmail using STARTTLS
let mailer: AsyncSmtpTransport<Tokio02Executor> =
AsyncSmtpTransport::<Tokio02Executor>::starttls_relay("smtp.gmail.com")
.unwrap()
.credentials(creds)
.build();
// Send the email
match mailer.send(email).await {
Ok(_) => println!("Email sent successfully!"),
Err(e) => panic!("Could not send email: {:?}", e),
}
}

View File

@@ -1,37 +0,0 @@
// This line is only to make it compile from lettre's examples folder,
// since it uses Rust 2018 crate renaming to import tokio.
// Won't be needed in user's code.
use tokio02_crate as tokio;
use lettre::{
transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncTransport, Message,
Tokio02Executor,
};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new async year")
.body(String::from("Be happy with async!"))
.unwrap();
let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
// Open a remote connection to gmail
let mailer: AsyncSmtpTransport<Tokio02Executor> =
AsyncSmtpTransport::<Tokio02Executor>::relay("smtp.gmail.com")
.unwrap()
.credentials(creds)
.build();
// Send the email
match mailer.send(email).await {
Ok(_) => println!("Email sent successfully!"),
Err(e) => panic!("Could not send email: {:?}", e),
}
}

View File

@@ -8,22 +8,22 @@ use std::path::Path;
#[cfg(all(
feature = "smtp-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
any(feature = "tokio1", feature = "async-std1")
))]
use crate::transport::smtp::client::AsyncSmtpConnection;
#[cfg(all(
feature = "smtp-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
any(feature = "tokio1", feature = "async-std1")
))]
use crate::transport::smtp::client::Tls;
#[cfg(all(
feature = "smtp-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
any(feature = "tokio1", feature = "async-std1")
))]
use crate::transport::smtp::extension::ClientId;
#[cfg(all(
feature = "smtp-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
any(feature = "tokio1", feature = "async-std1")
))]
use crate::transport::smtp::Error;
@@ -35,10 +35,7 @@ use crate::transport::smtp::Error;
/// [`AsyncSmtpTransport`]: crate::AsyncSmtpTransport
/// [`AsyncSendmailTransport`]: crate::AsyncSendmailTransport
/// [`AsyncFileTransport`]: crate::AsyncFileTransport
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))
)]
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
#[async_trait]
pub trait Executor: Debug + Send + Sync + private::Sealed {
#[doc(hidden)]
@@ -59,72 +56,6 @@ pub trait Executor: Debug + Send + Sync + private::Sealed {
async fn fs_write(path: &Path, contents: &[u8]) -> IoResult<()>;
}
/// Async [`Executor`] using `tokio` `0.2.x`
///
/// Used by [`AsyncSmtpTransport`], [`AsyncSendmailTransport`] and [`AsyncFileTransport`]
/// in order to be able to work with different async runtimes.
///
/// [`AsyncSmtpTransport`]: crate::AsyncSmtpTransport
/// [`AsyncSendmailTransport`]: crate::AsyncSendmailTransport
/// [`AsyncFileTransport`]: crate::AsyncFileTransport
#[allow(missing_copy_implementations)]
#[non_exhaustive]
#[cfg(feature = "tokio02")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio02")))]
#[derive(Debug)]
pub struct Tokio02Executor;
#[async_trait]
#[cfg(feature = "tokio02")]
impl Executor for Tokio02Executor {
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
async fn connect(
hostname: &str,
port: u16,
hello_name: &ClientId,
tls: &Tls,
) -> Result<AsyncSmtpConnection, Error> {
#[allow(clippy::match_single_binding)]
let tls_parameters = match tls {
#[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))]
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
_ => None,
};
#[allow(unused_mut)]
let mut conn =
AsyncSmtpConnection::connect_tokio02(hostname, port, hello_name, tls_parameters)
.await?;
#[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))]
match tls {
Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
}
Tls::Required(ref tls_parameters) => {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
_ => (),
}
Ok(conn)
}
#[doc(hidden)]
#[cfg(feature = "file-transport-envelope")]
async fn fs_read(path: &Path) -> IoResult<Vec<u8>> {
tokio02_crate::fs::read(path).await
}
#[doc(hidden)]
#[cfg(feature = "file-transport")]
async fn fs_write(path: &Path, contents: &[u8]) -> IoResult<()> {
tokio02_crate::fs::write(path, contents).await
}
}
/// Async [`Executor`] using `tokio` `1.x`
///
/// Used by [`AsyncSmtpTransport`], [`AsyncSendmailTransport`] and [`AsyncFileTransport`]
@@ -261,9 +192,6 @@ mod private {
pub trait Sealed {}
#[cfg(feature = "tokio02")]
impl Sealed for Tokio02Executor {}
#[cfg(feature = "tokio1")]
impl Sealed for Tokio1Executor {}

View File

@@ -37,7 +37,6 @@
//! Uses schannel on Windows, Security-Framework on macOS, and OpenSSL on Linux.
//!
//! * **native-tls** 📫: TLS support for the synchronous version of the API
//! * **tokio02-native-tls**: TLS support for the `tokio02` async version of the API
//! * **tokio1-native-tls**: TLS support for the `tokio1` async version of the API
//!
//! NOTE: native-tls isn't supported with `async-std`
@@ -49,7 +48,6 @@
//! Rustls uses [ring] as the cryptography implementation. As a result, [not all Rust's targets are supported][ring-support].
//!
//! * **rustls-tls**: TLS support for the synchronous version of the API
//! * **tokio02-rustls-tls**: TLS support for the `tokio02` async version of the API
//! * **tokio1-rustls-tls**: TLS support for the `tokio1` async version of the API
//! * **async-std1-rustls-tls**: TLS support for the `async-std1` async version of the API
//!
@@ -71,11 +69,10 @@
//! _Use [tokio] or [async-std] as an async execution runtime for sending emails_
//!
//! The correct runtime version must be chosen in order for lettre to work correctly.
//! For example, when sending emails from a Tokio 1.3.0 context, the Tokio 1.x executor
//! For example, when sending emails from a Tokio 1.x context, the Tokio 1.x executor
//! ([`Tokio1Executor`]) must be used. Using a different version (for example Tokio 0.2.x),
//! or async-std, would result in a runtime panic.
//!
//! * **tokio02**: Allow to asynchronously send emails using [Tokio 0.2.x]
//! * **tokio1**: Allow to asynchronously send emails using [Tokio 1.x]
//! * **async-std1**: Allow to asynchronously send emails using [async-std 1.x]
//!
@@ -95,11 +92,10 @@
//! [async-std]: https://docs.rs/async-std/1
//! [ring]: https://github.com/briansmith/ring#ring
//! [ring-support]: https://github.com/briansmith/ring#online-automated-testing
//! [Tokio 0.2.x]: https://docs.rs/tokio/0.2
//! [Tokio 1.x]: https://docs.rs/tokio/1
//! [async-std 1.x]: https://docs.rs/async-std/1
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.10.0-beta.4")]
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.10.0-rc.3")]
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
#![forbid(unsafe_code)]
@@ -115,7 +111,7 @@
pub mod address;
pub mod error;
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
mod executor;
#[cfg(feature = "builder")]
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
@@ -124,13 +120,11 @@ pub mod transport;
#[cfg(feature = "async-std1")]
pub use self::executor::AsyncStd1Executor;
#[cfg(all(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))]
#[cfg(all(any(feature = "tokio1", feature = "async-std1")))]
pub use self::executor::Executor;
#[cfg(feature = "tokio02")]
pub use self::executor::Tokio02Executor;
#[cfg(feature = "tokio1")]
pub use self::executor::Tokio1Executor;
#[cfg(all(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))]
#[cfg(all(any(feature = "tokio1", feature = "async-std1")))]
#[doc(inline)]
pub use self::transport::AsyncTransport;
pub use crate::address::Address;
@@ -139,7 +133,7 @@ pub use crate::address::Address;
pub use crate::message::Message;
#[cfg(all(
feature = "file-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
any(feature = "tokio1", feature = "async-std1")
))]
#[doc(inline)]
pub use crate::transport::file::AsyncFileTransport;
@@ -148,7 +142,7 @@ pub use crate::transport::file::AsyncFileTransport;
pub use crate::transport::file::FileTransport;
#[cfg(all(
feature = "sendmail-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
any(feature = "tokio1", feature = "async-std1")
))]
#[doc(inline)]
pub use crate::transport::sendmail::AsyncSendmailTransport;
@@ -157,7 +151,7 @@ pub use crate::transport::sendmail::AsyncSendmailTransport;
pub use crate::transport::sendmail::SendmailTransport;
#[cfg(all(
feature = "smtp-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
any(feature = "tokio1", feature = "async-std1")
))]
pub use crate::transport::smtp::AsyncSmtpTransport;
#[doc(inline)]

91
src/message/attachment.rs Normal file
View File

@@ -0,0 +1,91 @@
use crate::message::{
header::{self, ContentType},
IntoBody, SinglePart,
};
/// `SinglePart` builder for attachments
///
/// Allows building attachment parts easily.
#[derive(Clone)]
pub struct Attachment {
disposition: Disposition,
}
#[derive(Clone)]
enum Disposition {
/// file name
Attached(String),
/// content id
Inline(String),
}
impl Attachment {
/// Creates a new attachment
pub fn new(filename: String) -> Self {
Attachment {
disposition: Disposition::Attached(filename),
}
}
/// Creates a new inline attachment
pub fn new_inline(content_id: String) -> Self {
Attachment {
disposition: Disposition::Inline(content_id),
}
}
/// Build the attachment part
pub fn body<T: IntoBody>(self, content: T, content_type: ContentType) -> SinglePart {
let mut builder = SinglePart::builder();
builder = match self.disposition {
Disposition::Attached(filename) => {
builder.header(header::ContentDisposition::attachment(&filename))
}
Disposition::Inline(content_id) => builder
.header(header::ContentId::from(format!("<{}>", content_id)))
.header(header::ContentDisposition::inline()),
};
builder = builder.header(content_type);
builder.body(content)
}
}
#[cfg(test)]
mod tests {
use crate::message::header::ContentType;
#[test]
fn attachment() {
let part = super::Attachment::new(String::from("test.txt")).body(
String::from("Hello world!"),
ContentType::parse("text/plain").unwrap(),
);
assert_eq!(
&String::from_utf8_lossy(&part.formatted()),
concat!(
"Content-Disposition: attachment; filename=\"test.txt\"\r\n",
"Content-Type: text/plain\r\n",
"Content-Transfer-Encoding: 7bit\r\n\r\n",
"Hello world!\r\n",
)
);
}
#[test]
fn attachment_inline() {
let part = super::Attachment::new_inline(String::from("id")).body(
String::from("Hello world!"),
ContentType::parse("text/plain").unwrap(),
);
assert_eq!(
&String::from_utf8_lossy(&part.formatted()),
concat!(
"Content-ID: <id>\r\n",
"Content-Disposition: inline\r\n",
"Content-Type: text/plain\r\n",
"Content-Transfer-Encoding: 7bit\r\n\r\n",
"Hello world!\r\n"
)
);
}
}

View File

@@ -26,6 +26,9 @@ mod mailbox;
mod special;
mod textual;
/// Represents an email header
///
/// Email header as defined in [RFC5322](https://datatracker.ietf.org/doc/html/rfc5322) and extensions.
pub trait Header: Clone {
fn name() -> HeaderName;

View File

@@ -10,6 +10,9 @@ pub struct MimeVersion {
minor: u8,
}
/// MIME version 1.0
///
/// Should be used in all MIME messages.
pub const MIME_VERSION_1_0: MimeVersion = MimeVersion::new(1, 0);
impl MimeVersion {

View File

@@ -1,7 +1,7 @@
use std::io::Write;
use crate::message::{
header::{ContentTransferEncoding, ContentType, Header, Headers},
header::{self, ContentTransferEncoding, ContentType, Header, Headers},
EmailFormat, IntoBody,
};
use mime::Mime;
@@ -9,7 +9,7 @@ use std::iter::repeat_with;
/// MIME part variants
#[derive(Debug, Clone)]
pub enum Part {
pub(super) enum Part {
/// Single part with content
Single(SinglePart),
@@ -26,18 +26,6 @@ impl EmailFormat for Part {
}
}
impl Part {
/// Get message content formatted for SMTP
pub fn formatted(&self) -> Vec<u8> {
let mut out = Vec::new();
self.format(&mut out);
out
}
}
/// Parts of multipart body
pub type Parts = Vec<Part>;
/// Creates builder for single part
#[derive(Debug, Clone)]
pub struct SinglePartBuilder {
@@ -112,6 +100,20 @@ impl SinglePart {
SinglePartBuilder::new()
}
/// Directly create a `SinglePart` from an plain UTF-8 content
pub fn plain<T: IntoBody>(body: T) -> Self {
Self::builder()
.header(header::ContentType::TEXT_PLAIN)
.body(body)
}
/// Directly create a `SinglePart` from an UTF-8 HTML content
pub fn html<T: IntoBody>(body: T) -> Self {
Self::builder()
.header(header::ContentType::TEXT_HTML)
.body(body)
}
/// Get the headers from singlepart
#[inline]
pub fn headers(&self) -> &Headers {
@@ -260,11 +262,6 @@ impl MultiPartBuilder {
}
}
/// Creates multipart using part
pub fn part(self, part: Part) -> MultiPart {
self.build().part(part)
}
/// Creates multipart using singlepart
pub fn singlepart(self, part: SinglePart) -> MultiPart {
self.build().singlepart(part)
@@ -286,7 +283,7 @@ impl Default for MultiPartBuilder {
#[derive(Debug, Clone)]
pub struct MultiPart {
headers: Headers,
parts: Parts,
parts: Vec<Part>,
}
impl MultiPart {
@@ -330,10 +327,11 @@ impl MultiPart {
MultiPart::builder().kind(MultiPartKind::Signed { protocol, micalg })
}
/// Add part to multipart
pub fn part(mut self, part: Part) -> Self {
self.parts.push(part);
self
/// Alias for HTML and plain text versions of an email
pub fn alternative_plain_html<T: IntoBody, V: IntoBody>(plain: T, html: V) -> Self {
Self::alternative()
.singlepart(SinglePart::plain(plain))
.singlepart(SinglePart::html(html))
}
/// Add single part to multipart
@@ -369,16 +367,6 @@ impl MultiPart {
&mut self.headers
}
/// Get the parts from the multipart
pub fn parts(&self) -> &Parts {
&self.parts
}
/// Get a mutable reference to the parts
pub fn parts_mut(&mut self) -> &mut Parts {
&mut self.parts
}
/// Get message content formatted for SMTP
pub fn formatted(&self) -> Vec<u8> {
let mut out = Vec::new();
@@ -472,12 +460,12 @@ mod test {
fn multi_part_mixed() {
let part = MultiPart::mixed()
.boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
.part(Part::Single(
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_PLAIN)
.header(header::ContentTransferEncoding::Binary)
.body(String::from("Текст письма в уникоде")),
))
)
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_PLAIN)
@@ -511,11 +499,11 @@ mod test {
fn multi_part_encrypted() {
let part = MultiPart::encrypted("application/pgp-encrypted".to_owned())
.boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
.part(Part::Single(
.singlepart(
SinglePart::builder()
.header(header::ContentType::parse("application/pgp-encrypted").unwrap())
.body(String::from("Version: 1")),
))
)
.singlepart(
SinglePart::builder()
.header(
@@ -566,11 +554,11 @@ mod test {
"pgp-sha256".to_owned(),
)
.boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
.part(Part::Single(
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_PLAIN)
.body(String::from("Test email for signature")),
))
)
.singlepart(
SinglePart::builder()
.header(
@@ -624,10 +612,10 @@ mod test {
fn multi_part_alternative() {
let part = MultiPart::alternative()
.boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
.part(Part::Single(SinglePart::builder()
.singlepart(SinglePart::builder()
.header(header::ContentType::TEXT_PLAIN)
.header(header::ContentTransferEncoding::Binary)
.body(String::from("Текст письма в уникоде"))))
.body(String::from("Текст письма в уникоде")))
.singlepart(SinglePart::builder()
.header(header::ContentType::TEXT_HTML)
.header(header::ContentTransferEncoding::Binary)

View File

@@ -63,7 +63,7 @@
//!
//! ```rust
//! # use std::error::Error;
//! use lettre::message::{header, Message, MultiPart, Part, SinglePart};
//! use lettre::message::{header, Message, MultiPart, SinglePart};
//!
//! # fn main() -> Result<(), Box<dyn Error>> {
//! let m = Message::builder()
@@ -71,21 +71,10 @@
//! .reply_to("Yuin <yuin@domain.tld>".parse()?)
//! .to("Hei <hei@domain.tld>".parse()?)
//! .subject("Happy new year")
//! .multipart(
//! MultiPart::alternative()
//! .singlepart(
//! SinglePart::builder()
//! .header(header::ContentType::TEXT_PLAIN)
//! .body(String::from("Hello, world! :)")),
//! )
//! .singlepart(
//! SinglePart::builder()
//! .header(header::ContentType::TEXT_HTML)
//! .body(String::from(
//! "<p><b>Hello</b>, <i>world</i>! <img src=\"cid:123\"></p>",
//! )),
//! ),
//! )?;
//! .multipart(MultiPart::alternative_plain_html(
//! String::from("Hello, world! :)"),
//! String::from("<p><b>Hello</b>, <i>world</i>! <img src=\"cid:123\"></p>"),
//! ))?;
//! # Ok(())
//! # }
//! ```
@@ -124,7 +113,7 @@
//!
//! ```rust
//! # use std::error::Error;
//! use lettre::message::{header, Body, Message, MultiPart, Part, SinglePart};
//! use lettre::message::{header, Attachment, Body, Message, MultiPart, SinglePart};
//! use std::fs;
//!
//! # fn main() -> Result<(), Box<dyn Error>> {
@@ -144,35 +133,22 @@
//! MultiPart::mixed()
//! .multipart(
//! MultiPart::alternative()
//! .singlepart(
//! SinglePart::builder()
//! .header(header::ContentType::TEXT_PLAIN)
//! .body(String::from("Hello, world! :)")),
//! )
//! .singlepart(SinglePart::plain(String::from("Hello, world! :)")))
//! .multipart(
//! MultiPart::related()
//! .singlepart(SinglePart::html(String::from(
//! "<p><b>Hello</b>, <i>world</i>! <img src=cid:123></p>",
//! )))
//! .singlepart(
//! SinglePart::builder()
//! .header(header::ContentType::TEXT_HTML)
//! .body(String::from(
//! "<p><b>Hello</b>, <i>world</i>! <img src=cid:123></p>",
//! )),
//! )
//! .singlepart(
//! SinglePart::builder()
//! .header(header::ContentType::parse("image/png")?)
//! .header(header::ContentDisposition::inline())
//! .header(header::ContentId::from(String::from("<123>")))
//! .body(image_body),
//! Attachment::new_inline(String::from("123"))
//! .body(image_body, "image/png".parse().unwrap()),
//! ),
//! ),
//! )
//! .singlepart(
//! SinglePart::builder()
//! .header(header::ContentType::TEXT_PLAIN)
//! .header(header::ContentDisposition::attachment("example.rs"))
//! .body(String::from("fn main() { println!(\"Hello, World!\") }")),
//! ),
//! .singlepart(Attachment::new(String::from("example.rs")).body(
//! String::from("fn main() { println!(\"Hello, World!\") }"),
//! "text/plain".parse().unwrap(),
//! )),
//! )?;
//! # Ok(())
//! # }
@@ -228,10 +204,12 @@
use std::{convert::TryFrom, io::Write, iter, time::SystemTime};
pub use attachment::Attachment;
pub use body::{Body, IntoBody, MaybeString};
pub use mailbox::*;
pub use mimebody::*;
mod attachment;
mod body;
pub mod header;
mod mailbox;
@@ -293,7 +271,8 @@ impl MessageBuilder {
/// Set `Date` header using current date/time
///
/// Shortcut for `self.date(SystemTime::now())`.
/// Shortcut for `self.date(SystemTime::now())`, it is automatically inserted
/// if no date has been provided.
pub fn date_now(self) -> Self {
self.date(SystemTime::now())
}
@@ -306,7 +285,7 @@ impl MessageBuilder {
self.header(header::Subject::from(s))
}
/// Set `Mime-Version` header to 1.0
/// Set `MIME-Version` header to 1.0
///
/// Shortcut for `self.header(header::MIME_VERSION_1_0)`.
///
@@ -422,7 +401,7 @@ impl MessageBuilder {
// https://tools.ietf.org/html/rfc5322#section-3.6
// Insert Date if missing
let res = if self.headers.get::<header::Date>().is_none() {
let mut res = if self.headers.get::<header::Date>().is_none() {
self.date_now()
} else {
self
@@ -445,6 +424,10 @@ impl MessageBuilder {
Some(e) => e,
None => Envelope::try_from(&res.headers)?,
};
// Remove `Bcc` headers now the envelope is set
res.headers.remove::<header::Bcc>();
Ok(Message {
headers: res.headers,
body,
@@ -580,6 +563,7 @@ mod test {
let email = Message::builder()
.date(date)
.bcc("hidden@example.com".parse().unwrap())
.header(header::From(
vec![Mailbox::new(
Some("Каи".into()),

View File

@@ -134,11 +134,11 @@
pub use self::error::Error;
use crate::{address::Envelope, Transport};
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use crate::{AsyncTransport, Executor};
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use async_trait::async_trait;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use std::marker::PhantomData;
use std::{
path::{Path, PathBuf},
@@ -163,11 +163,8 @@ pub struct FileTransport {
/// Asynchronously writes the content and the envelope information to a file
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))
)]
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
pub struct AsyncFileTransport<E: Executor> {
inner: FileTransport,
marker_: PhantomData<E>,
@@ -220,7 +217,7 @@ impl FileTransport {
}
}
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
impl<E> AsyncFileTransport<E>
where
E: Executor,
@@ -290,7 +287,7 @@ impl Transport for FileTransport {
}
}
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
#[async_trait]
impl<E> AsyncTransport for AsyncFileTransport<E>
where

View File

@@ -99,7 +99,7 @@
//! [`AsyncFileTransport`]: crate::AsyncFileTransport
//! [`StubTransport`]: crate::transport::stub::StubTransport
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use async_trait::async_trait;
use crate::Envelope;
@@ -136,11 +136,8 @@ pub trait Transport {
}
/// Async Transport method for emails
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))
)]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
#[async_trait]
pub trait AsyncTransport {
/// Response produced by the Transport

View File

@@ -26,29 +26,6 @@
//! # fn main() {}
//! ```
//!
//! ## Async tokio 0.2 example
//!
//! ```rust,no_run
//! # use std::error::Error;
//! #
//! # #[cfg(all(feature = "tokio02", feature = "sendmail-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> {
//! use lettre::{Message, AsyncTransport, Tokio02Executor, AsyncSendmailTransport, SendmailTransport};
//!
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?)
//! .reply_to("Yuin <yuin@domain.tld>".parse()?)
//! .to("Hei <hei@domain.tld>".parse()?)
//! .subject("Happy new year")
//! .body(String::from("Be happy!"))?;
//!
//! let sender = AsyncSendmailTransport::<Tokio02Executor>::new();
//! let result = sender.send(email).await;
//! assert!(result.is_ok());
//! # Ok(())
//! # }
//! ```
//!
//! ## Async tokio 1.x example
//!
//! ```rust,no_run
@@ -98,16 +75,14 @@
pub use self::error::Error;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Executor;
#[cfg(feature = "tokio02")]
use crate::Tokio02Executor;
#[cfg(feature = "tokio1")]
use crate::Tokio1Executor;
use crate::{address::Envelope, Transport};
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use crate::{AsyncTransport, Executor};
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use async_trait::async_trait;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use std::marker::PhantomData;
use std::{
ffi::OsString,
@@ -130,11 +105,8 @@ pub struct SendmailTransport {
/// Asynchronously sends emails using the `sendmail` command
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))
)]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
pub struct AsyncSendmailTransport<E: Executor> {
inner: SendmailTransport,
marker_: PhantomData<E>,
@@ -170,7 +142,7 @@ impl SendmailTransport {
}
}
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
impl<E> AsyncSendmailTransport<E>
where
E: Executor,
@@ -191,24 +163,6 @@ where
}
}
#[cfg(feature = "tokio02")]
fn tokio02_command(&self, envelope: &Envelope) -> tokio02_crate::process::Command {
use tokio02_crate::process::Command;
let mut c = Command::new(&self.inner.command);
c.kill_on_drop(true);
c.arg("-i");
if let Some(from) = envelope.from() {
c.arg("-f").arg(from);
}
c.arg("--")
.args(envelope.to())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
c
}
#[cfg(feature = "tokio1")]
fn tokio1_command(&self, envelope: &Envelope) -> tokio1_crate::process::Command {
use tokio1_crate::process::Command;
@@ -253,7 +207,7 @@ impl Default for SendmailTransport {
}
}
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
impl<E> Default for AsyncSendmailTransport<E>
where
E: Executor,
@@ -320,38 +274,6 @@ impl AsyncTransport for AsyncSendmailTransport<AsyncStd1Executor> {
}
}
#[cfg(feature = "tokio02")]
#[async_trait]
impl AsyncTransport for AsyncSendmailTransport<Tokio02Executor> {
type Ok = ();
type Error = Error;
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use tokio02_crate::io::AsyncWriteExt;
let mut command = self.tokio02_command(envelope);
// Spawn the sendmail command
let mut process = command.spawn().map_err(error::client)?;
process
.stdin
.as_mut()
.unwrap()
.write_all(&email)
.await
.map_err(error::client)?;
let output = process.wait_with_output().await.map_err(error::client)?;
if output.status.success() {
Ok(())
} else {
let stderr = String::from_utf8(output.stderr).map_err(error::response)?;
Err(error::client(stderr))
}
}
}
#[cfg(feature = "tokio1")]
#[async_trait]
impl AsyncTransport for AsyncSendmailTransport<Tokio1Executor> {

View File

@@ -10,42 +10,19 @@ use super::{
};
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Executor;
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
use crate::AsyncTransport;
#[cfg(feature = "tokio02")]
use crate::Tokio02Executor;
#[cfg(feature = "tokio1")]
use crate::Tokio1Executor;
use crate::{Envelope, Executor};
/// Asynchronously sends emails using the SMTP protocol
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))
)]
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
pub struct AsyncSmtpTransport<E> {
// TODO: pool
inner: AsyncSmtpClient<E>,
}
#[cfg(feature = "tokio02")]
#[async_trait]
impl AsyncTransport for AsyncSmtpTransport<Tokio02Executor> {
type Ok = Response;
type Error = Error;
/// Sends an email
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
let mut conn = self.inner.connection().await?;
let result = conn.send(envelope, email).await?;
conn.quit().await?;
Ok(result)
}
}
#[cfg(feature = "tokio1")]
#[async_trait]
impl AsyncTransport for AsyncSmtpTransport<Tokio1Executor> {
@@ -93,8 +70,6 @@ where
/// Creates an encrypted transport over submissions port, using the provided domain
/// to validate TLS certificates.
#[cfg(any(
feature = "tokio02-native-tls",
feature = "tokio02-rustls-tls",
feature = "tokio1-native-tls",
feature = "tokio1-rustls-tls",
feature = "async-std1-native-tls",
@@ -103,8 +78,6 @@ where
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "tokio02-native-tls",
feature = "tokio02-rustls-tls",
feature = "tokio1-native-tls",
feature = "tokio1-rustls-tls",
feature = "async-std1-rustls-tls"
@@ -132,8 +105,6 @@ where
/// An error is returned if the connection can't be upgraded. No credentials
/// or emails will be sent to the server, protecting from downgrade attacks.
#[cfg(any(
feature = "tokio02-native-tls",
feature = "tokio02-rustls-tls",
feature = "tokio1-native-tls",
feature = "tokio1-rustls-tls",
feature = "async-std1-native-tls",
@@ -142,8 +113,6 @@ where
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "tokio02-native-tls",
feature = "tokio02-rustls-tls",
feature = "tokio1-native-tls",
feature = "tokio1-rustls-tls",
feature = "async-std1-rustls-tls"
@@ -208,10 +177,7 @@ where
/// Contains client configuration.
/// Instances of this struct can be created using functions of [`AsyncSmtpTransport`].
#[derive(Debug, Clone)]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))
)]
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
pub struct AsyncSmtpTransportBuilder {
info: SmtpInfo,
}
@@ -244,8 +210,6 @@ impl AsyncSmtpTransportBuilder {
/// Set the TLS settings to use
#[cfg(any(
feature = "tokio02-native-tls",
feature = "tokio02-rustls-tls",
feature = "tokio1-native-tls",
feature = "tokio1-rustls-tls",
feature = "async-std1-native-tls",
@@ -254,8 +218,6 @@ impl AsyncSmtpTransportBuilder {
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "tokio02-native-tls",
feature = "tokio02-rustls-tls",
feature = "tokio1-native-tls",
feature = "tokio1-rustls-tls",
feature = "async-std1-rustls-tls"

View File

@@ -4,6 +4,7 @@ use crate::transport::smtp::error::{self, Error};
use std::fmt::{self, Debug, Display, Formatter};
/// Accepted authentication mechanisms
///
/// Trying LOGIN last as it is deprecated.
pub const DEFAULT_MECHANISMS: &[Mechanism] = &[Mechanism::Plain, Mechanism::Login];

View File

@@ -44,20 +44,6 @@ impl AsyncSmtpConnection {
&self.server_info
}
/// Connects to the configured server
///
/// Sends EHLO and parses server information
#[cfg(feature = "tokio02")]
pub async fn connect_tokio02(
hostname: &str,
port: u16,
hello_name: &ClientId,
tls_parameters: Option<TlsParameters>,
) -> Result<AsyncSmtpConnection, Error> {
let stream = AsyncNetworkStream::connect_tokio02(hostname, port, tls_parameters).await?;
Self::connect_impl(stream, hello_name).await
}
/// Connects to the configured server
///
/// Sends EHLO and parses server information
@@ -312,7 +298,7 @@ impl AsyncSmtpConnection {
return if response.is_positive() {
Ok(response)
} else {
Err(error::code(response.code))
Err(error::code(response.code()))
}
}
Err(nom::Err::Failure(e)) => {

View File

@@ -9,35 +9,25 @@ use futures_io::{
AsyncRead as FuturesAsyncRead, AsyncWrite as FuturesAsyncWrite, Error as IoError, ErrorKind,
Result as IoResult,
};
#[cfg(feature = "tokio02")]
use tokio02_crate::io::{AsyncRead as _, AsyncWrite as _};
#[cfg(feature = "tokio1")]
use tokio1_crate::io::{AsyncRead as _, AsyncWrite as _, ReadBuf as Tokio1ReadBuf};
#[cfg(feature = "async-std1")]
use async_std::net::TcpStream as AsyncStd1TcpStream;
#[cfg(feature = "tokio02")]
use tokio02_crate::net::TcpStream as Tokio02TcpStream;
#[cfg(feature = "tokio1")]
use tokio1_crate::net::TcpStream as Tokio1TcpStream;
#[cfg(feature = "async-std1-native-tls")]
use async_native_tls::TlsStream as AsyncStd1TlsStream;
#[cfg(feature = "tokio02-native-tls")]
use tokio02_native_tls_crate::TlsStream as Tokio02TlsStream;
#[cfg(feature = "tokio1-native-tls")]
use tokio1_native_tls_crate::TlsStream as Tokio1TlsStream;
#[cfg(feature = "async-std1-rustls-tls")]
use async_rustls::client::TlsStream as AsyncStd1RustlsTlsStream;
#[cfg(feature = "tokio02-rustls-tls")]
use tokio02_rustls::client::TlsStream as Tokio02RustlsTlsStream;
#[cfg(feature = "tokio1-rustls-tls")]
use tokio1_rustls::client::TlsStream as Tokio1RustlsTlsStream;
#[cfg(any(
feature = "tokio02-native-tls",
feature = "tokio02-rustls-tls",
feature = "tokio1-native-tls",
feature = "tokio1-rustls-tls",
feature = "async-std1-native-tls",
@@ -58,15 +48,6 @@ pub struct AsyncNetworkStream {
#[allow(clippy::large_enum_variant)]
#[allow(dead_code)]
enum InnerAsyncNetworkStream {
/// Plain Tokio 0.2 TCP stream
#[cfg(feature = "tokio02")]
Tokio02Tcp(Tokio02TcpStream),
/// Encrypted Tokio 0.2 TCP stream
#[cfg(feature = "tokio02-native-tls")]
Tokio02NativeTls(Tokio02TlsStream<Tokio02TcpStream>),
/// Encrypted Tokio 0.2 TCP stream
#[cfg(feature = "tokio02-rustls-tls")]
Tokio02RustlsTls(Tokio02RustlsTlsStream<Tokio02TcpStream>),
/// Plain Tokio 1.x TCP stream
#[cfg(feature = "tokio1")]
Tokio1Tcp(Tokio1TcpStream),
@@ -101,14 +82,6 @@ impl AsyncNetworkStream {
/// Returns peer's address
pub fn peer_addr(&self) -> IoResult<SocketAddr> {
match self.inner {
#[cfg(feature = "tokio02")]
InnerAsyncNetworkStream::Tokio02Tcp(ref s) => s.peer_addr(),
#[cfg(feature = "tokio02-native-tls")]
InnerAsyncNetworkStream::Tokio02NativeTls(ref s) => {
s.get_ref().get_ref().get_ref().peer_addr()
}
#[cfg(feature = "tokio02-rustls-tls")]
InnerAsyncNetworkStream::Tokio02RustlsTls(ref s) => s.get_ref().0.peer_addr(),
#[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(ref s) => s.peer_addr(),
#[cfg(feature = "tokio1-native-tls")]
@@ -133,23 +106,6 @@ impl AsyncNetworkStream {
}
}
#[cfg(feature = "tokio02")]
pub async fn connect_tokio02(
hostname: &str,
port: u16,
tls_parameters: Option<TlsParameters>,
) -> Result<AsyncNetworkStream, Error> {
let tcp_stream = Tokio02TcpStream::connect((hostname, port))
.await
.map_err(error::connection)?;
let mut stream = AsyncNetworkStream::new(InnerAsyncNetworkStream::Tokio02Tcp(tcp_stream));
if let Some(tls_parameters) = tls_parameters {
stream.upgrade_tls(tls_parameters).await?;
}
Ok(stream)
}
#[cfg(feature = "tokio1")]
pub async fn connect_tokio1(
hostname: &str,
@@ -186,29 +142,6 @@ impl AsyncNetworkStream {
pub async fn upgrade_tls(&mut self, tls_parameters: TlsParameters) -> Result<(), Error> {
match &self.inner {
#[cfg(all(
feature = "tokio02",
not(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))
))]
InnerAsyncNetworkStream::Tokio02Tcp(_) => {
let _ = tls_parameters;
panic!("Trying to upgrade an AsyncNetworkStream without having enabled either the tokio02-native-tls or the tokio02-rustls-tls feature");
}
#[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))]
InnerAsyncNetworkStream::Tokio02Tcp(_) => {
// get owned TcpStream
let tcp_stream = mem::replace(&mut self.inner, InnerAsyncNetworkStream::None);
let tcp_stream = match tcp_stream {
InnerAsyncNetworkStream::Tokio02Tcp(tcp_stream) => tcp_stream,
_ => unreachable!(),
};
self.inner = Self::upgrade_tokio02_tls(tcp_stream, tls_parameters)
.await
.map_err(error::connection)?;
Ok(())
}
#[cfg(all(
feature = "tokio1",
not(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))
@@ -259,55 +192,6 @@ impl AsyncNetworkStream {
}
}
#[allow(unused_variables)]
#[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))]
async fn upgrade_tokio02_tls(
tcp_stream: Tokio02TcpStream,
mut tls_parameters: TlsParameters,
) -> Result<InnerAsyncNetworkStream, Error> {
let domain = mem::take(&mut tls_parameters.domain);
match tls_parameters.connector {
#[cfg(feature = "native-tls")]
InnerTlsParameters::NativeTls(connector) => {
#[cfg(not(feature = "tokio02-native-tls"))]
panic!("built without the tokio02-native-tls feature");
#[cfg(feature = "tokio02-native-tls")]
return {
use tokio02_native_tls_crate::TlsConnector;
let connector = TlsConnector::from(connector);
let stream = connector
.connect(&domain, tcp_stream)
.await
.map_err(error::connection)?;
Ok(InnerAsyncNetworkStream::Tokio02NativeTls(stream))
};
}
#[cfg(feature = "rustls-tls")]
InnerTlsParameters::RustlsTls(config) => {
#[cfg(not(feature = "tokio02-rustls-tls"))]
panic!("built without the tokio02-rustls-tls feature");
#[cfg(feature = "tokio02-rustls-tls")]
return {
use tokio02_rustls::{webpki::DNSNameRef, TlsConnector};
let domain =
DNSNameRef::try_from_ascii_str(&domain).map_err(error::connection)?;
let connector = TlsConnector::from(config);
let stream = connector
.connect(domain, tcp_stream)
.await
.map_err(error::connection)?;
Ok(InnerAsyncNetworkStream::Tokio02RustlsTls(stream))
};
}
}
}
#[allow(unused_variables)]
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
async fn upgrade_tokio1_tls(
@@ -411,12 +295,6 @@ impl AsyncNetworkStream {
pub fn is_encrypted(&self) -> bool {
match self.inner {
#[cfg(feature = "tokio02")]
InnerAsyncNetworkStream::Tokio02Tcp(_) => false,
#[cfg(feature = "tokio02-native-tls")]
InnerAsyncNetworkStream::Tokio02NativeTls(_) => true,
#[cfg(feature = "tokio02-rustls-tls")]
InnerAsyncNetworkStream::Tokio02RustlsTls(_) => true,
#[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(_) => false,
#[cfg(feature = "tokio1-native-tls")]
@@ -441,12 +319,6 @@ impl FuturesAsyncRead for AsyncNetworkStream {
buf: &mut [u8],
) -> Poll<IoResult<usize>> {
match self.inner {
#[cfg(feature = "tokio02")]
InnerAsyncNetworkStream::Tokio02Tcp(ref mut s) => Pin::new(s).poll_read(cx, buf),
#[cfg(feature = "tokio02-native-tls")]
InnerAsyncNetworkStream::Tokio02NativeTls(ref mut s) => Pin::new(s).poll_read(cx, buf),
#[cfg(feature = "tokio02-rustls-tls")]
InnerAsyncNetworkStream::Tokio02RustlsTls(ref mut s) => Pin::new(s).poll_read(cx, buf),
#[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => {
let mut b = Tokio1ReadBuf::new(buf);
@@ -499,12 +371,6 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
buf: &[u8],
) -> Poll<IoResult<usize>> {
match self.inner {
#[cfg(feature = "tokio02")]
InnerAsyncNetworkStream::Tokio02Tcp(ref mut s) => Pin::new(s).poll_write(cx, buf),
#[cfg(feature = "tokio02-native-tls")]
InnerAsyncNetworkStream::Tokio02NativeTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
#[cfg(feature = "tokio02-rustls-tls")]
InnerAsyncNetworkStream::Tokio02RustlsTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
#[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => Pin::new(s).poll_write(cx, buf),
#[cfg(feature = "tokio1-native-tls")]
@@ -530,12 +396,6 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
match self.inner {
#[cfg(feature = "tokio02")]
InnerAsyncNetworkStream::Tokio02Tcp(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "tokio02-native-tls")]
InnerAsyncNetworkStream::Tokio02NativeTls(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "tokio02-rustls-tls")]
InnerAsyncNetworkStream::Tokio02RustlsTls(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "tokio1-native-tls")]
@@ -557,12 +417,6 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
match self.inner {
#[cfg(feature = "tokio02")]
InnerAsyncNetworkStream::Tokio02Tcp(ref mut s) => Pin::new(s).poll_shutdown(cx),
#[cfg(feature = "tokio02-native-tls")]
InnerAsyncNetworkStream::Tokio02NativeTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
#[cfg(feature = "tokio02-rustls-tls")]
InnerAsyncNetworkStream::Tokio02RustlsTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
#[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => Pin::new(s).poll_shutdown(cx),
#[cfg(feature = "tokio1-native-tls")]

View File

@@ -276,7 +276,7 @@ impl SmtpConnection {
return if response.is_positive() {
Ok(response)
} else {
Err(error::code(response.code))
Err(error::code(response.code()))
};
}
Err(nom::Err::Failure(e)) => {

View File

@@ -27,9 +27,9 @@
#[cfg(feature = "serde")]
use std::fmt::Debug;
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
pub(crate) use self::async_connection::AsyncSmtpConnection;
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
pub(crate) use self::async_net::AsyncNetworkStream;
use self::net::NetworkStream;
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
@@ -39,9 +39,9 @@ pub use self::{
tls::{Certificate, Tls, TlsParameters, TlsParametersBuilder},
};
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
mod async_connection;
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
mod async_net;
mod connection;
mod net;
@@ -78,7 +78,15 @@ impl ClientCodec {
match self.escape_count {
0 => self.escape_count = if *byte == b'\r' { 1 } else { 0 },
1 => self.escape_count = if *byte == b'\n' { 2 } else { 0 },
2 => self.escape_count = if *byte == b'.' { 3 } else { 0 },
2 => {
self.escape_count = if *byte == b'.' {
3
} else if *byte == b'\r' {
1
} else {
0
}
}
_ => unreachable!(),
}
if self.escape_count == 3 {
@@ -111,6 +119,7 @@ mod test {
let mut buf: Vec<u8> = vec![];
codec.encode(b"test\r\n", &mut buf);
codec.encode(b"test\r\n\r\n", &mut buf);
codec.encode(b".\r\n", &mut buf);
codec.encode(b"\r\ntest", &mut buf);
codec.encode(b"te\r\n.\r\nst", &mut buf);
@@ -121,7 +130,7 @@ mod test {
codec.encode(b"test", &mut buf);
assert_eq!(
String::from_utf8(buf).unwrap(),
"test\r\n..\r\n\r\ntestte\r\n..\r\nsttesttest.test\n.test\ntest"
"test\r\ntest\r\n\r\n..\r\n\r\ntestte\r\n..\r\nsttesttest.test\n.test\ntest"
);
}

View File

@@ -72,6 +72,7 @@ impl ClientId {
/// Supported ESMTP keywords
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Extension {
/// 8BITMIME keyword
///
@@ -107,11 +108,11 @@ pub struct ServerInfo {
/// Server name
///
/// The name given in the server banner
pub name: String,
name: String,
/// ESMTP features supported by the server
///
/// It contains the features supported by the server and known by the `Extension` module.
pub features: HashSet<Extension>,
features: HashSet<Extension>,
}
impl Display for ServerInfo {
@@ -135,7 +136,7 @@ impl ServerInfo {
let mut features: HashSet<Extension> = HashSet::new();
for line in response.message.as_slice() {
for line in response.message() {
if line.is_empty() {
continue;
}
@@ -197,6 +198,11 @@ impl ServerInfo {
}
None
}
/// The name given in the server banner
pub fn name(&self) -> &str {
self.name.as_ref()
}
}
/// A `MAIL FROM` extension parameter

View File

@@ -116,7 +116,7 @@
//! # }
//! ```
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
pub use self::async_transport::{AsyncSmtpTransport, AsyncSmtpTransportBuilder};
#[cfg(feature = "r2d2")]
pub use self::pool::PoolConfig;
@@ -137,7 +137,7 @@ use crate::transport::smtp::{
use client::Tls;
use std::time::Duration;
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
mod async_transport;
pub mod authentication;
pub mod client;
@@ -148,7 +148,7 @@ pub mod extension;
mod pool;
pub mod response;
mod transport;
pub mod util;
pub(super) mod util;
// Registered port numbers:
// https://www.iana.
@@ -164,7 +164,7 @@ pub const SUBMISSION_PORT: u16 = 587;
pub const SUBMISSIONS_PORT: u16 = 465;
/// Default timeout
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);
#[derive(Debug, Clone)]
struct SmtpInfo {

View File

@@ -137,10 +137,10 @@ impl Code {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Response {
/// Response code
pub code: Code,
code: Code,
/// Server response string (optional)
/// Handle multiline responses
pub message: Vec<String>,
message: Vec<String>,
}
impl FromStr for Response {
@@ -180,6 +180,16 @@ impl Response {
pub fn first_line(&self) -> Option<&str> {
self.message.first().map(String::as_str)
}
/// Response code
pub fn code(&self) -> Code {
self.code
}
/// Server response string (array of lines)
pub fn message(&self) -> impl Iterator<Item = &str> {
self.message.iter().map(String::as_str)
}
}
// Parsers (originally from tokio-smtp)

View File

@@ -4,7 +4,6 @@ use std::fmt::{Display, Formatter, Result as FmtResult};
/// Encode a string as xtext
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct XText<'a>(pub &'a str);
impl<'a> Display for XText<'a> {

View File

@@ -28,10 +28,10 @@
//! # }
//! ```
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
use crate::AsyncTransport;
use crate::{address::Envelope, Transport};
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
use async_trait::async_trait;
use std::{error::Error as StdError, fmt};
@@ -80,7 +80,7 @@ impl Transport for StubTransport {
}
}
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
#[async_trait]
impl AsyncTransport for StubTransport {
type Ok = ();

View File

@@ -101,53 +101,6 @@ mod sync {
}
}
#[cfg(test)]
#[cfg(all(feature = "file-transport", feature = "builder", feature = "tokio02"))]
mod tokio_02 {
use crate::default_date;
use lettre::{AsyncFileTransport, AsyncTransport, Message, Tokio02Executor};
use std::{
env::temp_dir,
fs::{read_to_string, remove_file},
};
use tokio02_crate as tokio;
#[tokio::test]
async fn file_transport_tokio02() {
let sender = AsyncFileTransport::<Tokio02Executor>::new(temp_dir());
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.date(default_date())
.body(String::from("Be happy!"))
.unwrap();
let result = sender.send(email).await;
let id = result.unwrap();
let eml_file = temp_dir().join(format!("{}.eml", id));
let eml = read_to_string(&eml_file).unwrap();
assert_eq!(
eml,
concat!(
"From: NoBody <nobody@domain.tld>\r\n",
"Reply-To: Yuin <yuin@domain.tld>\r\n",
"To: Hei <hei@domain.tld>\r\n",
"Subject: Happy new year\r\n",
"Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n",
"Content-Transfer-Encoding: 7bit\r\n",
"\r\n",
"Be happy!"
)
);
remove_file(eml_file).unwrap();
}
}
#[cfg(test)]
#[cfg(all(feature = "file-transport", feature = "builder", feature = "tokio1"))]
mod tokio_1 {
@@ -160,7 +113,6 @@ mod tokio_1 {
use tokio1_crate as tokio;
#[cfg(feature = "tokio02")]
#[tokio::test]
async fn file_transport_tokio1() {
let sender = AsyncFileTransport::<Tokio1Executor>::new(temp_dir());

View File

@@ -20,33 +20,6 @@ mod sync {
}
}
#[cfg(test)]
#[cfg(all(
feature = "sendmail-transport",
feature = "builder",
feature = "tokio02"
))]
mod tokio_02 {
use lettre::{AsyncSendmailTransport, AsyncTransport, Message, Tokio02Executor};
use tokio02_crate as tokio;
#[tokio::test]
async fn sendmail_transport_tokio02() {
let sender = AsyncSendmailTransport::<Tokio02Executor>::new();
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.body(String::from("Be happy!"))
.unwrap();
let result = sender.send(email).await;
println!("{:?}", result);
assert!(result.is_ok());
}
}
#[cfg(test)]
#[cfg(all(
feature = "sendmail-transport",

View File

@@ -20,31 +20,6 @@ mod sync {
}
}
#[cfg(test)]
#[cfg(all(feature = "smtp-transport", feature = "builder", feature = "tokio02"))]
mod tokio_02 {
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio02Executor};
use tokio02_crate as tokio;
#[tokio::test]
async fn smtp_transport_simple_tokio02() {
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.body(String::from("Be happy!"))
.unwrap();
let sender: AsyncSmtpTransport<Tokio02Executor> =
AsyncSmtpTransport::<Tokio02Executor>::builder_dangerous("127.0.0.1")
.port(2525)
.build();
sender.send(email).await.unwrap();
}
}
#[cfg(test)]
#[cfg(all(feature = "smtp-transport", feature = "builder", feature = "tokio1"))]
mod tokio_1 {

View File

@@ -20,30 +20,6 @@ mod sync {
}
}
#[cfg(test)]
#[cfg(all(feature = "builder", feature = "tokio02"))]
mod tokio_02 {
use lettre::{transport::stub::StubTransport, AsyncTransport, Message};
use tokio02_crate as tokio;
#[tokio::test]
async fn stub_transport_tokio02() {
let sender_ok = StubTransport::new_ok();
let sender_ko = StubTransport::new_error();
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.body(String::from("Be happy!"))
.unwrap();
sender_ok.send(email.clone()).await.unwrap();
sender_ko.send(email).await.unwrap_err();
}
}
#[cfg(test)]
#[cfg(all(feature = "builder", feature = "tokio1"))]
mod tokio_1 {