Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3d00051b2 | ||
|
|
12580d82f4 | ||
|
|
f7849078b8 | ||
|
|
f2c94cdf4d | ||
|
|
74f64b81ab | ||
|
|
39c71dbfd2 | ||
|
|
c1bf5dfda1 | ||
|
|
1c1fef8055 | ||
|
|
1540f16015 | ||
|
|
330daa1173 | ||
|
|
47f2fe0750 | ||
|
|
8b6cee30ee | ||
|
|
62c16e90ef | ||
|
|
e0494a5f9d | ||
|
|
8c3bffa728 | ||
|
|
47eda90433 | ||
|
|
46ea8c48ac | ||
|
|
5f7063fdc3 | ||
|
|
61c1f6bc6f |
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -75,8 +75,8 @@ jobs:
|
||||
rust: stable
|
||||
- name: beta
|
||||
rust: beta
|
||||
- name: 1.65.0
|
||||
rust: 1.65.0
|
||||
- name: '1.70'
|
||||
rust: '1.70'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
57
CHANGELOG.md
57
CHANGELOG.md
@@ -1,5 +1,60 @@
|
||||
<a name="v0.11.4"></a>
|
||||
### v0.11.4 (2024-01-28)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
* Percent decode credentials in SMTP connect URL ([#932], [#934])
|
||||
* Fix mimebody DKIM body-hash computation ([#923])
|
||||
|
||||
[#923]: https://github.com/lettre/lettre/pull/923
|
||||
[#932]: https://github.com/lettre/lettre/pull/932
|
||||
[#934]: https://github.com/lettre/lettre/pull/934
|
||||
|
||||
<a name="v0.11.3"></a>
|
||||
### v0.11.3 (2024-01-02)
|
||||
|
||||
#### Features
|
||||
|
||||
* Derive `Clone` for `FileTransport` and `AsyncFileTransport` ([#924])
|
||||
* Derive `Debug` for `SmtpTransport` ([#925])
|
||||
|
||||
#### Misc
|
||||
|
||||
* Upgrade `rustls` to v0.22 ([#921])
|
||||
* Drop once_cell dependency in favor of OnceLock from std ([#928])
|
||||
|
||||
[#921]: https://github.com/lettre/lettre/pull/921
|
||||
[#924]: https://github.com/lettre/lettre/pull/924
|
||||
[#925]: https://github.com/lettre/lettre/pull/925
|
||||
[#928]: https://github.com/lettre/lettre/pull/928
|
||||
|
||||
<a name="v0.11.2"></a>
|
||||
### v0.11.2 (2023-11-23)
|
||||
|
||||
#### Upgrade notes
|
||||
|
||||
* MSRV is now 1.70 ([#916])
|
||||
|
||||
#### Misc
|
||||
|
||||
* Bump `idna` to v0.5 ([#918])
|
||||
* Bump `boring` and `tokio-boring` to v4 ([#915])
|
||||
|
||||
[#915]: https://github.com/lettre/lettre/pull/915
|
||||
[#916]: https://github.com/lettre/lettre/pull/916
|
||||
[#918]: https://github.com/lettre/lettre/pull/918
|
||||
|
||||
<a name="v0.11.1"></a>
|
||||
### v0.11.1 (2023-10-24)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
* Fix `webpki-roots` certificate store setup ([#909])
|
||||
|
||||
[#909]: https://github.com/lettre/lettre/pull/909
|
||||
|
||||
<a name="v0.11.0"></a>
|
||||
### v0.11.0 (2023-08-15)
|
||||
### v0.11.0 (2023-10-15)
|
||||
|
||||
While this release technically contains breaking changes, we expect most projects
|
||||
to be able to upgrade by only bumping the version in `Cargo.toml`.
|
||||
|
||||
28
Cargo.toml
28
Cargo.toml
@@ -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.11.0"
|
||||
version = "0.11.4"
|
||||
description = "Email client"
|
||||
readme = "README.md"
|
||||
homepage = "https://lettre.rs"
|
||||
@@ -11,7 +11,7 @@ authors = ["Alexis Mousset <contact@amousset.me>", "Paolo Barbolini <paolo@paolo
|
||||
categories = ["email", "network-programming"]
|
||||
keywords = ["email", "smtp", "mailer", "message", "sendmail"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
rust-version = "1.70"
|
||||
|
||||
[badges]
|
||||
is-it-maintained-issue-resolution = { repository = "lettre/lettre" }
|
||||
@@ -20,8 +20,7 @@ maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
chumsky = "0.9"
|
||||
idna = "0.4"
|
||||
once_cell = { version = "1", optional = true }
|
||||
idna = "0.5"
|
||||
tracing = { version = "0.1.16", default-features = false, features = ["std"], optional = true } # feature
|
||||
|
||||
# builder
|
||||
@@ -42,14 +41,15 @@ nom = { version = "7", optional = true }
|
||||
hostname = { version = "0.3", optional = true } # feature
|
||||
socket2 = { version = "0.5.1", optional = true }
|
||||
url = { version = "2.4", optional = true }
|
||||
percent-encoding = { version = "2.3", optional = true }
|
||||
|
||||
## tls
|
||||
native-tls = { version = "0.2.5", optional = true } # feature
|
||||
rustls = { version = "0.21", features = ["dangerous_configuration"], optional = true }
|
||||
rustls-pemfile = { version = "1", optional = true }
|
||||
rustls-native-certs = { version = "0.6.2", optional = true }
|
||||
webpki-roots = { version = "0.25", optional = true }
|
||||
boring = { version = "3", optional = true }
|
||||
rustls = { version = "0.22.1", optional = true }
|
||||
rustls-pemfile = { version = "2", optional = true }
|
||||
rustls-native-certs = { version = "0.7", optional = true }
|
||||
webpki-roots = { version = "0.26", optional = true }
|
||||
boring = { version = "4", optional = true }
|
||||
|
||||
# async
|
||||
futures-io = { version = "0.3.7", optional = true }
|
||||
@@ -59,13 +59,13 @@ async-trait = { version = "0.1", optional = true }
|
||||
## async-std
|
||||
async-std = { version = "1.8", optional = true }
|
||||
#async-native-tls = { version = "0.3.3", optional = true }
|
||||
futures-rustls = { version = "0.24", optional = true }
|
||||
futures-rustls = { version = "0.25", optional = true }
|
||||
|
||||
## tokio
|
||||
tokio1_crate = { package = "tokio", version = "1", optional = true }
|
||||
tokio1_native_tls_crate = { package = "tokio-native-tls", version = "0.3", optional = true }
|
||||
tokio1_rustls = { package = "tokio-rustls", version = "0.24", optional = true }
|
||||
tokio1_boring = { package = "tokio-boring", version = "3", optional = true }
|
||||
tokio1_rustls = { package = "tokio-rustls", version = "0.25", optional = true }
|
||||
tokio1_boring = { package = "tokio-boring", version = "4", optional = true }
|
||||
|
||||
## dkim
|
||||
sha2 = { version = "0.10", optional = true, features = ["oid"] }
|
||||
@@ -85,7 +85,7 @@ walkdir = "2"
|
||||
tokio1_crate = { package = "tokio", version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
async-std = { version = "1.8", features = ["attributes"] }
|
||||
serde_json = "1"
|
||||
maud = "0.25"
|
||||
maud = "0.26"
|
||||
|
||||
[[bench]]
|
||||
harness = false
|
||||
@@ -104,7 +104,7 @@ mime03 = ["dep:mime"]
|
||||
file-transport = ["dep:uuid", "tokio1_crate?/fs", "tokio1_crate?/io-util"]
|
||||
file-transport-envelope = ["serde", "dep:serde_json", "file-transport"]
|
||||
sendmail-transport = ["tokio1_crate?/process", "tokio1_crate?/io-util", "async-std?/unstable"]
|
||||
smtp-transport = ["dep:base64", "dep:nom", "dep:socket2", "dep:once_cell", "dep:url", "tokio1_crate?/rt", "tokio1_crate?/time", "tokio1_crate?/net"]
|
||||
smtp-transport = ["dep:base64", "dep:nom", "dep:socket2", "dep:url", "dep:percent-encoding", "tokio1_crate?/rt", "tokio1_crate?/time", "tokio1_crate?/net"]
|
||||
|
||||
pool = ["dep:futures-util"]
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://deps.rs/crate/lettre/0.11.0">
|
||||
<img src="https://deps.rs/crate/lettre/0.11.0/status.svg"
|
||||
<a href="https://deps.rs/crate/lettre/0.11.4">
|
||||
<img src="https://deps.rs/crate/lettre/0.11.4/status.svg"
|
||||
alt="dependency status" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -53,12 +53,12 @@ Lettre does not provide (for now):
|
||||
## 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.65, but this could change at any time either from
|
||||
the minimum supported Rust version is 1.70, 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.65 or newer.
|
||||
This library requires Rust 1.70 or newer.
|
||||
To use this library, add the following to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! * Secure defaults
|
||||
//! * Async support
|
||||
//!
|
||||
//! Lettre requires Rust 1.65 or newer.
|
||||
//! Lettre requires Rust 1.70 or newer.
|
||||
//!
|
||||
//! ## Features
|
||||
//!
|
||||
@@ -109,7 +109,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.11.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.4")]
|
||||
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
|
||||
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
@@ -17,6 +17,16 @@ pub(super) enum Part {
|
||||
Multi(MultiPart),
|
||||
}
|
||||
|
||||
impl Part {
|
||||
#[cfg(feature = "dkim")]
|
||||
pub(super) fn format_body(&self, out: &mut Vec<u8>) {
|
||||
match self {
|
||||
Part::Single(part) => part.format_body(out),
|
||||
Part::Multi(part) => part.format_body(out),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailFormat for Part {
|
||||
fn format(&self, out: &mut Vec<u8>) {
|
||||
match self {
|
||||
@@ -132,6 +142,12 @@ impl SinglePart {
|
||||
self.format(&mut out);
|
||||
out
|
||||
}
|
||||
|
||||
/// Format only the signlepart body
|
||||
fn format_body(&self, out: &mut Vec<u8>) {
|
||||
out.extend_from_slice(&self.body);
|
||||
out.extend_from_slice(b"\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailFormat for SinglePart {
|
||||
@@ -139,8 +155,7 @@ impl EmailFormat for SinglePart {
|
||||
write!(out, "{}", self.headers)
|
||||
.expect("A Write implementation panicked while formatting headers");
|
||||
out.extend_from_slice(b"\r\n");
|
||||
out.extend_from_slice(&self.body);
|
||||
out.extend_from_slice(b"\r\n");
|
||||
self.format_body(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,14 +388,9 @@ impl MultiPart {
|
||||
self.format(&mut out);
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailFormat for MultiPart {
|
||||
fn format(&self, out: &mut Vec<u8>) {
|
||||
write!(out, "{}", self.headers)
|
||||
.expect("A Write implementation panicked while formatting headers");
|
||||
out.extend_from_slice(b"\r\n");
|
||||
|
||||
/// Format only the multipart body
|
||||
fn format_body(&self, out: &mut Vec<u8>) {
|
||||
let boundary = self.boundary();
|
||||
|
||||
for part in &self.parts {
|
||||
@@ -396,6 +406,15 @@ impl EmailFormat for MultiPart {
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailFormat for MultiPart {
|
||||
fn format(&self, out: &mut Vec<u8>) {
|
||||
write!(out, "{}", self.headers)
|
||||
.expect("A Write implementation panicked while formatting headers");
|
||||
out.extend_from_slice(b"\r\n");
|
||||
self.format_body(out);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -525,7 +525,7 @@ impl Message {
|
||||
pub(crate) fn body_raw(&self) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
match &self.body {
|
||||
MessageBody::Mime(p) => p.format(&mut out),
|
||||
MessageBody::Mime(p) => p.format_body(&mut out),
|
||||
MessageBody::Raw(r) => out.extend_from_slice(r),
|
||||
};
|
||||
out.extend_from_slice(b"\r\n");
|
||||
|
||||
@@ -157,7 +157,7 @@ mod error;
|
||||
type Id = String;
|
||||
|
||||
/// Writes the content and the envelope information to a file
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "file-transport")))]
|
||||
pub struct FileTransport {
|
||||
@@ -167,7 +167,7 @@ pub struct FileTransport {
|
||||
}
|
||||
|
||||
/// Asynchronously writes the content and the envelope information to a file
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
|
||||
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
|
||||
|
||||
@@ -16,6 +16,8 @@ use futures_io::{
|
||||
};
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
use futures_rustls::client::TlsStream as AsyncStd1RustlsTlsStream;
|
||||
#[cfg(any(feature = "tokio1-rustls-tls", feature = "async-std1-rustls-tls"))]
|
||||
use rustls::pki_types::ServerName;
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
use tokio1_boring::SslStream as Tokio1SslStream;
|
||||
#[cfg(feature = "tokio1")]
|
||||
@@ -350,7 +352,6 @@ impl AsyncNetworkStream {
|
||||
|
||||
#[cfg(feature = "tokio1-rustls-tls")]
|
||||
return {
|
||||
use rustls::ServerName;
|
||||
use tokio1_rustls::TlsConnector;
|
||||
|
||||
let domain = ServerName::try_from(domain.as_str())
|
||||
@@ -358,7 +359,7 @@ impl AsyncNetworkStream {
|
||||
|
||||
let connector = TlsConnector::from(config);
|
||||
let stream = connector
|
||||
.connect(domain, tcp_stream)
|
||||
.connect(domain.to_owned(), tcp_stream)
|
||||
.await
|
||||
.map_err(error::connection)?;
|
||||
Ok(InnerAsyncNetworkStream::Tokio1RustlsTls(stream))
|
||||
@@ -424,14 +425,13 @@ impl AsyncNetworkStream {
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
return {
|
||||
use futures_rustls::TlsConnector;
|
||||
use rustls::ServerName;
|
||||
|
||||
let domain = ServerName::try_from(domain.as_str())
|
||||
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
|
||||
|
||||
let connector = TlsConnector::from(config);
|
||||
let stream = connector
|
||||
.connect(domain, tcp_stream)
|
||||
.connect(domain.to_owned(), tcp_stream)
|
||||
.await
|
||||
.map_err(error::connection)?;
|
||||
Ok(InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream))
|
||||
@@ -486,8 +486,7 @@ impl AsyncNetworkStream {
|
||||
.unwrap()
|
||||
.first()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.0),
|
||||
.to_vec()),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(stream) => Ok(stream
|
||||
.ssl()
|
||||
@@ -509,8 +508,7 @@ impl AsyncNetworkStream {
|
||||
.unwrap()
|
||||
.first()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.0),
|
||||
.to_vec()),
|
||||
InnerAsyncNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use boring::ssl::SslStream;
|
||||
#[cfg(feature = "native-tls")]
|
||||
use native_tls::TlsStream;
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
use rustls::{ClientConnection, ServerName, StreamOwned};
|
||||
use rustls::{pki_types::ServerName, ClientConnection, StreamOwned};
|
||||
use socket2::{Domain, Protocol, Type};
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||
@@ -189,7 +189,7 @@ 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(Arc::clone(connector), domain)
|
||||
let connection = ClientConnection::new(Arc::clone(connector), domain.to_owned())
|
||||
.map_err(error::connection)?;
|
||||
let stream = StreamOwned::new(connection, tcp_stream);
|
||||
InnerNetworkStream::RustlsTls(stream)
|
||||
@@ -241,8 +241,7 @@ impl NetworkStream {
|
||||
.unwrap()
|
||||
.first()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.0),
|
||||
.to_vec()),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(stream) => Ok(stream
|
||||
.ssl()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::{self, Debug};
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
use std::{sync::Arc, time::SystemTime};
|
||||
use std::{io, sync::Arc};
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
use boring::{
|
||||
@@ -11,8 +11,10 @@ use boring::{
|
||||
use native_tls::{Protocol, TlsConnector};
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
use rustls::{
|
||||
client::{ServerCertVerified, ServerCertVerifier, WebPkiVerifier},
|
||||
ClientConfig, Error as TlsError, RootCertStore, ServerName,
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
crypto::{verify_tls12_signature, verify_tls13_signature},
|
||||
pki_types::{CertificateDer, ServerName, UnixTime},
|
||||
ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||
@@ -115,7 +117,7 @@ pub enum CertificateStore {
|
||||
/// Use a hardcoded set of Mozilla roots via the `webpki-roots` crate.
|
||||
///
|
||||
/// This option is only available in the rustls backend.
|
||||
#[cfg(feature = "webpki-roots")]
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
WebpkiRoots,
|
||||
/// Don't use any system certificates.
|
||||
None,
|
||||
@@ -337,8 +339,6 @@ impl TlsParametersBuilder {
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
|
||||
pub fn build_rustls(self) -> Result<TlsParameters, Error> {
|
||||
let tls = ClientConfig::builder();
|
||||
|
||||
let just_version3 = &[&rustls::version::TLS13];
|
||||
let supported_versions = match self.min_tls_version {
|
||||
TlsVersion::Tlsv10 => {
|
||||
@@ -351,58 +351,38 @@ impl TlsParametersBuilder {
|
||||
TlsVersion::Tlsv13 => just_version3,
|
||||
};
|
||||
|
||||
let tls = tls
|
||||
.with_safe_default_cipher_suites()
|
||||
.with_safe_default_kx_groups()
|
||||
.with_protocol_versions(supported_versions)
|
||||
.map_err(error::tls)?;
|
||||
let tls = ClientConfig::builder_with_protocol_versions(supported_versions);
|
||||
|
||||
let tls = if self.accept_invalid_certs {
|
||||
tls.with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {}))
|
||||
tls.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {}))
|
||||
} else {
|
||||
let mut root_cert_store = RootCertStore::empty();
|
||||
|
||||
#[cfg(feature = "rustls-native-certs")]
|
||||
fn load_native_roots(store: &mut RootCertStore) -> Result<(), Error> {
|
||||
let native_certs = rustls_native_certs::load_native_certs().map_err(error::tls)?;
|
||||
let mut valid_count = 0;
|
||||
let mut invalid_count = 0;
|
||||
for cert in native_certs {
|
||||
match store.add(&rustls::Certificate(cert.0)) {
|
||||
Ok(_) => valid_count += 1,
|
||||
Err(err) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::debug!("certificate parsing failed: {:?}", err);
|
||||
invalid_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let (added, ignored) = store.add_parsable_certificates(native_certs);
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::debug!(
|
||||
"loaded platform certs with {valid_count} valid and {invalid_count} invalid certs"
|
||||
"loaded platform certs with {added} valid and {ignored} ignored (invalid) certs"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "webpki-roots")]
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
fn load_webpki_roots(store: &mut RootCertStore) {
|
||||
store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
|
||||
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject,
|
||||
ta.spki,
|
||||
ta.name_constraints,
|
||||
)
|
||||
}));
|
||||
store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
}
|
||||
|
||||
match self.cert_store {
|
||||
CertificateStore::Default => {
|
||||
#[cfg(feature = "rustls-native-certs")]
|
||||
load_native_roots(&mut root_cert_store)?;
|
||||
#[cfg(all(not(feature = "rustls-native-certs"), feature = "webpki-roots"))]
|
||||
#[cfg(not(feature = "rustls-native-certs"))]
|
||||
load_webpki_roots(&mut root_cert_store);
|
||||
}
|
||||
#[cfg(feature = "webpki-roots")]
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
CertificateStore::WebpkiRoots => {
|
||||
load_webpki_roots(&mut root_cert_store);
|
||||
}
|
||||
@@ -410,14 +390,11 @@ impl TlsParametersBuilder {
|
||||
}
|
||||
for cert in self.root_certs {
|
||||
for rustls_cert in cert.rustls {
|
||||
root_cert_store.add(&rustls_cert).map_err(error::tls)?;
|
||||
root_cert_store.add(rustls_cert).map_err(error::tls)?;
|
||||
}
|
||||
}
|
||||
|
||||
tls.with_custom_certificate_verifier(Arc::new(WebPkiVerifier::new(
|
||||
root_cert_store,
|
||||
None,
|
||||
)))
|
||||
tls.with_root_certificates(root_cert_store)
|
||||
};
|
||||
let tls = tls.with_no_client_auth();
|
||||
|
||||
@@ -491,7 +468,7 @@ pub struct Certificate {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: native_tls::Certificate,
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
rustls: Vec<rustls::Certificate>,
|
||||
rustls: Vec<CertificateDer<'static>>,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: boring::x509::X509,
|
||||
}
|
||||
@@ -510,7 +487,7 @@ impl Certificate {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: native_tls_cert,
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
rustls: vec![rustls::Certificate(der)],
|
||||
rustls: vec![der.into()],
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: boring_tls_cert,
|
||||
})
|
||||
@@ -530,10 +507,8 @@ impl Certificate {
|
||||
|
||||
let mut pem = Cursor::new(pem);
|
||||
rustls_pemfile::certs(&mut pem)
|
||||
.collect::<io::Result<Vec<_>>>()
|
||||
.map_err(|_| error::tls("invalid certificates"))?
|
||||
.into_iter()
|
||||
.map(rustls::Certificate)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
@@ -554,19 +529,53 @@ impl Debug for Certificate {
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
#[derive(Debug)]
|
||||
struct InvalidCertsVerifier;
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
impl ServerCertVerifier for InvalidCertsVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &rustls::Certificate,
|
||||
_intermediates: &[rustls::Certificate],
|
||||
_server_name: &ServerName,
|
||||
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
_end_entity: &CertificateDer<'_>,
|
||||
_intermediates: &[CertificateDer<'_>],
|
||||
_server_name: &ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: SystemTime,
|
||||
_now: UnixTime,
|
||||
) -> Result<ServerCertVerified, TlsError> {
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, TlsError> {
|
||||
verify_tls12_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&rustls::crypto::ring::default_provider().signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, TlsError> {
|
||||
verify_tls13_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&rustls::crypto::ring::default_provider().signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
rustls::crypto::ring::default_provider()
|
||||
.signature_verification_algorithms
|
||||
.supported_schemes()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,16 @@ pub(crate) fn from_connection_url<B: TransportBuilder>(connection_url: &str) ->
|
||||
}
|
||||
|
||||
if let Some(password) = connection_url.password() {
|
||||
let credentials = Credentials::new(connection_url.username().into(), password.into());
|
||||
let percent_decode = |s: &str| {
|
||||
percent_encoding::percent_decode_str(s)
|
||||
.decode_utf8()
|
||||
.map(|cow| cow.into_owned())
|
||||
.map_err(error::connection)
|
||||
};
|
||||
let credentials = Credentials::new(
|
||||
percent_decode(connection_url.username())?,
|
||||
percent_decode(password)?,
|
||||
);
|
||||
builder = builder.credentials(credentials);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{
|
||||
fmt::{self, Debug},
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
sync::{Arc, OnceLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
@@ -10,7 +10,6 @@ use futures_util::{
|
||||
lock::Mutex,
|
||||
stream::{self, StreamExt},
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use super::{
|
||||
super::{client::AsyncSmtpConnection, Error},
|
||||
@@ -22,7 +21,7 @@ pub struct Pool<E: Executor> {
|
||||
config: PoolConfig,
|
||||
connections: Mutex<Vec<ParkedConnection>>,
|
||||
client: AsyncSmtpClient<E>,
|
||||
handle: OnceCell<E::Handle>,
|
||||
handle: OnceLock<E::Handle>,
|
||||
}
|
||||
|
||||
struct ParkedConnection {
|
||||
@@ -41,7 +40,7 @@ impl<E: Executor> Pool<E> {
|
||||
config,
|
||||
connections: Mutex::new(Vec::new()),
|
||||
client,
|
||||
handle: OnceCell::new(),
|
||||
handle: OnceLock::new(),
|
||||
});
|
||||
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#[cfg(feature = "pool")]
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{fmt::Debug, time::Duration};
|
||||
|
||||
#[cfg(feature = "pool")]
|
||||
use super::pool::sync_impl::Pool;
|
||||
@@ -38,6 +38,14 @@ impl Transport for SmtpTransport {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SmtpTransport {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut builder = f.debug_struct("SmtpTransport");
|
||||
builder.field("inner", &self.inner);
|
||||
builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl SmtpTransport {
|
||||
/// Simple and secure transport, using TLS connections to communicate with the SMTP server
|
||||
///
|
||||
@@ -373,6 +381,22 @@ mod tests {
|
||||
assert!(matches!(builder.info.tls, Tls::Wrapper(_)));
|
||||
assert_eq!(builder.info.server, "smtp.example.com");
|
||||
|
||||
let builder = SmtpTransport::from_url(
|
||||
"smtps://user%40example.com:pa$$word%3F%22!@smtp.example.com:465",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(builder.info.port, 465);
|
||||
assert_eq!(
|
||||
builder.info.credentials,
|
||||
Some(Credentials::new(
|
||||
"user@example.com".to_owned(),
|
||||
"pa$$word?\"!".to_owned()
|
||||
))
|
||||
);
|
||||
assert!(matches!(builder.info.tls, Tls::Wrapper(_)));
|
||||
assert_eq!(builder.info.server, "smtp.example.com");
|
||||
|
||||
let builder =
|
||||
SmtpTransport::from_url("smtp://username:password@smtp.example.com:587?tls=required")
|
||||
.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user