Compare commits

...

9 Commits

Author SHA1 Message Date
Paolo Barbolini
9004d4ccc5 Add documentation to undocumented items (#793) 2022-06-29 22:44:29 +02:00
Paolo Barbolini
10171f8c75 Prepare 0.10.0 release (#538) 2022-06-29 21:17:37 +02:00
Paolo Barbolini
99e805952d Make it possible to keep the Bcc header when building a message (#792) 2022-06-29 21:08:27 +02:00
Paolo Barbolini
2d21dde5a1 Add autoconfigure.rs example (#787) 2022-06-17 06:35:07 +00:00
Paolo Barbolini
6fec936c0c Remove useless vec! allocations (#786) 2022-06-16 18:17:19 +00:00
Paolo Barbolini
22dfa5aa96 MessageBuilder: improve order headers are defined in (#783) 2022-06-16 17:53:54 +00:00
Paolo Barbolini
44e4cfd622 clippy: make rules stricter (#784) 2022-06-16 19:42:13 +02:00
Paolo Barbolini
7ea3d38a00 Mailboxes: add FromIterator impl and optimize Extend impl (#782) 2022-06-11 18:18:55 +00:00
Paolo Barbolini
73b89f5a9f clippy: fix latest warnings (#781) 2022-06-11 16:31:12 +00:00
27 changed files with 319 additions and 131 deletions

View File

@@ -29,6 +29,7 @@ Several breaking changes were made between 0.9 and 0.10, but changes should be s
* Refactor `TlsParameters` implementation to not expose the internal TLS library
* `FileTransport` writes emails into `.eml` instead of `.json`
* When the hostname feature is disabled or hostname cannot be fetched, `127.0.0.1` is used instead of `localhost` as EHLO parameter (for better RFC compliance and mail server compatibility)
* The `sendmail` and `file` transports aren't enabled by default anymore.
* The `new` method of `ClientId` is deprecated
* Rename `serde-impls` feature to `serde`
* The `SendmailTransport` now uses the `sendmail` command in current `PATH` by default instead of

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-rc.7"
version = "0.10.0"
description = "Email client"
readme = "README.md"
homepage = "https://lettre.rs"
@@ -74,6 +74,7 @@ email_address = { version = "0.2.1", default-features = false }
[dev-dependencies]
pretty_assertions = "1"
criterion = "0.3"
tracing = { version = "0.1.16", default-features = false, features = ["std"] }
tracing-subscriber = "0.3"
glob = "0.3"
walkdir = "2"
@@ -115,6 +116,10 @@ dkim = ["base64", "sha2", "rsa", "ed25519-dalek", "regex", "once_cell"]
all-features = true
rustdoc-args = ["--cfg", "docsrs", "--cfg", "lettre_ignore_tls_mismatch"]
[[example]]
name = "autoconfigure"
required-features = ["smtp-transport", "native-tls"]
[[example]]
name = "basic_html"
required-features = ["file-transport", "builder"]

View File

@@ -28,27 +28,14 @@
</div>
<div align="center">
<a href="https://deps.rs/crate/lettre/0.10.0-rc.7">
<img src="https://deps.rs/crate/lettre/0.10.0-rc.7/status.svg"
<a href="https://deps.rs/crate/lettre/0.10.0">
<img src="https://deps.rs/crate/lettre/0.10.0/status.svg"
alt="dependency status" />
</a>
</div>
---
**NOTE**: this readme refers to the 0.10 version of lettre, which is
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.
---
## Features
Lettre provides the following features:
@@ -63,15 +50,20 @@ Lettre does not provide (for now):
* Email parsing
## Supported Rust Versions
Lettre supports all Rust versions released in the last 6 months. At the time of writing
the minimum supported Rust version is 1.56, but this could change at any time either from
one of our dependencies bumping their MSRV or by a new patch release of lettre.
## Example
This library requires Rust 1.56.0 or newer.
To use this library, add the following to your `Cargo.toml`:
```toml
[dependencies]
lettre = "0.10.0-rc.7"
lettre = "0.10"
```
```rust,no_run
@@ -101,6 +93,14 @@ match mailer.send(&email) {
}
```
## Not sure of which connect options to use?
Clone the lettre git repository and run the following command (replacing `SMTP_HOST` with your SMTP server's hostname)
```shell
cargo run --example autoconfigure SMTP_HOST
```
## Testing
The `lettre` tests require an open mail server listening locally on port 2525 and the `sendmail` command. If you have python installed

93
examples/autoconfigure.rs Normal file
View File

@@ -0,0 +1,93 @@
use std::{env, process, time::Duration};
use lettre::SmtpTransport;
fn main() {
tracing_subscriber::fmt::init();
let smtp_host = match env::args().nth(1) {
Some(smtp_host) => smtp_host,
None => {
println!("Please provide the SMTP host as the first argument to this command");
process::exit(1);
}
};
// TLS wrapped connection
{
tracing::info!(
"Trying to establish a TLS wrapped connection to {}",
smtp_host
);
let transport = SmtpTransport::relay(&smtp_host)
.expect("build SmtpTransport::relay")
.timeout(Some(Duration::from_secs(10)))
.build();
match transport.test_connection() {
Ok(true) => {
tracing::info!("Successfully connected to {} via a TLS wrapped connection (SmtpTransport::relay). This is the fastest option available for connecting to an SMTP server", smtp_host);
}
Ok(false) => {
tracing::error!("Couldn't connect to {} via a TLS wrapped connection. No more information is available", smtp_host);
}
Err(err) => {
tracing::error!(err = %err, "Couldn't connect to {} via a TLS wrapped connection", smtp_host);
}
}
}
println!();
// Plaintext connection which MUST then successfully upgrade to TLS via STARTTLS
{
tracing::info!("Trying to establish a plaintext connection to {} and then updating it via the SMTP STARTTLS extension", smtp_host);
let transport = SmtpTransport::starttls_relay(&smtp_host)
.expect("build SmtpTransport::starttls_relay")
.timeout(Some(Duration::from_secs(10)))
.build();
match transport.test_connection() {
Ok(true) => {
tracing::info!("Successfully connected to {} via a plaintext connection which then got upgraded to TLS via the SMTP STARTTLS extension (SmtpTransport::starttls_relay). This is the second best option after the previous TLS wrapped option", smtp_host);
}
Ok(false) => {
tracing::error!(
"Couldn't connect to {} via STARTTLS. No more information is available",
smtp_host
);
}
Err(err) => {
tracing::error!(err = %err, "Couldn't connect to {} via STARTTLS", smtp_host);
}
}
}
println!();
// Plaintext connection (very insecure)
{
tracing::info!(
"Trying to establish a plaintext connection to {}",
smtp_host
);
let transport = SmtpTransport::builder_dangerous(&smtp_host)
.timeout(Some(Duration::from_secs(10)))
.build();
match transport.test_connection() {
Ok(true) => {
tracing::info!("Successfully connected to {} via a plaintext connection. This option is very insecure and shouldn't be used on the public internet (SmtpTransport::builder_dangerous)", smtp_host);
}
Ok(false) => {
tracing::error!(
"Couldn't connect to {} via a plaintext connection. No more information is available",
smtp_host
);
}
Err(err) => {
tracing::error!(err = %err, "Couldn't connect to {} via a plaintext connection", smtp_host);
}
}
}
}

View File

@@ -226,7 +226,7 @@ fn check_address(val: &str) -> Result<usize, AddressError> {
Ok(user.len())
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
/// Errors in email addresses parsing
pub enum AddressError {
/// Missing domain or user

View File

@@ -109,7 +109,6 @@ impl Executor for Tokio1Executor {
#[cfg(feature = "smtp-transport")]
type Sleep = tokio1_crate::time::Sleep;
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
fn spawn<F>(fut: F) -> Self::Handle
where
@@ -119,13 +118,11 @@ impl Executor for Tokio1Executor {
tokio1_crate::spawn(fut)
}
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
fn sleep(duration: Duration) -> Self::Sleep {
tokio1_crate::time::sleep(duration)
}
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
async fn connect(
hostname: &str,
@@ -166,13 +163,11 @@ impl Executor for Tokio1Executor {
Ok(conn)
}
#[doc(hidden)]
#[cfg(feature = "file-transport-envelope")]
async fn fs_read(path: &Path) -> IoResult<Vec<u8>> {
tokio1_crate::fs::read(path).await
}
#[doc(hidden)]
#[cfg(feature = "file-transport")]
async fn fs_write(path: &Path, contents: &[u8]) -> IoResult<()> {
tokio1_crate::fs::write(path, contents).await
@@ -210,7 +205,6 @@ impl Executor for AsyncStd1Executor {
#[cfg(feature = "smtp-transport")]
type Sleep = BoxFuture<'static, ()>;
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
fn spawn<F>(fut: F) -> Self::Handle
where
@@ -220,14 +214,12 @@ impl Executor for AsyncStd1Executor {
async_std::task::spawn(fut)
}
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
fn sleep(duration: Duration) -> Self::Sleep {
let fut = async move { async_std::task::sleep(duration).await };
Box::pin(fut)
}
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
async fn connect(
hostname: &str,
@@ -267,13 +259,11 @@ impl Executor for AsyncStd1Executor {
Ok(conn)
}
#[doc(hidden)]
#[cfg(feature = "file-transport-envelope")]
async fn fs_read(path: &Path) -> IoResult<Vec<u8>> {
async_std::fs::read(path).await
}
#[doc(hidden)]
#[cfg(feature = "file-transport")]
async fn fs_write(path: &Path, contents: &[u8]) -> IoResult<()> {
async_std::fs::write(path, contents).await
@@ -289,15 +279,13 @@ impl SpawnHandle for async_std::task::JoinHandle<()> {
}
mod private {
use super::*;
pub trait Sealed {}
#[cfg(feature = "tokio1")]
impl Sealed for Tokio1Executor {}
impl Sealed for super::Tokio1Executor {}
#[cfg(feature = "async-std1")]
impl Sealed for AsyncStd1Executor {}
impl Sealed for super::AsyncStd1Executor {}
#[cfg(all(feature = "smtp-transport", feature = "tokio1"))]
impl Sealed for tokio1_crate::task::JoinHandle<()> {}

View File

@@ -100,7 +100,7 @@
//! [mime 0.3]: https://docs.rs/mime/0.3
//! [DKIM]: https://datatracker.ietf.org/doc/html/rfc6376
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.10.0-rc.7")]
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.10.0")]
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
#![forbid(unsafe_code)]
@@ -112,7 +112,21 @@
unused_import_braces,
rust_2018_idioms,
clippy::string_add,
clippy::string_add_assign
clippy::string_add_assign,
clippy::clone_on_ref_ptr,
clippy::verbose_file_reads,
clippy::unnecessary_self_imports,
clippy::string_to_string,
clippy::mem_forget,
clippy::cast_lossless,
clippy::inefficient_to_string,
clippy::inline_always,
clippy::linkedlist,
clippy::macro_use_imports,
clippy::manual_assert,
clippy::unnecessary_join,
clippy::wildcard_imports,
clippy::zero_sized_map_values
)]
#![cfg_attr(docsrs, feature(doc_cfg))]

View File

@@ -96,9 +96,9 @@ impl Display for DkimSigningKeyError {
impl StdError for DkimSigningKeyError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(match &self.0 {
InnerDkimSigningKeyError::Base64(err) => &*err,
InnerDkimSigningKeyError::Rsa(err) => &*err,
InnerDkimSigningKeyError::Ed25519(err) => &*err,
InnerDkimSigningKeyError::Base64(err) => err,
InnerDkimSigningKeyError::Rsa(err) => err,
InnerDkimSigningKeyError::Ed25519(err) => err,
})
}
}
@@ -245,7 +245,7 @@ fn dkim_canonicalize_headers_relaxed(headers: &str) -> String {
let mut r = String::with_capacity(headers.len());
fn skip_whitespace(h: &str) -> &str {
match h.as_bytes().get(0) {
match h.as_bytes().first() {
Some(b' ' | b'\t') => skip_whitespace(&h[1..]),
_ => h,
}

View File

@@ -8,7 +8,7 @@ use crate::BoxError;
/// Message `Date` header
///
/// Defined in [RFC2822](https://tools.ietf.org/html/rfc2822#section-3.3)
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Date(HttpDate);
impl Date {

View File

@@ -14,7 +14,7 @@ pub trait MailboxesHeader {
macro_rules! mailbox_header {
($(#[$doc:meta])*($type_name: ident, $header_name: expr)) => {
$(#[$doc])*
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct $type_name(Mailbox);
impl Header for $type_name {
@@ -56,7 +56,7 @@ macro_rules! mailbox_header {
macro_rules! mailboxes_header {
($(#[$doc:meta])*($type_name: ident, $header_name: expr)) => {
$(#[$doc])*
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct $type_name(pub(crate) Mailboxes);
impl MailboxesHeader for $type_name {

View File

@@ -277,6 +277,7 @@ impl PartialEq<HeaderName> for &str {
}
}
/// A safe for use header value
#[derive(Debug, Clone, PartialEq)]
pub struct HeaderValue {
name: HeaderName,
@@ -285,6 +286,12 @@ pub struct HeaderValue {
}
impl HeaderValue {
/// Construct a new `HeaderValue` and encode it
///
/// Takes the header `name` and the `raw_value` and encodes
/// it via `RFC2047` and line folds it.
///
/// [`RFC2047`]: https://datatracker.ietf.org/doc/html/rfc2047
pub fn new(name: HeaderName, raw_value: String) -> Self {
let mut encoded_value = String::with_capacity(raw_value.len());
HeaderValueEncoder::encode(&name, &raw_value, &mut encoded_value).unwrap();
@@ -296,6 +303,14 @@ impl HeaderValue {
}
}
/// Construct a new `HeaderValue` using a pre-encoded header value
///
/// This method is _extremely_ dangerous as it opens up
/// the encoder to header injection attacks, but is sometimes
/// acceptable for use if `encoded_value` contains only ascii
/// printable characters and is already line folded.
///
/// When in doubt use [`HeaderValue::new`].
pub fn dangerous_new_pre_encoded(
name: HeaderName,
raw_value: String,

View File

@@ -4,7 +4,7 @@ use crate::{
};
/// Message format version, defined in [RFC2045](https://tools.ietf.org/html/rfc2045#section-4)
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct MimeVersion {
major: u8,
minor: u8,
@@ -16,15 +16,18 @@ pub struct MimeVersion {
pub const MIME_VERSION_1_0: MimeVersion = MimeVersion::new(1, 0);
impl MimeVersion {
/// Build a new `MimeVersion` header
pub const fn new(major: u8, minor: u8) -> Self {
MimeVersion { major, minor }
}
/// Get the `major` value of this `MimeVersion` header.
#[inline]
pub const fn major(self) -> u8 {
self.major
}
/// Get the `minor` value of this `MimeVersion` header.
#[inline]
pub const fn minor(self) -> u8 {
self.minor

View File

@@ -4,7 +4,7 @@ use crate::BoxError;
macro_rules! text_header {
($(#[$attr:meta])* Header($type_name: ident, $header_name: expr )) => {
$(#[$attr])*
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct $type_name(String);
impl Header for $type_name {

View File

@@ -315,6 +315,18 @@ impl From<Mailboxes> for Vec<Mailbox> {
}
}
impl FromIterator<Mailbox> for Mailboxes {
fn from_iter<T: IntoIterator<Item = Mailbox>>(iter: T) -> Self {
Self(Vec::from_iter(iter))
}
}
impl Extend<Mailbox> for Mailboxes {
fn extend<T: IntoIterator<Item = Mailbox>>(&mut self, iter: T) {
self.0.extend(iter);
}
}
impl IntoIterator for Mailboxes {
type Item = Mailbox;
type IntoIter = ::std::vec::IntoIter<Mailbox>;
@@ -324,14 +336,6 @@ impl IntoIterator for Mailboxes {
}
}
impl Extend<Mailbox> for Mailboxes {
fn extend<T: IntoIterator<Item = Mailbox>>(&mut self, iter: T) {
for elem in iter {
self.0.push(elem);
}
}
}
impl Display for Mailboxes {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let mut iter = self.iter();

View File

@@ -232,6 +232,7 @@ trait EmailFormat {
pub struct MessageBuilder {
headers: Headers,
envelope: Option<Envelope>,
drop_bcc: bool,
}
impl MessageBuilder {
@@ -240,24 +241,26 @@ impl MessageBuilder {
Self {
headers: Headers::new(),
envelope: None,
drop_bcc: true,
}
}
/// Set custom header to message
pub fn header<H: Header>(mut self, header: H) -> Self {
self.headers.set(header);
self
/// Set or add mailbox to `From` header
///
/// Defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.2).
///
/// Shortcut for `self.mailbox(header::From(mbox))`.
pub fn from(self, mbox: Mailbox) -> Self {
self.mailbox(header::From::from(Mailboxes::from(mbox)))
}
/// Add mailbox to header
pub fn mailbox<H: Header + MailboxesHeader>(self, header: H) -> Self {
match self.headers.get::<H>() {
Some(mut header_) => {
header_.join_mailboxes(header);
self.header(header_)
}
None => self.header(header),
}
/// Set `Sender` header. Should be used when providing several `From` mailboxes.
///
/// Defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.2).
///
/// Shortcut for `self.header(header::Sender(mbox))`.
pub fn sender(self, mbox: Mailbox) -> Self {
self.header(header::Sender::from(mbox))
}
/// Add `Date` header to message
@@ -275,41 +278,6 @@ impl MessageBuilder {
self.date(SystemTime::now())
}
/// Set `Subject` header to message
///
/// Shortcut for `self.header(header::Subject(subject.into()))`.
pub fn subject<S: Into<String>>(self, subject: S) -> Self {
let s: String = subject.into();
self.header(header::Subject::from(s))
}
/// Set `MIME-Version` header to 1.0
///
/// Shortcut for `self.header(header::MIME_VERSION_1_0)`.
///
/// Not exposed as it is set by body methods
fn mime_1_0(self) -> Self {
self.header(header::MIME_VERSION_1_0)
}
/// Set `Sender` header. Should be used when providing several `From` mailboxes.
///
/// Defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.2).
///
/// Shortcut for `self.header(header::Sender(mbox))`.
pub fn sender(self, mbox: Mailbox) -> Self {
self.header(header::Sender::from(mbox))
}
/// Set or add mailbox to `From` header
///
/// Defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.2).
///
/// Shortcut for `self.mailbox(header::From(mbox))`.
pub fn from(self, mbox: Mailbox) -> Self {
self.mailbox(header::From::from(Mailboxes::from(mbox)))
}
/// Set or add mailbox to `ReplyTo` header
///
/// Defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.2).
@@ -352,6 +320,14 @@ impl MessageBuilder {
self.header(header::References::from(id))
}
/// Set `Subject` header to message
///
/// Shortcut for `self.header(header::Subject(subject.into()))`.
pub fn subject<S: Into<String>>(self, subject: S) -> Self {
let s: String = subject.into();
self.header(header::Subject::from(s))
}
/// Set [Message-ID
/// header](https://tools.ietf.org/html/rfc5322#section-3.6.4)
///
@@ -385,12 +361,43 @@ impl MessageBuilder {
self.header(header::UserAgent::from(id))
}
/// Set custom header to message
pub fn header<H: Header>(mut self, header: H) -> Self {
self.headers.set(header);
self
}
/// Add mailbox to header
pub fn mailbox<H: Header + MailboxesHeader>(self, header: H) -> Self {
match self.headers.get::<H>() {
Some(mut header_) => {
header_.join_mailboxes(header);
self.header(header_)
}
None => self.header(header),
}
}
/// Force specific envelope (by default it is derived from headers)
pub fn envelope(mut self, envelope: Envelope) -> Self {
self.envelope = Some(envelope);
self
}
/// Keep the `Bcc` header
///
/// By default the `Bcc` header is removed from the email after
/// using it to generate the message envelope. In some cases though,
/// like when saving the email as an `.eml`, or sending through
/// some transports (like the Gmail API) that don't take a separate
/// envelope value, it becomes necessary to keep the `Bcc` header.
///
/// Calling this method overrides the default behaviour.
pub fn keep_bcc(mut self) -> Self {
self.drop_bcc = false;
self
}
// TODO: High-level methods for attachments and embedded files
/// Create message from body
@@ -423,8 +430,10 @@ impl MessageBuilder {
None => Envelope::try_from(&res.headers)?,
};
// Remove `Bcc` headers now the envelope is set
res.headers.remove::<header::Bcc>();
if res.drop_bcc {
// Remove `Bcc` headers now the envelope is set
res.headers.remove::<header::Bcc>();
}
Ok(Message {
headers: res.headers,
@@ -455,6 +464,15 @@ impl MessageBuilder {
pub fn singlepart(self, part: SinglePart) -> Result<Message, EmailError> {
self.mime_1_0().build(MessageBody::Mime(Part::Single(part)))
}
/// Set `MIME-Version` header to 1.0
///
/// Shortcut for `self.header(header::MIME_VERSION_1_0)`.
///
/// Not exposed as it is set by body methods
fn mime_1_0(self) -> Self {
self.header(header::MIME_VERSION_1_0)
}
}
/// Email message which can be formatted
@@ -628,7 +646,7 @@ mod test {
}
#[test]
fn email_message() {
fn email_message_no_bcc() {
// Tue, 15 Nov 1994 08:12:31 GMT
let date = SystemTime::UNIX_EPOCH + Duration::from_secs(784887151);
@@ -663,6 +681,44 @@ mod test {
);
}
#[test]
fn email_message_keep_bcc() {
// Tue, 15 Nov 1994 08:12:31 GMT
let date = SystemTime::UNIX_EPOCH + Duration::from_secs(784887151);
let email = Message::builder()
.date(date)
.bcc("hidden@example.com".parse().unwrap())
.keep_bcc()
.header(header::From(
vec![Mailbox::new(
Some("Каи".into()),
"kayo@example.com".parse().unwrap(),
)]
.into(),
))
.header(header::To(
vec!["Pony O.P. <pony@domain.tld>".parse().unwrap()].into(),
))
.header(header::Subject::from(String::from("яңа ел белән!")))
.body(String::from("Happy new year!"))
.unwrap();
assert_eq!(
String::from_utf8(email.formatted()).unwrap(),
concat!(
"Date: Tue, 15 Nov 1994 08:12:31 +0000\r\n",
"Bcc: hidden@example.com\r\n",
"From: =?utf-8?b?0JrQsNC4?= <kayo@example.com>\r\n",
"To: \"Pony O.P.\" <pony@domain.tld>\r\n",
"Subject: =?utf-8?b?0Y/So9CwINC10Lsg0LHQtdC705nQvQ==?=!\r\n",
"Content-Transfer-Encoding: 7bit\r\n",
"\r\n",
"Happy new year!"
)
);
}
#[test]
fn email_with_png() {
// Tue, 15 Nov 1994 08:12:31 GMT

View File

@@ -198,6 +198,9 @@ where
{
fn clone(&self) -> Self {
Self {
#[cfg(feature = "pool")]
inner: Arc::clone(&self.inner),
#[cfg(not(feature = "pool"))]
inner: self.inner.clone(),
}
}

View File

@@ -98,12 +98,12 @@ impl Mechanism {
let decoded_challenge = challenge
.ok_or_else(|| error::client("This mechanism does expect a challenge"))?;
if vec!["User Name", "Username:", "Username"].contains(&decoded_challenge) {
return Ok(credentials.authentication_identity.to_string());
if ["User Name", "Username:", "Username"].contains(&decoded_challenge) {
return Ok(credentials.authentication_identity.clone());
}
if vec!["Password", "Password:"].contains(&decoded_challenge) {
return Ok(credentials.secret.to_string());
if ["Password", "Password:"].contains(&decoded_challenge) {
return Ok(credentials.secret.clone());
}
Err(error::client("Unrecognized challenge"))

View File

@@ -8,7 +8,7 @@ use super::{AsyncNetworkStream, ClientCodec, TlsParameters};
use crate::{
transport::smtp::{
authentication::{Credentials, Mechanism},
commands::*,
commands::{Auth, Data, Ehlo, Mail, Noop, Quit, Rcpt, Starttls},
error,
error::Error,
extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo},
@@ -41,6 +41,7 @@ pub struct AsyncSmtpConnection {
}
impl AsyncSmtpConnection {
/// Get information about the server
pub fn server_info(&self) -> &ServerInfo {
&self.server_info
}

View File

@@ -12,7 +12,7 @@ use crate::{
address::Envelope,
transport::smtp::{
authentication::{Credentials, Mechanism},
commands::*,
commands::{Auth, Data, Ehlo, Mail, Noop, Quit, Rcpt, Starttls},
error,
error::Error,
extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo},
@@ -44,6 +44,7 @@ pub struct SmtpConnection {
}
impl SmtpConnection {
/// Get information about the server
pub fn server_info(&self) -> &ServerInfo {
&self.server_info
}

View File

@@ -2,6 +2,7 @@ use std::{
io::{self, Read, Write},
mem,
net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream, ToSocketAddrs},
sync::Arc,
time::Duration,
};
@@ -175,8 +176,8 @@ impl NetworkStream {
InnerTlsParameters::RustlsTls(connector) => {
let domain = ServerName::try_from(tls_parameters.domain())
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
let connection =
ClientConnection::new(connector.clone(), domain).map_err(error::connection)?;
let connection = ClientConnection::new(Arc::clone(connector), domain)
.map_err(error::connection)?;
let stream = StreamOwned::new(connection, tcp_stream);
InnerNetworkStream::RustlsTls(stream)
}

View File

@@ -219,6 +219,7 @@ impl TlsParameters {
TlsParametersBuilder::new(domain).build()
}
/// Creates a new `TlsParameters` builder
pub fn builder(domain: String) -> TlsParametersBuilder {
TlsParametersBuilder::new(domain)
}

View File

@@ -13,7 +13,7 @@ use crate::{
};
/// EHLO command
#[derive(PartialEq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ehlo {
client_id: ClientId,
@@ -33,7 +33,7 @@ impl Ehlo {
}
/// STARTTLS command
#[derive(PartialEq, Clone, Debug, Copy)]
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Starttls;
@@ -44,7 +44,7 @@ impl Display for Starttls {
}
/// MAIL command
#[derive(PartialEq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Mail {
sender: Option<Address>,
@@ -73,7 +73,7 @@ impl Mail {
}
/// RCPT command
#[derive(PartialEq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rcpt {
recipient: Address,
@@ -101,7 +101,7 @@ impl Rcpt {
}
/// DATA command
#[derive(PartialEq, Clone, Debug, Copy)]
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Data;
@@ -112,7 +112,7 @@ impl Display for Data {
}
/// QUIT command
#[derive(PartialEq, Clone, Debug, Copy)]
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Quit;
@@ -123,7 +123,7 @@ impl Display for Quit {
}
/// NOOP command
#[derive(PartialEq, Clone, Debug, Copy)]
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Noop;
@@ -134,7 +134,7 @@ impl Display for Noop {
}
/// HELP command
#[derive(PartialEq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Help {
argument: Option<String>,
@@ -158,7 +158,7 @@ impl Help {
}
/// VRFY command
#[derive(PartialEq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Vrfy {
argument: String,
@@ -178,7 +178,7 @@ impl Vrfy {
}
/// EXPN command
#[derive(PartialEq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Expn {
argument: String,
@@ -198,7 +198,7 @@ impl Expn {
}
/// RSET command
#[derive(PartialEq, Clone, Debug, Copy)]
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rset;
@@ -209,7 +209,7 @@ impl Display for Rset {
}
/// AUTH command
#[derive(PartialEq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Auth {
mechanism: Mechanism,

View File

@@ -158,14 +158,14 @@ impl<E: Executor> Pool<E> {
#[cfg(feature = "tracing")]
tracing::debug!("reusing a pooled connection");
return Ok(PooledConnection::wrap(conn, self.clone()));
return Ok(PooledConnection::wrap(conn, Arc::clone(self)));
}
None => {
#[cfg(feature = "tracing")]
tracing::debug!("creating a new connection");
let conn = self.client.connection().await?;
return Ok(PooledConnection::wrap(conn, self.clone()));
return Ok(PooledConnection::wrap(conn, Arc::clone(self)));
}
}
}

View File

@@ -141,14 +141,14 @@ impl Pool {
#[cfg(feature = "tracing")]
tracing::debug!("reusing a pooled connection");
return Ok(PooledConnection::wrap(conn, self.clone()));
return Ok(PooledConnection::wrap(conn, Arc::clone(self)));
}
None => {
#[cfg(feature = "tracing")]
tracing::debug!("creating a new connection");
let conn = self.client.connection()?;
return Ok(PooledConnection::wrap(conn, self.clone()));
return Ok(PooledConnection::wrap(conn, Arc::clone(self)));
}
}
}

View File

@@ -41,7 +41,7 @@ mod tests {
]
.iter()
{
assert_eq!(format!("{}", XText(input)), expect.to_string());
assert_eq!(format!("{}", XText(input)), (*expect).to_string());
}
}
}

View File

@@ -53,6 +53,8 @@ use futures_util::lock::Mutex as FuturesMutex;
use crate::AsyncTransport;
use crate::{address::Envelope, Transport};
/// An error returned by the stub transport
#[non_exhaustive]
#[derive(Debug, Copy, Clone)]
pub struct Error;

View File

@@ -18,7 +18,7 @@ mod sync {
sender_ok.send(&email).unwrap();
sender_ko.send(&email).unwrap_err();
let expected_messages = vec![(
let expected_messages = [(
email.envelope().clone(),
String::from_utf8(email.formatted()).unwrap(),
)];
@@ -47,7 +47,7 @@ mod tokio_1 {
sender_ok.send(email.clone()).await.unwrap();
sender_ko.send(email.clone()).await.unwrap_err();
let expected_messages = vec![(
let expected_messages = [(
email.envelope().clone(),
String::from_utf8(email.formatted()).unwrap(),
)];
@@ -75,7 +75,7 @@ mod asyncstd_1 {
sender_ok.send(email.clone()).await.unwrap();
sender_ko.send(email.clone()).await.unwrap_err();
let expected_messages = vec![(
let expected_messages = [(
email.envelope().clone(),
String::from_utf8(email.formatted()).unwrap(),
)];