Compare commits

..

6 Commits

Author SHA1 Message Date
Paolo Barbolini
c7d1f35676 Prepare 0.11.6 (#955) 2024-03-28 16:19:10 +01:00
Paolo Barbolini
eebea56f16 Upgrade email-encoding to v0.3 (#952) 2024-03-28 15:22:55 +01:00
Paolo Barbolini
851d6ae164 Bump license year (#954) 2024-03-28 14:50:53 +01:00
Paolo Barbolini
6f38e6b9a9 Fix latest clippy warnings (#953) 2024-03-28 05:12:13 +01:00
Paolo Barbolini
c40af78809 Prepare 0.11.5 (#951) 2024-03-25 17:59:47 +01:00
Paolo Barbolini
6d2e0d5046 Bump rustls to v0.23 (#950) 2024-03-22 20:09:27 +01:00
22 changed files with 426 additions and 330 deletions

View File

@@ -1,3 +1,40 @@
<a name="v0.11.6"></a>
### v0.11.6 (2024-03-28)
#### Bug fixes
* Upgraded `email-encoding` to v0.3 - fixing multiple encoding bugs in the process ([#952])
#### Misc
* Updated copyright year in license ([#954])
[#952]: https://github.com/lettre/lettre/pull/952
[#954]: https://github.com/lettre/lettre/pull/954
<a name="v0.11.5"></a>
### v0.11.5 (2024-03-25)
#### Features
* Support SMTP SASL draft login challenge ([#911])
* Add conversion from SMTP response code to integer ([#941])
#### Misc
* Upgrade `rustls` to v0.23 ([#950])
* Bump `base64` to v0.22 ([#945])
* Fix typos in documentation ([#943], [#944])
* Add `Cargo.lock` ([#942])
[#911]: https://github.com/lettre/lettre/pull/911
[#941]: https://github.com/lettre/lettre/pull/941
[#942]: https://github.com/lettre/lettre/pull/942
[#943]: https://github.com/lettre/lettre/pull/943
[#944]: https://github.com/lettre/lettre/pull/944
[#945]: https://github.com/lettre/lettre/pull/945
[#950]: https://github.com/lettre/lettre/pull/950
<a name="v0.11.4"></a> <a name="v0.11.4"></a>
### v0.11.4 (2024-01-28) ### v0.11.4 (2024-01-28)

21
Cargo.lock generated
View File

@@ -720,11 +720,11 @@ checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]] [[package]]
name = "email-encoding" name = "email-encoding"
version = "0.2.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75" checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.22.0",
"memchr", "memchr",
] ]
@@ -942,9 +942,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-rustls" name = "futures-rustls"
version = "0.25.1" version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d8a2499f0fecc0492eb3e47eab4e92da7875e1028ad2528f214ac3346ca04e" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb"
dependencies = [ dependencies = [
"futures-io", "futures-io",
"rustls", "rustls",
@@ -1151,7 +1151,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "lettre" name = "lettre"
version = "0.11.4" version = "0.11.5"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-trait", "async-trait",
@@ -1847,11 +1847,12 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.22.2" version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" checksum = "1b3818d6051afeb6f88412bc8693cf8219799b2f2c2365f15e7534f0e198a16c"
dependencies = [ dependencies = [
"log", "log",
"once_cell",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki", "rustls-webpki",
@@ -2226,9 +2227,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.25.0" version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [ dependencies = [
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "lettre" name = "lettre"
# remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge) # remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge)
version = "0.11.4" version = "0.11.6"
description = "Email client" description = "Email client"
readme = "README.md" readme = "README.md"
homepage = "https://lettre.rs" homepage = "https://lettre.rs"
@@ -29,7 +29,7 @@ mime = { version = "0.3.4", optional = true }
fastrand = { version = "2.0", optional = true } fastrand = { version = "2.0", optional = true }
quoted_printable = { version = "0.5", optional = true } quoted_printable = { version = "0.5", optional = true }
base64 = { version = "0.22", optional = true } base64 = { version = "0.22", optional = true }
email-encoding = { version = "0.2", optional = true } email-encoding = { version = "0.3", optional = true }
# file transport # file transport
uuid = { version = "1", features = ["v4"], optional = true } uuid = { version = "1", features = ["v4"], optional = true }
@@ -45,7 +45,7 @@ percent-encoding = { version = "2.3", optional = true }
## tls ## tls
native-tls = { version = "0.2.5", optional = true } # feature native-tls = { version = "0.2.5", optional = true } # feature
rustls = { version = "0.22.1", optional = true } rustls = { version = "0.23.3", default-features = false, features = ["ring", "logging", "std", "tls12"], optional = true }
rustls-pemfile = { version = "2", optional = true } rustls-pemfile = { version = "2", optional = true }
rustls-native-certs = { version = "0.7", optional = true } rustls-native-certs = { version = "0.7", optional = true }
webpki-roots = { version = "0.26", optional = true } webpki-roots = { version = "0.26", optional = true }
@@ -59,12 +59,12 @@ async-trait = { version = "0.1", optional = true }
## async-std ## async-std
async-std = { version = "1.8", optional = true } async-std = { version = "1.8", optional = true }
#async-native-tls = { version = "0.3.3", optional = true } #async-native-tls = { version = "0.3.3", optional = true }
futures-rustls = { version = "0.25", optional = true } futures-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"], optional = true }
## tokio ## tokio
tokio1_crate = { package = "tokio", version = "1", optional = true } tokio1_crate = { package = "tokio", version = "1", optional = true }
tokio1_native_tls_crate = { package = "tokio-native-tls", version = "0.3", optional = true } tokio1_native_tls_crate = { package = "tokio-native-tls", version = "0.3", optional = true }
tokio1_rustls = { package = "tokio-rustls", version = "0.25", optional = true } tokio1_rustls = { package = "tokio-rustls", version = "0.26", default-features = false, features = ["logging", "tls12", "ring"], optional = true }
tokio1_boring = { package = "tokio-boring", version = "4", optional = true } tokio1_boring = { package = "tokio-boring", version = "4", optional = true }
## dkim ## dkim

View File

@@ -1,5 +1,5 @@
Copyright (c) 2014-2022 Alexis Mousset <contact@amousset.me> Copyright (c) 2014-2024 Alexis Mousset <contact@amousset.me>
Copyright (c) 2019-2022 Paolo Barbolini <paolo@paolo565.org> Copyright (c) 2019-2024 Paolo Barbolini <paolo@paolo565.org>
Copyright (c) 2018 K. <kayo@illumium.org> Copyright (c) 2018 K. <kayo@illumium.org>
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any

View File

@@ -28,8 +28,8 @@
</div> </div>
<div align="center"> <div align="center">
<a href="https://deps.rs/crate/lettre/0.11.4"> <a href="https://deps.rs/crate/lettre/0.11.6">
<img src="https://deps.rs/crate/lettre/0.11.4/status.svg" <img src="https://deps.rs/crate/lettre/0.11.6/status.svg"
alt="dependency status" /> alt="dependency status" />
</a> </a>
</div> </div>

View File

@@ -134,7 +134,7 @@ impl Executor for Tokio1Executor {
#[allow(clippy::match_single_binding)] #[allow(clippy::match_single_binding)]
let tls_parameters = match tls { let tls_parameters = match tls {
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))] #[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
Tls::Wrapper(tls_parameters) => Some(tls_parameters.clone()), Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
_ => None, _ => None,
}; };
#[allow(unused_mut)] #[allow(unused_mut)]
@@ -149,13 +149,13 @@ impl Executor for Tokio1Executor {
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))] #[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
match tls { match tls {
Tls::Opportunistic(tls_parameters) => { Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() { if conn.can_starttls() {
conn = conn.starttls(tls_parameters.clone(), hello_name).await?; conn.starttls(tls_parameters.clone(), hello_name).await?;
} }
} }
Tls::Required(tls_parameters) => { Tls::Required(ref tls_parameters) => {
conn = conn.starttls(tls_parameters.clone(), hello_name).await?; conn.starttls(tls_parameters.clone(), hello_name).await?;
} }
_ => (), _ => (),
} }
@@ -231,7 +231,7 @@ impl Executor for AsyncStd1Executor {
#[allow(clippy::match_single_binding)] #[allow(clippy::match_single_binding)]
let tls_parameters = match tls { let tls_parameters = match tls {
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))] #[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
Tls::Wrapper(tls_parameters) => Some(tls_parameters.clone()), Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
_ => None, _ => None,
}; };
#[allow(unused_mut)] #[allow(unused_mut)]
@@ -245,13 +245,13 @@ impl Executor for AsyncStd1Executor {
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))] #[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
match tls { match tls {
Tls::Opportunistic(tls_parameters) => { Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() { if conn.can_starttls() {
conn = conn.starttls(tls_parameters.clone(), hello_name).await?; conn.starttls(tls_parameters.clone(), hello_name).await?;
} }
} }
Tls::Required(tls_parameters) => { Tls::Required(ref tls_parameters) => {
conn = conn.starttls(tls_parameters.clone(), hello_name).await?; conn.starttls(tls_parameters.clone(), hello_name).await?;
} }
_ => (), _ => (),
} }

View File

@@ -109,7 +109,7 @@
//! [mime 0.3]: https://docs.rs/mime/0.3 //! [mime 0.3]: https://docs.rs/mime/0.3
//! [DKIM]: https://datatracker.ietf.org/doc/html/rfc6376 //! [DKIM]: https://datatracker.ietf.org/doc/html/rfc6376
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.4")] #![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.6")]
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")] #![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")] #![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]

View File

@@ -1,6 +1,6 @@
use std::fmt::Write; use std::fmt::Write;
use email_encoding::headers::EmailWriter; use email_encoding::headers::writer::EmailWriter;
use super::{Header, HeaderName, HeaderValue}; use super::{Header, HeaderName, HeaderValue};
use crate::BoxError; use crate::BoxError;
@@ -38,10 +38,10 @@ impl ContentDisposition {
let mut encoded_value = String::new(); let mut encoded_value = String::new();
let line_len = "Content-Disposition: ".len(); let line_len = "Content-Disposition: ".len();
{ {
let mut w = EmailWriter::new(&mut encoded_value, line_len, 0, false, false); let mut w = EmailWriter::new(&mut encoded_value, line_len, 0, false);
w.write_str(kind).expect("writing `kind` returned an error"); w.write_str(kind).expect("writing `kind` returned an error");
w.write_char(';').expect("writing `;` returned an error"); w.write_char(';').expect("writing `;` returned an error");
w.optional_breakpoint(); w.space();
email_encoding::headers::rfc2231::encode("filename", file_name, &mut w) email_encoding::headers::rfc2231::encode("filename", file_name, &mut w)
.expect("some Write implementation returned an error"); .expect("some Write implementation returned an error");

View File

@@ -1,4 +1,4 @@
use email_encoding::headers::EmailWriter; use email_encoding::headers::writer::EmailWriter;
use super::{Header, HeaderName, HeaderValue}; use super::{Header, HeaderName, HeaderValue};
use crate::{ use crate::{
@@ -31,7 +31,7 @@ macro_rules! mailbox_header {
let mut encoded_value = String::new(); let mut encoded_value = String::new();
let line_len = $header_name.len() + ": ".len(); let line_len = $header_name.len() + ": ".len();
{ {
let mut w = EmailWriter::new(&mut encoded_value, line_len, 0, false, false); let mut w = EmailWriter::new(&mut encoded_value, line_len, 0, false);
self.0.encode(&mut w).expect("writing `Mailbox` returned an error"); self.0.encode(&mut w).expect("writing `Mailbox` returned an error");
} }
@@ -81,7 +81,7 @@ macro_rules! mailboxes_header {
let mut encoded_value = String::new(); let mut encoded_value = String::new();
let line_len = $header_name.len() + ": ".len(); let line_len = $header_name.len() + ": ".len();
{ {
let mut w = EmailWriter::new(&mut encoded_value, line_len, 0, false, false); let mut w = EmailWriter::new(&mut encoded_value, line_len, 0, false);
self.0.encode(&mut w).expect("writing `Mailboxes` returned an error"); self.0.encode(&mut w).expect("writing `Mailboxes` returned an error");
} }

View File

@@ -7,7 +7,7 @@ use std::{
ops::Deref, ops::Deref,
}; };
use email_encoding::headers::EmailWriter; use email_encoding::headers::writer::EmailWriter;
pub use self::{ pub use self::{
content::*, content::*,
@@ -348,7 +348,7 @@ impl<'a> HeaderValueEncoder<'a> {
fn new(name: &str, writer: &'a mut dyn Write) -> Self { fn new(name: &str, writer: &'a mut dyn Write) -> Self {
let line_len = name.len() + ": ".len(); let line_len = name.len() + ": ".len();
let writer = EmailWriter::new(writer, line_len, 0, false, false); let writer = EmailWriter::new(writer, line_len, 0, false);
Self { Self {
writer, writer,
@@ -612,17 +612,14 @@ mod tests {
"🌍 <world@example.com>, 🦆 Everywhere <ducks@example.com>, Иванов Иван Иванович <ivanov@example.com>, Jānis Bērziņš <janis@example.com>, Seán Ó Rudaí <sean@example.com>".to_owned(), "🌍 <world@example.com>, 🦆 Everywhere <ducks@example.com>, Иванов Иван Иванович <ivanov@example.com>, Jānis Bērziņš <janis@example.com>, Seán Ó Rudaí <sean@example.com>".to_owned(),
)); ));
// TODO: fix the fact that the encoder doesn't know that
// the space between the name and the address should be
// removed when wrapping.
assert_eq!( assert_eq!(
headers.to_string(), headers.to_string(),
concat!( concat!(
"To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?=\r\n", "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?=\r\n",
" Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LLQsNC9?=\r\n", " Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LLQsNC9?=\r\n",
" =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n", " =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n",
" =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>,\r\n", " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
" =?utf-8?b?U2XDoW4gw5MgUnVkYcOt?= <sean@example.com>\r\n", " =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
) )
); );
} }
@@ -687,9 +684,6 @@ mod tests {
"quoted-printable".to_owned(), "quoted-printable".to_owned(),
)); ));
// TODO: fix the fact that the encoder doesn't know that
// the space between the name and the address should be
// removed when wrapping.
assert_eq!( assert_eq!(
headers.to_string(), headers.to_string(),
concat!( concat!(
@@ -699,8 +693,8 @@ mod tests {
"To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?=\r\n", "To: =?utf-8?b?8J+MjQ==?= <world@example.com>, =?utf-8?b?8J+mhg==?=\r\n",
" Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LLQsNC9?=\r\n", " Everywhere <ducks@example.com>, =?utf-8?b?0JjQstCw0L3QvtCyINCY0LLQsNC9?=\r\n",
" =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n", " =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n",
" =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>,\r\n", " =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
" =?utf-8?b?U2XDoW4gw5MgUnVkYcOt?= <sean@example.com>\r\n", " =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
"From: Someone <somewhere@example.com>\r\n", "From: Someone <somewhere@example.com>\r\n",
"Content-Transfer-Encoding: quoted-printable\r\n", "Content-Transfer-Encoding: quoted-printable\r\n",
) )

View File

@@ -6,7 +6,7 @@ use std::{
}; };
use chumsky::prelude::*; use chumsky::prelude::*;
use email_encoding::headers::EmailWriter; use email_encoding::headers::writer::EmailWriter;
use super::parsers; use super::parsers;
use crate::address::{Address, AddressError}; use crate::address::{Address, AddressError};
@@ -72,7 +72,7 @@ impl Mailbox {
pub(crate) fn encode(&self, w: &mut EmailWriter<'_>) -> FmtResult { pub(crate) fn encode(&self, w: &mut EmailWriter<'_>) -> FmtResult {
if let Some(name) = &self.name { if let Some(name) = &self.name {
email_encoding::headers::quoted_string::encode(name, w)?; email_encoding::headers::quoted_string::encode(name, w)?;
w.optional_breakpoint(); w.space();
w.write_char('<')?; w.write_char('<')?;
} }
@@ -88,7 +88,7 @@ impl Mailbox {
impl Display for Mailbox { impl Display for Mailbox {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if let Some(name) = &self.name { if let Some(ref name) = self.name {
let name = name.trim(); let name = name.trim();
if !name.is_empty() { if !name.is_empty() {
write_word(f, name)?; write_word(f, name)?;
@@ -261,7 +261,7 @@ impl Mailboxes {
for mailbox in self.iter() { for mailbox in self.iter() {
if !mem::take(&mut first) { if !mem::take(&mut first) {
w.write_char(',')?; w.write_char(',')?;
w.optional_breakpoint(); w.space();
} }
mailbox.encode(w)?; mailbox.encode(w)?;
@@ -444,8 +444,6 @@ fn write_quoted_string_char(f: &mut Formatter<'_>, c: char) -> FmtResult {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::convert::TryInto;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::Mailbox; use super::Mailbox;

View File

@@ -420,7 +420,6 @@ mod test {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::*; use super::*;
use crate::message::header;
#[test] #[test]
fn single_part_binary() { fn single_part_binary() {

View File

@@ -54,7 +54,7 @@ impl fmt::Debug for Error {
builder.field("kind", &self.inner.kind); builder.field("kind", &self.inner.kind);
if let Some(source) = &self.inner.source { if let Some(ref source) = self.inner.source {
builder.field("source", source); builder.field("source", source);
} }
@@ -70,7 +70,7 @@ impl fmt::Display for Error {
Kind::Envelope => f.write_str("internal client error")?, Kind::Envelope => f.write_str("internal client error")?,
}; };
if let Some(e) = &self.inner.source { if let Some(ref e) = self.inner.source {
write!(f, ": {e}")?; write!(f, ": {e}")?;
} }

View File

@@ -52,7 +52,7 @@ impl fmt::Debug for Error {
builder.field("kind", &self.inner.kind); builder.field("kind", &self.inner.kind);
if let Some(source) = &self.inner.source { if let Some(ref source) = self.inner.source {
builder.field("source", source); builder.field("source", source);
} }
@@ -67,7 +67,7 @@ impl fmt::Display for Error {
Kind::Client => f.write_str("internal client error")?, Kind::Client => f.write_str("internal client error")?,
}; };
if let Some(e) = &self.inner.source { if let Some(ref e) = self.inner.source {
write!(f, ": {e}")?; write!(f, ": {e}")?;
} }

View File

@@ -6,7 +6,7 @@ use futures_util::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use super::async_net::AsyncTokioStream; use super::async_net::AsyncTokioStream;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use super::escape_crlf; use super::escape_crlf;
use super::{AsyncNetworkStream, ClientCodec, ConnectionState, TlsParameters}; use super::{AsyncNetworkStream, ClientCodec, TlsParameters};
use crate::{ use crate::{
transport::smtp::{ transport::smtp::{
authentication::{Credentials, Mechanism}, authentication::{Credentials, Mechanism},
@@ -19,11 +19,25 @@ use crate::{
Envelope, Envelope,
}; };
macro_rules! try_smtp (
($err: expr, $client: ident) => ({
match $err {
Ok(val) => val,
Err(err) => {
$client.abort().await;
return Err(From::from(err))
},
}
})
);
/// Structure that implements the SMTP client /// Structure that implements the SMTP client
pub struct AsyncSmtpConnection { pub struct AsyncSmtpConnection {
/// TCP stream between client and server /// TCP stream between client and server
/// Value is None before connection /// Value is None before connection
stream: BufReader<AsyncNetworkStream>, stream: BufReader<AsyncNetworkStream>,
/// Panic state
panic: bool,
/// Information about the server /// Information about the server
server_info: ServerInfo, server_info: ServerInfo,
} }
@@ -112,6 +126,7 @@ impl AsyncSmtpConnection {
let stream = BufReader::new(stream); let stream = BufReader::new(stream);
let mut conn = AsyncSmtpConnection { let mut conn = AsyncSmtpConnection {
stream, stream,
panic: false,
server_info: ServerInfo::default(), server_info: ServerInfo::default(),
}; };
// TODO log // TODO log
@@ -155,26 +170,30 @@ impl AsyncSmtpConnection {
mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime)); mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime));
} }
self.command(Mail::new(envelope.from().cloned(), mail_options)) try_smtp!(
.await?; self.command(Mail::new(envelope.from().cloned(), mail_options))
.await,
self
);
// Recipient // Recipient
for to_address in envelope.to() { for to_address in envelope.to() {
self.command(Rcpt::new(to_address.clone(), vec![])).await?; try_smtp!(
self.command(Rcpt::new(to_address.clone(), vec![])).await,
self
);
} }
// Data // Data
self.command(Data).await?; try_smtp!(self.command(Data).await, self);
// Message content // Message content
self.message(email).await let result = try_smtp!(self.message(email).await, self);
Ok(result)
} }
pub fn has_broken(&self) -> bool { pub fn has_broken(&self) -> bool {
match self.stream.get_ref().state() { self.panic
ConnectionState::Ok => false,
ConnectionState::Broken | ConnectionState::Closed => true,
}
} }
pub fn can_starttls(&self) -> bool { pub fn can_starttls(&self) -> bool {
@@ -189,20 +208,18 @@ impl AsyncSmtpConnection {
/// [rfc8314]: https://www.rfc-editor.org/rfc/rfc8314 /// [rfc8314]: https://www.rfc-editor.org/rfc/rfc8314
#[allow(unused_variables)] #[allow(unused_variables)]
pub async fn starttls( pub async fn starttls(
mut self, &mut self,
tls_parameters: TlsParameters, tls_parameters: TlsParameters,
hello_name: &ClientId, hello_name: &ClientId,
) -> Result<Self, Error> { ) -> Result<(), Error> {
if self.server_info.supports_feature(Extension::StartTls) { if self.server_info.supports_feature(Extension::StartTls) {
self.command(Starttls).await?; try_smtp!(self.command(Starttls).await, self);
let stream = self.stream.into_inner(); self.stream.get_mut().upgrade_tls(tls_parameters).await?;
let stream = stream.upgrade_tls(tls_parameters).await?;
self.stream = BufReader::new(stream);
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
tracing::debug!("connection encrypted"); tracing::debug!("connection encrypted");
// Send EHLO again // Send EHLO again
self.ehlo(hello_name).await?; try_smtp!(self.ehlo(hello_name).await, self);
Ok(self) Ok(())
} else { } else {
Err(error::client("STARTTLS is not supported on this server")) Err(error::client("STARTTLS is not supported on this server"))
} }
@@ -210,24 +227,22 @@ impl AsyncSmtpConnection {
/// Send EHLO and update server info /// Send EHLO and update server info
async fn ehlo(&mut self, hello_name: &ClientId) -> Result<(), Error> { async fn ehlo(&mut self, hello_name: &ClientId) -> Result<(), Error> {
let ehlo_response = self.command(Ehlo::new(hello_name.clone())).await?; let ehlo_response = try_smtp!(self.command(Ehlo::new(hello_name.clone())).await, self);
self.server_info = ServerInfo::from_response(&ehlo_response)?; self.server_info = try_smtp!(ServerInfo::from_response(&ehlo_response), self);
Ok(()) Ok(())
} }
pub async fn quit(&mut self) -> Result<Response, Error> { pub async fn quit(&mut self) -> Result<Response, Error> {
self.command(Quit).await Ok(try_smtp!(self.command(Quit).await, self))
} }
pub async fn abort(&mut self) { pub async fn abort(&mut self) {
match self.stream.get_ref().state() { // Only try to quit if we are not already broken
ConnectionState::Ok | ConnectionState::Broken => { if !self.panic {
let _ = self.command(Quit).await; self.panic = true;
let _ = self.stream.close().await; let _ = self.command(Quit).await;
self.stream.get_mut().set_state(ConnectionState::Closed);
}
ConnectionState::Closed => {}
} }
let _ = self.stream.close().await;
} }
/// Sets the underlying stream /// Sets the underlying stream
@@ -264,13 +279,15 @@ impl AsyncSmtpConnection {
while challenges > 0 && response.has_code(334) { while challenges > 0 && response.has_code(334) {
challenges -= 1; challenges -= 1;
response = self response = try_smtp!(
.command(Auth::new_from_response( self.command(Auth::new_from_response(
mechanism, mechanism,
credentials.clone(), credentials.clone(),
&response, &response,
)?) )?)
.await?; .await,
self
);
} }
if challenges == 0 { if challenges == 0 {
@@ -298,9 +315,6 @@ impl AsyncSmtpConnection {
/// Writes a string to the server /// Writes a string to the server
async fn write(&mut self, string: &[u8]) -> Result<(), Error> { async fn write(&mut self, string: &[u8]) -> Result<(), Error> {
self.stream.get_ref().state().verify()?;
self.stream.get_mut().set_state(ConnectionState::Broken);
self.stream self.stream
.get_mut() .get_mut()
.write_all(string) .write_all(string)
@@ -312,8 +326,6 @@ impl AsyncSmtpConnection {
.await .await
.map_err(error::network)?; .map_err(error::network)?;
self.stream.get_mut().set_state(ConnectionState::Ok);
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
tracing::debug!("Wrote: {}", escape_crlf(&String::from_utf8_lossy(string))); tracing::debug!("Wrote: {}", escape_crlf(&String::from_utf8_lossy(string)));
Ok(()) Ok(())
@@ -321,9 +333,6 @@ impl AsyncSmtpConnection {
/// Gets the SMTP response /// Gets the SMTP response
pub async fn read_response(&mut self) -> Result<Response, Error> { pub async fn read_response(&mut self) -> Result<Response, Error> {
self.stream.get_ref().state().verify()?;
self.stream.get_mut().set_state(ConnectionState::Broken);
let mut buffer = String::with_capacity(100); let mut buffer = String::with_capacity(100);
while self while self
@@ -337,8 +346,6 @@ impl AsyncSmtpConnection {
tracing::debug!("<< {}", escape_crlf(&buffer)); tracing::debug!("<< {}", escape_crlf(&buffer));
match parse_response(&buffer) { match parse_response(&buffer) {
Ok((_remaining, response)) => { Ok((_remaining, response)) => {
self.stream.get_mut().set_state(ConnectionState::Ok);
return if response.is_positive() { return if response.is_positive() {
Ok(response) Ok(response)
} else { } else {
@@ -346,7 +353,7 @@ impl AsyncSmtpConnection {
response.code(), response.code(),
Some(response.message().collect()), Some(response.message().collect()),
)) ))
}; }
} }
Err(nom::Err::Failure(e)) => { Err(nom::Err::Failure(e)) => {
return Err(error::response(e.to_string())); return Err(error::response(e.to_string()));

View File

@@ -11,7 +11,8 @@ use async_native_tls::TlsStream as AsyncStd1TlsStream;
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
use async_std::net::{TcpStream as AsyncStd1TcpStream, ToSocketAddrs as AsyncStd1ToSocketAddrs}; use async_std::net::{TcpStream as AsyncStd1TcpStream, ToSocketAddrs as AsyncStd1ToSocketAddrs};
use futures_io::{ use futures_io::{
AsyncRead as FuturesAsyncRead, AsyncWrite as FuturesAsyncWrite, Result as IoResult, AsyncRead as FuturesAsyncRead, AsyncWrite as FuturesAsyncWrite, Error as IoError, ErrorKind,
Result as IoResult,
}; };
#[cfg(feature = "async-std1-rustls-tls")] #[cfg(feature = "async-std1-rustls-tls")]
use futures_rustls::client::TlsStream as AsyncStd1RustlsTlsStream; use futures_rustls::client::TlsStream as AsyncStd1RustlsTlsStream;
@@ -39,7 +40,7 @@ use tokio1_rustls::client::TlsStream as Tokio1RustlsTlsStream;
feature = "async-std1-rustls-tls" feature = "async-std1-rustls-tls"
))] ))]
use super::InnerTlsParameters; use super::InnerTlsParameters;
use super::{ConnectionState, TlsParameters}; use super::TlsParameters;
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
use crate::transport::smtp::client::net::resolved_address_filter; use crate::transport::smtp::client::net::resolved_address_filter;
use crate::transport::smtp::{error, Error}; use crate::transport::smtp::{error, Error};
@@ -48,7 +49,6 @@ use crate::transport::smtp::{error, Error};
#[derive(Debug)] #[derive(Debug)]
pub struct AsyncNetworkStream { pub struct AsyncNetworkStream {
inner: InnerAsyncNetworkStream, inner: InnerAsyncNetworkStream,
state: ConnectionState,
} }
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
@@ -91,43 +91,45 @@ enum InnerAsyncNetworkStream {
/// Encrypted Tokio 1.x TCP stream /// Encrypted Tokio 1.x TCP stream
#[cfg(feature = "async-std1-rustls-tls")] #[cfg(feature = "async-std1-rustls-tls")]
AsyncStd1RustlsTls(AsyncStd1RustlsTlsStream<AsyncStd1TcpStream>), AsyncStd1RustlsTls(AsyncStd1RustlsTlsStream<AsyncStd1TcpStream>),
/// Can't be built
None,
} }
impl AsyncNetworkStream { impl AsyncNetworkStream {
fn new(inner: InnerAsyncNetworkStream) -> Self { fn new(inner: InnerAsyncNetworkStream) -> Self {
AsyncNetworkStream { if let InnerAsyncNetworkStream::None = inner {
inner, debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
state: ConnectionState::Ok,
} }
}
pub(super) fn state(&self) -> ConnectionState { AsyncNetworkStream { inner }
self.state
}
pub(super) fn set_state(&mut self, state: ConnectionState) {
self.state = state;
} }
/// Returns peer's address /// Returns peer's address
pub fn peer_addr(&self) -> IoResult<SocketAddr> { pub fn peer_addr(&self) -> IoResult<SocketAddr> {
match &self.inner { match self.inner {
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(s) => s.peer_addr(), InnerAsyncNetworkStream::Tokio1Tcp(ref s) => s.peer_addr(),
#[cfg(feature = "tokio1-native-tls")] #[cfg(feature = "tokio1-native-tls")]
InnerAsyncNetworkStream::Tokio1NativeTls(s) => { InnerAsyncNetworkStream::Tokio1NativeTls(ref s) => {
s.get_ref().get_ref().get_ref().peer_addr() s.get_ref().get_ref().get_ref().peer_addr()
} }
#[cfg(feature = "tokio1-rustls-tls")] #[cfg(feature = "tokio1-rustls-tls")]
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => s.get_ref().0.peer_addr(), InnerAsyncNetworkStream::Tokio1RustlsTls(ref s) => s.get_ref().0.peer_addr(),
#[cfg(feature = "tokio1-boring-tls")] #[cfg(feature = "tokio1-boring-tls")]
InnerAsyncNetworkStream::Tokio1BoringTls(s) => s.get_ref().peer_addr(), InnerAsyncNetworkStream::Tokio1BoringTls(ref s) => s.get_ref().peer_addr(),
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => s.peer_addr(), InnerAsyncNetworkStream::AsyncStd1Tcp(ref s) => s.peer_addr(),
#[cfg(feature = "async-std1-native-tls")] #[cfg(feature = "async-std1-native-tls")]
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => s.get_ref().peer_addr(), InnerAsyncNetworkStream::AsyncStd1NativeTls(ref s) => s.get_ref().peer_addr(),
#[cfg(feature = "async-std1-rustls-tls")] #[cfg(feature = "async-std1-rustls-tls")]
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => s.get_ref().0.peer_addr(), InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref s) => s.get_ref().0.peer_addr(),
InnerAsyncNetworkStream::None => {
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
Err(IoError::new(
ErrorKind::Other,
"InnerAsyncNetworkStream::None must never be built",
))
}
} }
} }
@@ -197,7 +199,7 @@ impl AsyncNetworkStream {
let mut stream = let mut stream =
AsyncNetworkStream::new(InnerAsyncNetworkStream::Tokio1Tcp(Box::new(tcp_stream))); AsyncNetworkStream::new(InnerAsyncNetworkStream::Tokio1Tcp(Box::new(tcp_stream)));
if let Some(tls_parameters) = tls_parameters { if let Some(tls_parameters) = tls_parameters {
stream = stream.upgrade_tls(tls_parameters).await?; stream.upgrade_tls(tls_parameters).await?;
} }
Ok(stream) Ok(stream)
} }
@@ -248,13 +250,13 @@ impl AsyncNetworkStream {
let mut stream = AsyncNetworkStream::new(InnerAsyncNetworkStream::AsyncStd1Tcp(tcp_stream)); let mut stream = AsyncNetworkStream::new(InnerAsyncNetworkStream::AsyncStd1Tcp(tcp_stream));
if let Some(tls_parameters) = tls_parameters { if let Some(tls_parameters) = tls_parameters {
stream = stream.upgrade_tls(tls_parameters).await?; stream.upgrade_tls(tls_parameters).await?;
} }
Ok(stream) Ok(stream)
} }
pub async fn upgrade_tls(self, tls_parameters: TlsParameters) -> Result<Self, Error> { pub async fn upgrade_tls(&mut self, tls_parameters: TlsParameters) -> Result<(), Error> {
match self.inner { match &self.inner {
#[cfg(all( #[cfg(all(
feature = "tokio1", feature = "tokio1",
not(any( not(any(
@@ -273,14 +275,18 @@ impl AsyncNetworkStream {
feature = "tokio1-rustls-tls", feature = "tokio1-rustls-tls",
feature = "tokio1-boring-tls" feature = "tokio1-boring-tls"
))] ))]
InnerAsyncNetworkStream::Tokio1Tcp(tcp_stream) => { InnerAsyncNetworkStream::Tokio1Tcp(_) => {
let inner = Self::upgrade_tokio1_tls(tcp_stream, tls_parameters) // get owned TcpStream
let tcp_stream = mem::replace(&mut self.inner, InnerAsyncNetworkStream::None);
let tcp_stream = match tcp_stream {
InnerAsyncNetworkStream::Tokio1Tcp(tcp_stream) => tcp_stream,
_ => unreachable!(),
};
self.inner = Self::upgrade_tokio1_tls(tcp_stream, tls_parameters)
.await .await
.map_err(error::connection)?; .map_err(error::connection)?;
Ok(Self { Ok(())
inner,
state: ConnectionState::Ok,
})
} }
#[cfg(all( #[cfg(all(
feature = "async-std1", feature = "async-std1",
@@ -292,16 +298,20 @@ impl AsyncNetworkStream {
} }
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))] #[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
InnerAsyncNetworkStream::AsyncStd1Tcp(tcp_stream) => { InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
let inner = Self::upgrade_asyncstd1_tls(tcp_stream, tls_parameters) // get owned TcpStream
let tcp_stream = mem::replace(&mut self.inner, InnerAsyncNetworkStream::None);
let tcp_stream = match tcp_stream {
InnerAsyncNetworkStream::AsyncStd1Tcp(tcp_stream) => tcp_stream,
_ => unreachable!(),
};
self.inner = Self::upgrade_asyncstd1_tls(tcp_stream, tls_parameters)
.await .await
.map_err(error::connection)?; .map_err(error::connection)?;
Ok(Self { Ok(())
inner,
state: ConnectionState::Ok,
})
} }
_ => Ok(self), _ => Ok(()),
} }
} }
@@ -435,7 +445,7 @@ impl AsyncNetworkStream {
} }
pub fn is_encrypted(&self) -> bool { pub fn is_encrypted(&self) -> bool {
match &self.inner { match self.inner {
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(_) => false, InnerAsyncNetworkStream::Tokio1Tcp(_) => false,
#[cfg(feature = "tokio1-native-tls")] #[cfg(feature = "tokio1-native-tls")]
@@ -450,6 +460,7 @@ impl AsyncNetworkStream {
InnerAsyncNetworkStream::AsyncStd1NativeTls(_) => true, InnerAsyncNetworkStream::AsyncStd1NativeTls(_) => true,
#[cfg(feature = "async-std1-rustls-tls")] #[cfg(feature = "async-std1-rustls-tls")]
InnerAsyncNetworkStream::AsyncStd1RustlsTls(_) => true, InnerAsyncNetworkStream::AsyncStd1RustlsTls(_) => true,
InnerAsyncNetworkStream::None => false,
} }
} }
@@ -498,6 +509,7 @@ impl AsyncNetworkStream {
.first() .first()
.unwrap() .unwrap()
.to_vec()), .to_vec()),
InnerAsyncNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
} }
} }
} }
@@ -508,9 +520,9 @@ impl FuturesAsyncRead for AsyncNetworkStream {
cx: &mut Context<'_>, cx: &mut Context<'_>,
buf: &mut [u8], buf: &mut [u8],
) -> Poll<IoResult<usize>> { ) -> Poll<IoResult<usize>> {
match &mut self.inner { match self.inner {
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(s) => { InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => {
let mut b = Tokio1ReadBuf::new(buf); let mut b = Tokio1ReadBuf::new(buf);
match Pin::new(s).poll_read(cx, &mut b) { match Pin::new(s).poll_read(cx, &mut b) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())), Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
@@ -519,7 +531,7 @@ impl FuturesAsyncRead for AsyncNetworkStream {
} }
} }
#[cfg(feature = "tokio1-native-tls")] #[cfg(feature = "tokio1-native-tls")]
InnerAsyncNetworkStream::Tokio1NativeTls(s) => { InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => {
let mut b = Tokio1ReadBuf::new(buf); let mut b = Tokio1ReadBuf::new(buf);
match Pin::new(s).poll_read(cx, &mut b) { match Pin::new(s).poll_read(cx, &mut b) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())), Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
@@ -528,7 +540,7 @@ impl FuturesAsyncRead for AsyncNetworkStream {
} }
} }
#[cfg(feature = "tokio1-rustls-tls")] #[cfg(feature = "tokio1-rustls-tls")]
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => { InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => {
let mut b = Tokio1ReadBuf::new(buf); let mut b = Tokio1ReadBuf::new(buf);
match Pin::new(s).poll_read(cx, &mut b) { match Pin::new(s).poll_read(cx, &mut b) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())), Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
@@ -537,7 +549,7 @@ impl FuturesAsyncRead for AsyncNetworkStream {
} }
} }
#[cfg(feature = "tokio1-boring-tls")] #[cfg(feature = "tokio1-boring-tls")]
InnerAsyncNetworkStream::Tokio1BoringTls(s) => { InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => {
let mut b = Tokio1ReadBuf::new(buf); let mut b = Tokio1ReadBuf::new(buf);
match Pin::new(s).poll_read(cx, &mut b) { match Pin::new(s).poll_read(cx, &mut b) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())), Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
@@ -546,11 +558,19 @@ impl FuturesAsyncRead for AsyncNetworkStream {
} }
} }
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_read(cx, buf), InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_read(cx, buf),
#[cfg(feature = "async-std1-native-tls")] #[cfg(feature = "async-std1-native-tls")]
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => Pin::new(s).poll_read(cx, buf), InnerAsyncNetworkStream::AsyncStd1NativeTls(ref mut s) => {
Pin::new(s).poll_read(cx, buf)
}
#[cfg(feature = "async-std1-rustls-tls")] #[cfg(feature = "async-std1-rustls-tls")]
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_read(cx, buf), InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref mut s) => {
Pin::new(s).poll_read(cx, buf)
}
InnerAsyncNetworkStream::None => {
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
Poll::Ready(Ok(0))
}
} }
} }
} }
@@ -561,61 +581,75 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
cx: &mut Context<'_>, cx: &mut Context<'_>,
buf: &[u8], buf: &[u8],
) -> Poll<IoResult<usize>> { ) -> Poll<IoResult<usize>> {
match &mut self.inner { match self.inner {
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(s) => Pin::new(s).poll_write(cx, buf), InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => Pin::new(s).poll_write(cx, buf),
#[cfg(feature = "tokio1-native-tls")] #[cfg(feature = "tokio1-native-tls")]
InnerAsyncNetworkStream::Tokio1NativeTls(s) => Pin::new(s).poll_write(cx, buf), InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
#[cfg(feature = "tokio1-rustls-tls")] #[cfg(feature = "tokio1-rustls-tls")]
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => Pin::new(s).poll_write(cx, buf), InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
#[cfg(feature = "tokio1-boring-tls")] #[cfg(feature = "tokio1-boring-tls")]
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_write(cx, buf), InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_write(cx, buf), InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_write(cx, buf),
#[cfg(feature = "async-std1-native-tls")] #[cfg(feature = "async-std1-native-tls")]
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => Pin::new(s).poll_write(cx, buf), InnerAsyncNetworkStream::AsyncStd1NativeTls(ref mut s) => {
Pin::new(s).poll_write(cx, buf)
}
#[cfg(feature = "async-std1-rustls-tls")] #[cfg(feature = "async-std1-rustls-tls")]
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_write(cx, buf), InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref mut s) => {
Pin::new(s).poll_write(cx, buf)
}
InnerAsyncNetworkStream::None => {
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
Poll::Ready(Ok(0))
}
} }
} }
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> { fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
match &mut self.inner { match self.inner {
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(s) => Pin::new(s).poll_flush(cx), InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "tokio1-native-tls")] #[cfg(feature = "tokio1-native-tls")]
InnerAsyncNetworkStream::Tokio1NativeTls(s) => Pin::new(s).poll_flush(cx), InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "tokio1-rustls-tls")] #[cfg(feature = "tokio1-rustls-tls")]
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => Pin::new(s).poll_flush(cx), InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "tokio1-boring-tls")] #[cfg(feature = "tokio1-boring-tls")]
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_flush(cx), InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_flush(cx), InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "async-std1-native-tls")] #[cfg(feature = "async-std1-native-tls")]
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => Pin::new(s).poll_flush(cx), InnerAsyncNetworkStream::AsyncStd1NativeTls(ref mut s) => Pin::new(s).poll_flush(cx),
#[cfg(feature = "async-std1-rustls-tls")] #[cfg(feature = "async-std1-rustls-tls")]
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_flush(cx), InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref mut s) => Pin::new(s).poll_flush(cx),
InnerAsyncNetworkStream::None => {
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
Poll::Ready(Ok(()))
}
} }
} }
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> { fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
self.state = ConnectionState::Closed; match self.inner {
match &mut self.inner {
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
InnerAsyncNetworkStream::Tokio1Tcp(s) => Pin::new(s).poll_shutdown(cx), InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => Pin::new(s).poll_shutdown(cx),
#[cfg(feature = "tokio1-native-tls")] #[cfg(feature = "tokio1-native-tls")]
InnerAsyncNetworkStream::Tokio1NativeTls(s) => Pin::new(s).poll_shutdown(cx), InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
#[cfg(feature = "tokio1-rustls-tls")] #[cfg(feature = "tokio1-rustls-tls")]
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => Pin::new(s).poll_shutdown(cx), InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
#[cfg(feature = "tokio1-boring-tls")] #[cfg(feature = "tokio1-boring-tls")]
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_shutdown(cx), InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_close(cx), InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_close(cx),
#[cfg(feature = "async-std1-native-tls")] #[cfg(feature = "async-std1-native-tls")]
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => Pin::new(s).poll_close(cx), InnerAsyncNetworkStream::AsyncStd1NativeTls(ref mut s) => Pin::new(s).poll_close(cx),
#[cfg(feature = "async-std1-rustls-tls")] #[cfg(feature = "async-std1-rustls-tls")]
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_close(cx), InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref mut s) => Pin::new(s).poll_close(cx),
InnerAsyncNetworkStream::None => {
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
Poll::Ready(Ok(()))
}
} }
} }
} }

View File

@@ -7,7 +7,7 @@ use std::{
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use super::escape_crlf; use super::escape_crlf;
use super::{ClientCodec, ConnectionState, NetworkStream, TlsParameters}; use super::{ClientCodec, NetworkStream, TlsParameters};
use crate::{ use crate::{
address::Envelope, address::Envelope,
transport::smtp::{ transport::smtp::{
@@ -20,11 +20,25 @@ use crate::{
}, },
}; };
macro_rules! try_smtp (
($err: expr, $client: ident) => ({
match $err {
Ok(val) => val,
Err(err) => {
$client.abort();
return Err(From::from(err))
},
}
})
);
/// Structure that implements the SMTP client /// Structure that implements the SMTP client
pub struct SmtpConnection { pub struct SmtpConnection {
/// TCP stream between client and server /// TCP stream between client and server
/// Value is None before connection /// Value is None before connection
stream: BufReader<NetworkStream>, stream: BufReader<NetworkStream>,
/// Panic state
panic: bool,
/// Information about the server /// Information about the server
server_info: ServerInfo, server_info: ServerInfo,
} }
@@ -51,6 +65,7 @@ impl SmtpConnection {
let stream = BufReader::new(stream); let stream = BufReader::new(stream);
let mut conn = SmtpConnection { let mut conn = SmtpConnection {
stream, stream,
panic: false,
server_info: ServerInfo::default(), server_info: ServerInfo::default(),
}; };
conn.set_timeout(timeout).map_err(error::network)?; conn.set_timeout(timeout).map_err(error::network)?;
@@ -95,25 +110,26 @@ impl SmtpConnection {
mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime)); mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime));
} }
self.command(Mail::new(envelope.from().cloned(), mail_options))?; try_smtp!(
self.command(Mail::new(envelope.from().cloned(), mail_options)),
self
);
// Recipient // Recipient
for to_address in envelope.to() { for to_address in envelope.to() {
self.command(Rcpt::new(to_address.clone(), vec![]))?; try_smtp!(self.command(Rcpt::new(to_address.clone(), vec![])), self);
} }
// Data // Data
self.command(Data)?; try_smtp!(self.command(Data), self);
// Message content // Message content
self.message(email) let result = try_smtp!(self.message(email), self);
Ok(result)
} }
pub fn has_broken(&self) -> bool { pub fn has_broken(&self) -> bool {
match self.stream.get_ref().state() { self.panic
ConnectionState::Ok => false,
ConnectionState::Broken | ConnectionState::Closed => true,
}
} }
pub fn can_starttls(&self) -> bool { pub fn can_starttls(&self) -> bool {
@@ -122,22 +138,20 @@ impl SmtpConnection {
#[allow(unused_variables)] #[allow(unused_variables)]
pub fn starttls( pub fn starttls(
mut self, &mut self,
tls_parameters: &TlsParameters, tls_parameters: &TlsParameters,
hello_name: &ClientId, hello_name: &ClientId,
) -> Result<Self, Error> { ) -> Result<(), Error> {
if self.server_info.supports_feature(Extension::StartTls) { if self.server_info.supports_feature(Extension::StartTls) {
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))] #[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
{ {
self.command(Starttls)?; try_smtp!(self.command(Starttls), self);
let stream = self.stream.into_inner(); self.stream.get_mut().upgrade_tls(tls_parameters)?;
let stream = stream.upgrade_tls(tls_parameters)?;
self.stream = BufReader::new(stream);
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
tracing::debug!("connection encrypted"); tracing::debug!("connection encrypted");
// Send EHLO again // Send EHLO again
self.ehlo(hello_name)?; try_smtp!(self.ehlo(hello_name), self);
Ok(self) Ok(())
} }
#[cfg(not(any( #[cfg(not(any(
feature = "native-tls", feature = "native-tls",
@@ -154,24 +168,22 @@ impl SmtpConnection {
/// Send EHLO and update server info /// Send EHLO and update server info
fn ehlo(&mut self, hello_name: &ClientId) -> Result<(), Error> { fn ehlo(&mut self, hello_name: &ClientId) -> Result<(), Error> {
let ehlo_response = self.command(Ehlo::new(hello_name.clone()))?; let ehlo_response = try_smtp!(self.command(Ehlo::new(hello_name.clone())), self);
self.server_info = ServerInfo::from_response(&ehlo_response)?; self.server_info = try_smtp!(ServerInfo::from_response(&ehlo_response), self);
Ok(()) Ok(())
} }
pub fn quit(&mut self) -> Result<Response, Error> { pub fn quit(&mut self) -> Result<Response, Error> {
self.command(Quit) Ok(try_smtp!(self.command(Quit), self))
} }
pub fn abort(&mut self) { pub fn abort(&mut self) {
match self.stream.get_ref().state() { // Only try to quit if we are not already broken
ConnectionState::Ok | ConnectionState::Broken => { if !self.panic {
let _ = self.command(Quit); self.panic = true;
let _ = self.stream.get_mut().shutdown(std::net::Shutdown::Both); let _ = self.command(Quit);
self.stream.get_mut().set_state(ConnectionState::Closed);
}
ConnectionState::Closed => {}
} }
let _ = self.stream.get_mut().shutdown(std::net::Shutdown::Both);
} }
/// Sets the underlying stream /// Sets the underlying stream
@@ -212,11 +224,14 @@ impl SmtpConnection {
while challenges > 0 && response.has_code(334) { while challenges > 0 && response.has_code(334) {
challenges -= 1; challenges -= 1;
response = self.command(Auth::new_from_response( response = try_smtp!(
mechanism, self.command(Auth::new_from_response(
credentials.clone(), mechanism,
&response, credentials.clone(),
)?)?; &response,
)?),
self
);
} }
if challenges == 0 { if challenges == 0 {
@@ -245,17 +260,12 @@ impl SmtpConnection {
/// Writes a string to the server /// Writes a string to the server
fn write(&mut self, string: &[u8]) -> Result<(), Error> { fn write(&mut self, string: &[u8]) -> Result<(), Error> {
self.stream.get_ref().state().verify()?;
self.stream.get_mut().set_state(ConnectionState::Broken);
self.stream self.stream
.get_mut() .get_mut()
.write_all(string) .write_all(string)
.map_err(error::network)?; .map_err(error::network)?;
self.stream.get_mut().flush().map_err(error::network)?; self.stream.get_mut().flush().map_err(error::network)?;
self.stream.get_mut().set_state(ConnectionState::Ok);
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
tracing::debug!("Wrote: {}", escape_crlf(&String::from_utf8_lossy(string))); tracing::debug!("Wrote: {}", escape_crlf(&String::from_utf8_lossy(string)));
Ok(()) Ok(())
@@ -263,9 +273,6 @@ impl SmtpConnection {
/// Gets the SMTP response /// Gets the SMTP response
pub fn read_response(&mut self) -> Result<Response, Error> { pub fn read_response(&mut self) -> Result<Response, Error> {
self.stream.get_ref().state().verify()?;
self.stream.get_mut().set_state(ConnectionState::Broken);
let mut buffer = String::with_capacity(100); let mut buffer = String::with_capacity(100);
while self.stream.read_line(&mut buffer).map_err(error::network)? > 0 { while self.stream.read_line(&mut buffer).map_err(error::network)? > 0 {
@@ -273,8 +280,6 @@ impl SmtpConnection {
tracing::debug!("<< {}", escape_crlf(&buffer)); tracing::debug!("<< {}", escape_crlf(&buffer));
match parse_response(&buffer) { match parse_response(&buffer) {
Ok((_remaining, response)) => { Ok((_remaining, response)) => {
self.stream.get_mut().set_state(ConnectionState::Ok);
return if response.is_positive() { return if response.is_positive() {
Ok(response) Ok(response)
} else { } else {

View File

@@ -40,7 +40,6 @@ pub use self::{
connection::SmtpConnection, connection::SmtpConnection,
tls::{Certificate, CertificateStore, Tls, TlsParameters, TlsParametersBuilder}, tls::{Certificate, CertificateStore, Tls, TlsParameters, TlsParametersBuilder},
}; };
use super::{error, Error};
#[cfg(any(feature = "tokio1", feature = "async-std1"))] #[cfg(any(feature = "tokio1", feature = "async-std1"))]
mod async_connection; mod async_connection;
@@ -50,23 +49,6 @@ mod connection;
mod net; mod net;
mod tls; mod tls;
#[derive(Debug, Copy, Clone)]
enum ConnectionState {
Ok,
Broken,
Closed,
}
impl ConnectionState {
fn verify(&mut self) -> Result<(), Error> {
match self {
Self::Ok => Ok(()),
Self::Broken => Err(error::connection("connection broken")),
Self::Closed => Err(error::connection("connection closed")),
}
}
}
/// The codec used for transparency /// The codec used for transparency
#[derive(Debug)] #[derive(Debug)]
struct ClientCodec { struct ClientCodec {

View File

@@ -2,7 +2,8 @@
use std::sync::Arc; use std::sync::Arc;
use std::{ use std::{
io::{self, Read, Write}, io::{self, Read, Write},
net::{IpAddr, Shutdown, SocketAddr, TcpStream, ToSocketAddrs}, mem,
net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream, ToSocketAddrs},
time::Duration, time::Duration,
}; };
@@ -16,13 +17,12 @@ use socket2::{Domain, Protocol, Type};
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))] #[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
use super::InnerTlsParameters; use super::InnerTlsParameters;
use super::{ConnectionState, TlsParameters}; use super::TlsParameters;
use crate::transport::smtp::{error, Error}; use crate::transport::smtp::{error, Error};
/// A network stream /// A network stream
pub struct NetworkStream { pub struct NetworkStream {
inner: InnerNetworkStream, inner: InnerNetworkStream,
state: ConnectionState,
} }
/// Represents the different types of underlying network streams /// Represents the different types of underlying network streams
@@ -40,49 +40,53 @@ enum InnerNetworkStream {
RustlsTls(StreamOwned<ClientConnection, TcpStream>), RustlsTls(StreamOwned<ClientConnection, TcpStream>),
#[cfg(feature = "boring-tls")] #[cfg(feature = "boring-tls")]
BoringTls(SslStream<TcpStream>), BoringTls(SslStream<TcpStream>),
/// Can't be built
None,
} }
impl NetworkStream { impl NetworkStream {
fn new(inner: InnerNetworkStream) -> Self { fn new(inner: InnerNetworkStream) -> Self {
NetworkStream { if let InnerNetworkStream::None = inner {
inner, debug_assert!(false, "InnerNetworkStream::None must never be built");
state: ConnectionState::Ok,
} }
}
pub(super) fn state(&self) -> ConnectionState { NetworkStream { inner }
self.state
}
pub(super) fn set_state(&mut self, state: ConnectionState) {
self.state = state;
} }
/// Returns peer's address /// Returns peer's address
pub fn peer_addr(&self) -> io::Result<SocketAddr> { pub fn peer_addr(&self) -> io::Result<SocketAddr> {
match &self.inner { match self.inner {
InnerNetworkStream::Tcp(s) => s.peer_addr(), InnerNetworkStream::Tcp(ref s) => s.peer_addr(),
#[cfg(feature = "native-tls")] #[cfg(feature = "native-tls")]
InnerNetworkStream::NativeTls(s) => s.get_ref().peer_addr(), InnerNetworkStream::NativeTls(ref s) => s.get_ref().peer_addr(),
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
InnerNetworkStream::RustlsTls(s) => s.get_ref().peer_addr(), InnerNetworkStream::RustlsTls(ref s) => s.get_ref().peer_addr(),
#[cfg(feature = "boring-tls")] #[cfg(feature = "boring-tls")]
InnerNetworkStream::BoringTls(s) => s.get_ref().peer_addr(), InnerNetworkStream::BoringTls(ref s) => s.get_ref().peer_addr(),
InnerNetworkStream::None => {
debug_assert!(false, "InnerNetworkStream::None must never be built");
Ok(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::new(127, 0, 0, 1),
80,
)))
}
} }
} }
/// Shutdowns the connection /// Shutdowns the connection
pub fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
self.state = ConnectionState::Closed; match self.inner {
InnerNetworkStream::Tcp(ref s) => s.shutdown(how),
match &self.inner {
InnerNetworkStream::Tcp(s) => s.shutdown(how),
#[cfg(feature = "native-tls")] #[cfg(feature = "native-tls")]
InnerNetworkStream::NativeTls(s) => s.get_ref().shutdown(how), InnerNetworkStream::NativeTls(ref s) => s.get_ref().shutdown(how),
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
InnerNetworkStream::RustlsTls(s) => s.get_ref().shutdown(how), InnerNetworkStream::RustlsTls(ref s) => s.get_ref().shutdown(how),
#[cfg(feature = "boring-tls")] #[cfg(feature = "boring-tls")]
InnerNetworkStream::BoringTls(s) => s.get_ref().shutdown(how), InnerNetworkStream::BoringTls(ref s) => s.get_ref().shutdown(how),
InnerNetworkStream::None => {
debug_assert!(false, "InnerNetworkStream::None must never be built");
Ok(())
}
} }
} }
@@ -135,13 +139,13 @@ impl NetworkStream {
let tcp_stream = try_connect(server, timeout, local_addr)?; let tcp_stream = try_connect(server, timeout, local_addr)?;
let mut stream = NetworkStream::new(InnerNetworkStream::Tcp(tcp_stream)); let mut stream = NetworkStream::new(InnerNetworkStream::Tcp(tcp_stream));
if let Some(tls_parameters) = tls_parameters { if let Some(tls_parameters) = tls_parameters {
stream = stream.upgrade_tls(tls_parameters)?; stream.upgrade_tls(tls_parameters)?;
} }
Ok(stream) Ok(stream)
} }
pub fn upgrade_tls(self, tls_parameters: &TlsParameters) -> Result<Self, Error> { pub fn upgrade_tls(&mut self, tls_parameters: &TlsParameters) -> Result<(), Error> {
match self.inner { match &self.inner {
#[cfg(not(any( #[cfg(not(any(
feature = "native-tls", feature = "native-tls",
feature = "rustls-tls", feature = "rustls-tls",
@@ -153,14 +157,18 @@ impl NetworkStream {
} }
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))] #[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
InnerNetworkStream::Tcp(tcp_stream) => { InnerNetworkStream::Tcp(_) => {
let inner = Self::upgrade_tls_impl(tcp_stream, tls_parameters)?; // get owned TcpStream
Ok(Self { let tcp_stream = mem::replace(&mut self.inner, InnerNetworkStream::None);
inner, let tcp_stream = match tcp_stream {
state: ConnectionState::Ok, InnerNetworkStream::Tcp(tcp_stream) => tcp_stream,
}) _ => unreachable!(),
};
self.inner = Self::upgrade_tls_impl(tcp_stream, tls_parameters)?;
Ok(())
} }
_ => Ok(self), _ => Ok(()),
} }
} }
@@ -200,7 +208,7 @@ impl NetworkStream {
} }
pub fn is_encrypted(&self) -> bool { pub fn is_encrypted(&self) -> bool {
match &self.inner { match self.inner {
InnerNetworkStream::Tcp(_) => false, InnerNetworkStream::Tcp(_) => false,
#[cfg(feature = "native-tls")] #[cfg(feature = "native-tls")]
InnerNetworkStream::NativeTls(_) => true, InnerNetworkStream::NativeTls(_) => true,
@@ -208,6 +216,10 @@ impl NetworkStream {
InnerNetworkStream::RustlsTls(_) => true, InnerNetworkStream::RustlsTls(_) => true,
#[cfg(feature = "boring-tls")] #[cfg(feature = "boring-tls")]
InnerNetworkStream::BoringTls(_) => true, InnerNetworkStream::BoringTls(_) => true,
InnerNetworkStream::None => {
debug_assert!(false, "InnerNetworkStream::None must never be built");
false
}
} }
} }
@@ -237,72 +249,105 @@ impl NetworkStream {
.unwrap() .unwrap()
.to_der() .to_der()
.map_err(error::tls)?), .map_err(error::tls)?),
InnerNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
} }
} }
pub fn set_read_timeout(&mut self, duration: Option<Duration>) -> io::Result<()> { pub fn set_read_timeout(&mut self, duration: Option<Duration>) -> io::Result<()> {
match &mut self.inner { match self.inner {
InnerNetworkStream::Tcp(stream) => stream.set_read_timeout(duration), InnerNetworkStream::Tcp(ref mut stream) => stream.set_read_timeout(duration),
#[cfg(feature = "native-tls")] #[cfg(feature = "native-tls")]
InnerNetworkStream::NativeTls(stream) => stream.get_ref().set_read_timeout(duration), InnerNetworkStream::NativeTls(ref mut stream) => {
stream.get_ref().set_read_timeout(duration)
}
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
InnerNetworkStream::RustlsTls(stream) => stream.get_ref().set_read_timeout(duration), InnerNetworkStream::RustlsTls(ref mut stream) => {
stream.get_ref().set_read_timeout(duration)
}
#[cfg(feature = "boring-tls")] #[cfg(feature = "boring-tls")]
InnerNetworkStream::BoringTls(stream) => stream.get_ref().set_read_timeout(duration), InnerNetworkStream::BoringTls(ref mut stream) => {
stream.get_ref().set_read_timeout(duration)
}
InnerNetworkStream::None => {
debug_assert!(false, "InnerNetworkStream::None must never be built");
Ok(())
}
} }
} }
/// Set write timeout for IO calls /// Set write timeout for IO calls
pub fn set_write_timeout(&mut self, duration: Option<Duration>) -> io::Result<()> { pub fn set_write_timeout(&mut self, duration: Option<Duration>) -> io::Result<()> {
match &mut self.inner { match self.inner {
InnerNetworkStream::Tcp(stream) => stream.set_write_timeout(duration), InnerNetworkStream::Tcp(ref mut stream) => stream.set_write_timeout(duration),
#[cfg(feature = "native-tls")] #[cfg(feature = "native-tls")]
InnerNetworkStream::NativeTls(stream) => stream.get_ref().set_write_timeout(duration), InnerNetworkStream::NativeTls(ref mut stream) => {
stream.get_ref().set_write_timeout(duration)
}
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
InnerNetworkStream::RustlsTls(stream) => stream.get_ref().set_write_timeout(duration), InnerNetworkStream::RustlsTls(ref mut stream) => {
stream.get_ref().set_write_timeout(duration)
}
#[cfg(feature = "boring-tls")] #[cfg(feature = "boring-tls")]
InnerNetworkStream::BoringTls(stream) => stream.get_ref().set_write_timeout(duration), InnerNetworkStream::BoringTls(ref mut stream) => {
stream.get_ref().set_write_timeout(duration)
}
InnerNetworkStream::None => {
debug_assert!(false, "InnerNetworkStream::None must never be built");
Ok(())
}
} }
} }
} }
impl Read for NetworkStream { impl Read for NetworkStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match &mut self.inner { match self.inner {
InnerNetworkStream::Tcp(s) => s.read(buf), InnerNetworkStream::Tcp(ref mut s) => s.read(buf),
#[cfg(feature = "native-tls")] #[cfg(feature = "native-tls")]
InnerNetworkStream::NativeTls(s) => s.read(buf), InnerNetworkStream::NativeTls(ref mut s) => s.read(buf),
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
InnerNetworkStream::RustlsTls(s) => s.read(buf), InnerNetworkStream::RustlsTls(ref mut s) => s.read(buf),
#[cfg(feature = "boring-tls")] #[cfg(feature = "boring-tls")]
InnerNetworkStream::BoringTls(s) => s.read(buf), InnerNetworkStream::BoringTls(ref mut s) => s.read(buf),
InnerNetworkStream::None => {
debug_assert!(false, "InnerNetworkStream::None must never be built");
Ok(0)
}
} }
} }
} }
impl Write for NetworkStream { impl Write for NetworkStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match &mut self.inner { match self.inner {
InnerNetworkStream::Tcp(s) => s.write(buf), InnerNetworkStream::Tcp(ref mut s) => s.write(buf),
#[cfg(feature = "native-tls")] #[cfg(feature = "native-tls")]
InnerNetworkStream::NativeTls(s) => s.write(buf), InnerNetworkStream::NativeTls(ref mut s) => s.write(buf),
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
InnerNetworkStream::RustlsTls(s) => s.write(buf), InnerNetworkStream::RustlsTls(ref mut s) => s.write(buf),
#[cfg(feature = "boring-tls")] #[cfg(feature = "boring-tls")]
InnerNetworkStream::BoringTls(s) => s.write(buf), InnerNetworkStream::BoringTls(ref mut s) => s.write(buf),
InnerNetworkStream::None => {
debug_assert!(false, "InnerNetworkStream::None must never be built");
Ok(0)
}
} }
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
match &mut self.inner { match self.inner {
InnerNetworkStream::Tcp(s) => s.flush(), InnerNetworkStream::Tcp(ref mut s) => s.flush(),
#[cfg(feature = "native-tls")] #[cfg(feature = "native-tls")]
InnerNetworkStream::NativeTls(s) => s.flush(), InnerNetworkStream::NativeTls(ref mut s) => s.flush(),
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
InnerNetworkStream::RustlsTls(s) => s.flush(), InnerNetworkStream::RustlsTls(ref mut s) => s.flush(),
#[cfg(feature = "boring-tls")] #[cfg(feature = "boring-tls")]
InnerNetworkStream::BoringTls(s) => s.flush(), InnerNetworkStream::BoringTls(ref mut s) => s.flush(),
InnerNetworkStream::None => {
debug_assert!(false, "InnerNetworkStream::None must never be built");
Ok(())
}
} }
} }
} }

View File

@@ -119,7 +119,7 @@ impl fmt::Debug for Error {
builder.field("kind", &self.inner.kind); builder.field("kind", &self.inner.kind);
if let Some(source) = &self.inner.source { if let Some(ref source) = self.inner.source {
builder.field("source", source); builder.field("source", source);
} }
@@ -129,22 +129,22 @@ impl fmt::Debug for Error {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner.kind { match self.inner.kind {
Kind::Response => f.write_str("response error")?, Kind::Response => f.write_str("response error")?,
Kind::Client => f.write_str("internal client error")?, Kind::Client => f.write_str("internal client error")?,
Kind::Network => f.write_str("network error")?, Kind::Network => f.write_str("network error")?,
Kind::Connection => f.write_str("Connection error")?, Kind::Connection => f.write_str("Connection error")?,
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))] #[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
Kind::Tls => f.write_str("tls error")?, Kind::Tls => f.write_str("tls error")?,
Kind::Transient(code) => { Kind::Transient(ref code) => {
write!(f, "transient error ({code})")?; write!(f, "transient error ({code})")?;
} }
Kind::Permanent(code) => { Kind::Permanent(ref code) => {
write!(f, "permanent error ({code})")?; write!(f, "permanent error ({code})")?;
} }
}; };
if let Some(e) = &self.inner.source { if let Some(ref e) = self.inner.source {
write!(f, ": {e}")?; write!(f, ": {e}")?;
} }

View File

@@ -52,10 +52,10 @@ impl Default for ClientId {
impl Display for ClientId { impl Display for ClientId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match *self {
Self::Domain(value) => f.write_str(value), Self::Domain(ref value) => f.write_str(value),
Self::Ipv4(value) => write!(f, "[{value}]"), Self::Ipv4(ref value) => write!(f, "[{value}]"),
Self::Ipv6(value) => write!(f, "[IPv6:{value}]"), Self::Ipv6(ref value) => write!(f, "[IPv6:{value}]"),
} }
} }
} }
@@ -92,11 +92,11 @@ pub enum Extension {
impl Display for Extension { impl Display for Extension {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match *self {
Extension::EightBitMime => f.write_str("8BITMIME"), Extension::EightBitMime => f.write_str("8BITMIME"),
Extension::SmtpUtfEight => f.write_str("SMTPUTF8"), Extension::SmtpUtfEight => f.write_str("SMTPUTF8"),
Extension::StartTls => f.write_str("STARTTLS"), Extension::StartTls => f.write_str("STARTTLS"),
Extension::Authentication(mechanism) => write!(f, "AUTH {mechanism}"), Extension::Authentication(ref mechanism) => write!(f, "AUTH {mechanism}"),
} }
} }
} }
@@ -226,16 +226,16 @@ pub enum MailParameter {
impl Display for MailParameter { impl Display for MailParameter {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match *self {
MailParameter::Body(value) => write!(f, "BODY={value}"), MailParameter::Body(ref value) => write!(f, "BODY={value}"),
MailParameter::Size(size) => write!(f, "SIZE={size}"), MailParameter::Size(size) => write!(f, "SIZE={size}"),
MailParameter::SmtpUtfEight => f.write_str("SMTPUTF8"), MailParameter::SmtpUtfEight => f.write_str("SMTPUTF8"),
MailParameter::Other { MailParameter::Other {
keyword, ref keyword,
value: Some(value), value: Some(ref value),
} => write!(f, "{}={}", keyword, XText(value)), } => write!(f, "{}={}", keyword, XText(value)),
MailParameter::Other { MailParameter::Other {
keyword, ref keyword,
value: None, value: None,
} => f.write_str(keyword), } => f.write_str(keyword),
} }
@@ -276,13 +276,13 @@ pub enum RcptParameter {
impl Display for RcptParameter { impl Display for RcptParameter {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match &self { match *self {
RcptParameter::Other { RcptParameter::Other {
keyword, ref keyword,
value: Some(value), value: Some(ref value),
} => write!(f, "{keyword}={}", XText(value)), } => write!(f, "{keyword}={}", XText(value)),
RcptParameter::Other { RcptParameter::Other {
keyword, ref keyword,
value: None, value: None,
} => f.write_str(keyword), } => f.write_str(keyword),
} }
@@ -291,14 +291,8 @@ impl Display for RcptParameter {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::collections::HashSet;
use super::*; use super::*;
use crate::transport::smtp::{ use crate::transport::smtp::response::{Category, Code, Detail, Severity};
authentication::Mechanism,
response::{Category, Code, Detail, Response, Severity},
};
#[test] #[test]
fn test_clientid_fmt() { fn test_clientid_fmt() {

View File

@@ -317,9 +317,9 @@ impl SmtpClient {
/// Handles encryption and authentication /// Handles encryption and authentication
pub fn connection(&self) -> Result<SmtpConnection, Error> { pub fn connection(&self) -> Result<SmtpConnection, Error> {
#[allow(clippy::match_single_binding)] #[allow(clippy::match_single_binding)]
let tls_parameters = match &self.info.tls { let tls_parameters = match self.info.tls {
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))] #[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
Tls::Wrapper(tls_parameters) => Some(tls_parameters), Tls::Wrapper(ref tls_parameters) => Some(tls_parameters),
_ => None, _ => None,
}; };
@@ -333,14 +333,14 @@ impl SmtpClient {
)?; )?;
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))] #[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
match &self.info.tls { match self.info.tls {
Tls::Opportunistic(tls_parameters) => { Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() { if conn.can_starttls() {
conn = conn.starttls(tls_parameters, &self.info.hello_name)?; conn.starttls(tls_parameters, &self.info.hello_name)?;
} }
} }
Tls::Required(tls_parameters) => { Tls::Required(ref tls_parameters) => {
conn = conn.starttls(tls_parameters, &self.info.hello_name)?; conn.starttls(tls_parameters, &self.info.hello_name)?;
} }
_ => (), _ => (),
} }