Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6babbce00 | ||
|
|
c9895c52de | ||
|
|
575492b9ed | ||
|
|
ad665cd01e | ||
|
|
e2ac5dadfb | ||
|
|
1c6a348eb8 | ||
|
|
e8b2498ad7 | ||
|
|
bf48bd6b96 | ||
|
|
fa6191983a | ||
|
|
ca405040ae | ||
|
|
f7a1b790df | ||
|
|
caff354cbf | ||
|
|
a81401c4cb | ||
|
|
54df594d6c | ||
|
|
cada01d039 | ||
|
|
0132bee59d | ||
|
|
acdf189717 | ||
|
|
3aea65315f | ||
|
|
9d3ebfab1a | ||
|
|
6fb69086fb | ||
|
|
dfdf3a61d2 | ||
|
|
e30ac2dbff | ||
|
|
22dca340a7 | ||
|
|
c7d1f35676 | ||
|
|
eebea56f16 | ||
|
|
851d6ae164 | ||
|
|
6f38e6b9a9 | ||
|
|
c40af78809 | ||
|
|
6d2e0d5046 |
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
@@ -13,16 +13,16 @@ env:
|
||||
|
||||
jobs:
|
||||
rustfmt:
|
||||
name: rustfmt / nightly-2023-06-22
|
||||
name: rustfmt / nightly-2024-09-01
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust
|
||||
run: |
|
||||
rustup default nightly-2023-06-22
|
||||
rustup default nightly-2024-09-01
|
||||
rustup component add rustfmt
|
||||
|
||||
- name: cargo fmt
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust
|
||||
run: |
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust
|
||||
run: rustup update --no-self-update stable
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust
|
||||
run: |
|
||||
@@ -112,12 +112,6 @@ jobs:
|
||||
- name: Install dkimverify
|
||||
run: sudo apt -y install python3-dkim
|
||||
|
||||
- name: Work around early dependencies MSRV bump
|
||||
run: |
|
||||
cargo update -p anstyle --precise 1.0.2
|
||||
cargo update -p clap --precise 4.3.24
|
||||
cargo update -p clap_lex --precise 0.5.0
|
||||
|
||||
- name: Test with no default features
|
||||
run: cargo test --no-default-features
|
||||
|
||||
@@ -134,7 +128,7 @@ jobs:
|
||||
# name: Coverage
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: actions-rs/toolchain@v1
|
||||
# with:
|
||||
# toolchain: nightly
|
||||
|
||||
94
CHANGELOG.md
94
CHANGELOG.md
@@ -1,3 +1,97 @@
|
||||
<a name="v0.11.9"></a>
|
||||
### v0.11.9 (2024-09-13)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
* Fix feature gate for `accept_invalid_hostnames` for rustls ([#988])
|
||||
* Fix parsing `Mailbox` with trailing spaces ([#986])
|
||||
|
||||
#### Misc
|
||||
|
||||
* Bump `rustls-native-certs` to v0.8 ([#992])
|
||||
* Make getting started example in readme complete ([#990])
|
||||
|
||||
[#988]: https://github.com/lettre/lettre/pull/988
|
||||
[#986]: https://github.com/lettre/lettre/pull/986
|
||||
[#990]: https://github.com/lettre/lettre/pull/990
|
||||
[#992]: https://github.com/lettre/lettre/pull/992
|
||||
|
||||
<a name="v0.11.8"></a>
|
||||
### v0.11.8 (2024-09-03)
|
||||
|
||||
#### Features
|
||||
|
||||
* Add mTLS support ([#974])
|
||||
* Implement `accept_invalid_hostnames` for rustls ([#977])
|
||||
* Provide certificate chain for peer certificates when using `rustls` or `boring-tls` ([#976])
|
||||
|
||||
#### Changes
|
||||
|
||||
* Make `HeaderName` comparisons via `PartialEq` case insensitive ([#980])
|
||||
|
||||
#### Misc
|
||||
|
||||
* Fix clippy warnings ([#979])
|
||||
* Replace manual impl of `#[non_exhaustive]` for `InvalidHeaderName` ([#981])
|
||||
|
||||
[#974]: https://github.com/lettre/lettre/pull/974
|
||||
[#976]: https://github.com/lettre/lettre/pull/976
|
||||
[#977]: https://github.com/lettre/lettre/pull/977
|
||||
[#980]: https://github.com/lettre/lettre/pull/980
|
||||
[#981]: https://github.com/lettre/lettre/pull/981
|
||||
|
||||
<a name="v0.11.7"></a>
|
||||
### v0.11.7 (2024-04-23)
|
||||
|
||||
#### Misc
|
||||
|
||||
* Bump `hostname` to v0.4 ([#956])
|
||||
* Fix `tracing` message consistency ([#960])
|
||||
* Bump minimum required `rustls` to v0.23.5 ([#958])
|
||||
* Dropped use of `ref` syntax in the entire project ([#959])
|
||||
|
||||
[#956]: https://github.com/lettre/lettre/pull/956
|
||||
[#958]: https://github.com/lettre/lettre/pull/958
|
||||
[#959]: https://github.com/lettre/lettre/pull/959
|
||||
[#960]: https://github.com/lettre/lettre/pull/960
|
||||
|
||||
<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>
|
||||
### v0.11.4 (2024-01-28)
|
||||
|
||||
|
||||
1314
Cargo.lock
generated
1314
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
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.4"
|
||||
version = "0.11.9"
|
||||
description = "Email client"
|
||||
readme = "README.md"
|
||||
homepage = "https://lettre.rs"
|
||||
@@ -20,7 +20,7 @@ maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
chumsky = "0.9"
|
||||
idna = "0.5"
|
||||
idna = "1"
|
||||
tracing = { version = "0.1.16", default-features = false, features = ["std"], optional = true } # feature
|
||||
|
||||
# builder
|
||||
@@ -29,7 +29,7 @@ mime = { version = "0.3.4", optional = true }
|
||||
fastrand = { version = "2.0", optional = true }
|
||||
quoted_printable = { version = "0.5", optional = true }
|
||||
base64 = { version = "0.22", optional = true }
|
||||
email-encoding = { version = "0.2", optional = true }
|
||||
email-encoding = { version = "0.3", optional = true }
|
||||
|
||||
# file transport
|
||||
uuid = { version = "1", features = ["v4"], optional = true }
|
||||
@@ -38,16 +38,17 @@ serde_json = { version = "1", optional = true }
|
||||
|
||||
# smtp-transport
|
||||
nom = { version = "7", optional = true }
|
||||
hostname = { version = "0.3", optional = true } # feature
|
||||
hostname = { version = "0.4", 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.22.1", optional = true }
|
||||
rustls = { version = "0.23.5", default-features = false, features = ["ring", "logging", "std", "tls12"], optional = true }
|
||||
rustls-pemfile = { version = "2", optional = true }
|
||||
rustls-native-certs = { version = "0.7", optional = true }
|
||||
rustls-native-certs = { version = "0.8", optional = true }
|
||||
rustls-pki-types = { version = "1.7", optional = true }
|
||||
webpki-roots = { version = "0.26", optional = true }
|
||||
boring = { version = "4", optional = true }
|
||||
|
||||
@@ -58,13 +59,12 @@ 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.25", optional = true }
|
||||
futures-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"], 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.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 }
|
||||
|
||||
## dkim
|
||||
@@ -108,13 +108,12 @@ smtp-transport = ["dep:base64", "dep:nom", "dep:socket2", "dep:url", "dep:percen
|
||||
|
||||
pool = ["dep:futures-util"]
|
||||
|
||||
rustls-tls = ["dep:webpki-roots", "dep:rustls", "dep:rustls-pemfile"]
|
||||
rustls-tls = ["dep:webpki-roots", "dep:rustls", "dep:rustls-pemfile", "dep:rustls-pki-types"]
|
||||
|
||||
boring-tls = ["dep:boring"]
|
||||
|
||||
# async
|
||||
async-std1 = ["dep:async-std", "dep:async-trait", "dep:futures-io", "dep:futures-util"]
|
||||
#async-std1-native-tls = ["async-std1", "native-tls", "dep:async-native-tls"]
|
||||
async-std1-rustls-tls = ["async-std1", "rustls-tls", "dep:futures-rustls"]
|
||||
tokio1 = ["dep:tokio1_crate", "dep:async-trait", "dep:futures-io", "dep:futures-util"]
|
||||
tokio1-native-tls = ["tokio1", "native-tls", "dep:tokio1_native_tls_crate"]
|
||||
@@ -123,6 +122,9 @@ tokio1-boring-tls = ["tokio1", "boring-tls", "dep:tokio1_boring"]
|
||||
|
||||
dkim = ["dep:base64", "dep:sha2", "dep:rsa", "dep:ed25519-dalek"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(lettre_ignore_tls_mismatch)'] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs", "--cfg", "lettre_ignore_tls_mismatch"]
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -1,5 +1,5 @@
|
||||
Copyright (c) 2014-2022 Alexis Mousset <contact@amousset.me>
|
||||
Copyright (c) 2019-2022 Paolo Barbolini <paolo@paolo565.org>
|
||||
Copyright (c) 2014-2024 Alexis Mousset <contact@amousset.me>
|
||||
Copyright (c) 2019-2024 Paolo Barbolini <paolo@paolo565.org>
|
||||
Copyright (c) 2018 K. <kayo@illumium.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
|
||||
42
README.md
42
README.md
@@ -28,8 +28,8 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://deps.rs/crate/lettre/0.11.4">
|
||||
<img src="https://deps.rs/crate/lettre/0.11.4/status.svg"
|
||||
<a href="https://deps.rs/crate/lettre/0.11.9">
|
||||
<img src="https://deps.rs/crate/lettre/0.11.9/status.svg"
|
||||
alt="dependency status" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -71,27 +71,29 @@ use lettre::message::header::ContentType;
|
||||
use lettre::transport::smtp::authentication::Credentials;
|
||||
use lettre::{Message, SmtpTransport, Transport};
|
||||
|
||||
let email = Message::builder()
|
||||
.from("NoBody <nobody@domain.tld>".parse().unwrap())
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
fn main() {
|
||||
let email = Message::builder()
|
||||
.from("NoBody <nobody@domain.tld>".parse().unwrap())
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
let creds = Credentials::new("smtp_username".to_owned(), "smtp_password".to_owned());
|
||||
let creds = Credentials::new("smtp_username".to_owned(), "smtp_password".to_owned());
|
||||
|
||||
// Open a remote connection to gmail
|
||||
let mailer = SmtpTransport::relay("smtp.gmail.com")
|
||||
.unwrap()
|
||||
.credentials(creds)
|
||||
.build();
|
||||
// Open a remote connection to gmail
|
||||
let mailer = SmtpTransport::relay("smtp.gmail.com")
|
||||
.unwrap()
|
||||
.credentials(creds)
|
||||
.build();
|
||||
|
||||
// Send the email
|
||||
match mailer.send(&email) {
|
||||
Ok(_) => println!("Email sent successfully!"),
|
||||
Err(e) => panic!("Could not send email: {e:?}"),
|
||||
// Send the email
|
||||
match mailer.send(&email) {
|
||||
Ok(_) => println!("Email sent successfully!"),
|
||||
Err(e) => panic!("Could not send email: {e:?}"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ fn main() {
|
||||
|
||||
// Plaintext connection which MUST then successfully upgrade to TLS via STARTTLS
|
||||
{
|
||||
tracing::info!("Trying to establish a plaintext connection to {} and then updating it via the SMTP STARTTLS extension", smtp_host);
|
||||
tracing::info!("Trying to establish a plaintext connection to {} and then upgrading it via the SMTP STARTTLS extension", smtp_host);
|
||||
|
||||
let transport = SmtpTransport::starttls_relay(&smtp_host)
|
||||
.expect("build SmtpTransport::starttls_relay")
|
||||
|
||||
@@ -14,11 +14,71 @@ pub struct Envelope {
|
||||
/// The envelope recipient's addresses
|
||||
///
|
||||
/// This can not be empty.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(deserialize_with = "serde_forward_path::deserialize")
|
||||
)]
|
||||
forward_path: Vec<Address>,
|
||||
/// The envelope sender address
|
||||
reverse_path: Option<Address>,
|
||||
}
|
||||
|
||||
/// just like the default implementation to deserialize `Vec<Address>` but it
|
||||
/// forbids **de**serializing empty lists
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde_forward_path {
|
||||
use super::Address;
|
||||
/// dummy type required for serde
|
||||
/// see example: https://serde.rs/deserialize-map.html
|
||||
struct CustomVisitor;
|
||||
impl<'de> serde::de::Visitor<'de> for CustomVisitor {
|
||||
type Value = Vec<Address>;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter.write_str("a non-empty list of recipient addresses")
|
||||
}
|
||||
|
||||
fn visit_seq<S>(self, mut access: S) -> Result<Self::Value, S::Error>
|
||||
where
|
||||
S: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut seq: Vec<Address> = Vec::with_capacity(access.size_hint().unwrap_or(0));
|
||||
while let Some(key) = access.next_element()? {
|
||||
seq.push(key);
|
||||
}
|
||||
if seq.is_empty() {
|
||||
Err(serde::de::Error::invalid_length(seq.len(), &self))
|
||||
} else {
|
||||
Ok(seq)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Address>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_seq(CustomVisitor {})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn deserializing_empty_recipient_list_returns_error() {
|
||||
assert!(
|
||||
serde_json::from_str::<crate::address::Envelope>(r#"{"forward_path": []}"#)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn deserializing_non_empty_recipient_list_is_ok() {
|
||||
serde_json::from_str::<crate::address::Envelope>(
|
||||
r#"{ "forward_path": [ {"user":"foo", "domain":"example.com"} ] }"#,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Envelope {
|
||||
/// Creates a new envelope, which may fail if `to` is empty.
|
||||
///
|
||||
|
||||
@@ -134,7 +134,7 @@ impl Executor for Tokio1Executor {
|
||||
#[allow(clippy::match_single_binding)]
|
||||
let tls_parameters = match tls {
|
||||
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
|
||||
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
|
||||
Tls::Wrapper(tls_parameters) => Some(tls_parameters.clone()),
|
||||
_ => None,
|
||||
};
|
||||
#[allow(unused_mut)]
|
||||
@@ -149,12 +149,12 @@ impl Executor for Tokio1Executor {
|
||||
|
||||
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
|
||||
match tls {
|
||||
Tls::Opportunistic(ref tls_parameters) => {
|
||||
Tls::Opportunistic(tls_parameters) => {
|
||||
if conn.can_starttls() {
|
||||
conn.starttls(tls_parameters.clone(), hello_name).await?;
|
||||
}
|
||||
}
|
||||
Tls::Required(ref tls_parameters) => {
|
||||
Tls::Required(tls_parameters) => {
|
||||
conn.starttls(tls_parameters.clone(), hello_name).await?;
|
||||
}
|
||||
_ => (),
|
||||
@@ -230,8 +230,8 @@ impl Executor for AsyncStd1Executor {
|
||||
) -> Result<AsyncSmtpConnection, Error> {
|
||||
#[allow(clippy::match_single_binding)]
|
||||
let tls_parameters = match tls {
|
||||
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
|
||||
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
Tls::Wrapper(tls_parameters) => Some(tls_parameters.clone()),
|
||||
_ => None,
|
||||
};
|
||||
#[allow(unused_mut)]
|
||||
@@ -243,14 +243,14 @@ impl Executor for AsyncStd1Executor {
|
||||
)
|
||||
.await?;
|
||||
|
||||
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
match tls {
|
||||
Tls::Opportunistic(ref tls_parameters) => {
|
||||
Tls::Opportunistic(tls_parameters) => {
|
||||
if conn.can_starttls() {
|
||||
conn.starttls(tls_parameters.clone(), hello_name).await?;
|
||||
}
|
||||
}
|
||||
Tls::Required(ref tls_parameters) => {
|
||||
Tls::Required(tls_parameters) => {
|
||||
conn.starttls(tls_parameters.clone(), hello_name).await?;
|
||||
}
|
||||
_ => (),
|
||||
|
||||
18
src/lib.rs
18
src/lib.rs
@@ -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.4")]
|
||||
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.9")]
|
||||
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
|
||||
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -174,21 +174,7 @@ mod compiletime_checks {
|
||||
If you'd like to use `boring-tls` make sure that the `rustls-tls` feature hasn't been enabled by mistake.
|
||||
Make sure to apply the same to any of your crate dependencies that use the `lettre` crate.");
|
||||
|
||||
/*
|
||||
#[cfg(all(
|
||||
feature = "async-std1",
|
||||
feature = "native-tls",
|
||||
not(feature = "async-std1-native-tls")
|
||||
))]
|
||||
compile_error!("Lettre is being built with the `async-std1` and the `native-tls` features, but the `async-std1-native-tls` feature hasn't been turned on.
|
||||
If you'd like to use rustls make sure that the `native-tls` hasn't been enabled by mistake (you may need to import lettre without default features)
|
||||
If you're building a library which depends on lettre import it without default features and enable just the features you need.");
|
||||
*/
|
||||
#[cfg(all(
|
||||
feature = "async-std1",
|
||||
feature = "native-tls",
|
||||
not(feature = "async-std1-native-tls")
|
||||
))]
|
||||
#[cfg(all(feature = "async-std1", feature = "native-tls",))]
|
||||
compile_error!("Lettre is being built with the `async-std1` and the `native-tls` features, but the async-std integration doesn't support native-tls yet.
|
||||
If you'd like to work on the issue please take a look at https://github.com/lettre/lettre/issues/576.
|
||||
If you were trying to opt into `rustls-tls` and did not activate `native-tls`, disable the default-features of lettre in `Cargo.toml` and manually add the required features.
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::{
|
||||
borrow::Cow,
|
||||
error::Error as StdError,
|
||||
fmt::{self, Display},
|
||||
iter::IntoIterator,
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use email_encoding::headers::EmailWriter;
|
||||
use email_encoding::headers::writer::EmailWriter;
|
||||
|
||||
use super::{Header, HeaderName, HeaderValue};
|
||||
use crate::BoxError;
|
||||
@@ -38,10 +38,10 @@ impl ContentDisposition {
|
||||
let mut encoded_value = String::new();
|
||||
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_char(';').expect("writing `;` returned an error");
|
||||
w.optional_breakpoint();
|
||||
w.space();
|
||||
|
||||
email_encoding::headers::rfc2231::encode("filename", file_name, &mut w)
|
||||
.expect("some Write implementation returned an error");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use email_encoding::headers::EmailWriter;
|
||||
use email_encoding::headers::writer::EmailWriter;
|
||||
|
||||
use super::{Header, HeaderName, HeaderValue};
|
||||
use crate::{
|
||||
@@ -31,7 +31,7 @@ macro_rules! mailbox_header {
|
||||
let mut encoded_value = String::new();
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ macro_rules! mailboxes_header {
|
||||
let mut encoded_value = String::new();
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use email_encoding::headers::EmailWriter;
|
||||
use email_encoding::headers::writer::EmailWriter;
|
||||
|
||||
pub use self::{
|
||||
content::*,
|
||||
@@ -124,22 +124,18 @@ impl Headers {
|
||||
}
|
||||
|
||||
pub(crate) fn find_header(&self, name: &str) -> Option<&HeaderValue> {
|
||||
self.headers
|
||||
.iter()
|
||||
.find(|value| name.eq_ignore_ascii_case(&value.name))
|
||||
self.headers.iter().find(|value| name == value.name)
|
||||
}
|
||||
|
||||
fn find_header_mut(&mut self, name: &str) -> Option<&mut HeaderValue> {
|
||||
self.headers
|
||||
.iter_mut()
|
||||
.find(|value| name.eq_ignore_ascii_case(&value.name))
|
||||
self.headers.iter_mut().find(|value| name == value.name)
|
||||
}
|
||||
|
||||
fn find_header_index(&self, name: &str) -> Option<usize> {
|
||||
self.headers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_i, value)| name.eq_ignore_ascii_case(&value.name))
|
||||
.find(|(_i, value)| name == value.name)
|
||||
.map(|(i, _)| i)
|
||||
}
|
||||
}
|
||||
@@ -161,18 +157,9 @@ impl Display for Headers {
|
||||
/// A possible error when converting a `HeaderName` from another type.
|
||||
// comes from `http` crate
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[derive(Clone)]
|
||||
pub struct InvalidHeaderName {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
impl fmt::Debug for InvalidHeaderName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("InvalidHeaderName")
|
||||
// skip _priv noise
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct InvalidHeaderName;
|
||||
|
||||
impl fmt::Display for InvalidHeaderName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@@ -189,14 +176,11 @@ pub struct HeaderName(Cow<'static, str>);
|
||||
impl HeaderName {
|
||||
/// Creates a new header name
|
||||
pub fn new_from_ascii(ascii: String) -> Result<Self, InvalidHeaderName> {
|
||||
if !ascii.is_empty()
|
||||
&& ascii.len() <= 76
|
||||
&& ascii.is_ascii()
|
||||
&& !ascii.contains(|c| c == ':' || c == ' ')
|
||||
if !ascii.is_empty() && ascii.len() <= 76 && ascii.is_ascii() && !ascii.contains([':', ' '])
|
||||
{
|
||||
Ok(Self(Cow::Owned(ascii)))
|
||||
} else {
|
||||
Err(InvalidHeaderName { _priv: () })
|
||||
Err(InvalidHeaderName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,23 +241,19 @@ impl AsRef<str> for HeaderName {
|
||||
|
||||
impl PartialEq<HeaderName> for HeaderName {
|
||||
fn eq(&self, other: &HeaderName) -> bool {
|
||||
let s1: &str = self.as_ref();
|
||||
let s2: &str = other.as_ref();
|
||||
s1 == s2
|
||||
self.eq_ignore_ascii_case(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for HeaderName {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
let s: &str = self.as_ref();
|
||||
s == *other
|
||||
self.eq_ignore_ascii_case(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<HeaderName> for &str {
|
||||
fn eq(&self, other: &HeaderName) -> bool {
|
||||
let s: &str = other.as_ref();
|
||||
*self == s
|
||||
self.eq_ignore_ascii_case(other)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,7 +328,7 @@ impl<'a> HeaderValueEncoder<'a> {
|
||||
|
||||
fn new(name: &str, writer: &'a mut dyn Write) -> Self {
|
||||
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 {
|
||||
writer,
|
||||
@@ -467,6 +447,60 @@ mod tests {
|
||||
let _ = HeaderName::new_from_ascii_str("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headername_headername_eq() {
|
||||
assert_eq!(
|
||||
HeaderName::new_from_ascii_str("From"),
|
||||
HeaderName::new_from_ascii_str("From")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headername_str_eq() {
|
||||
assert_eq!(HeaderName::new_from_ascii_str("From"), "From");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_headername_eq() {
|
||||
assert_eq!("From", HeaderName::new_from_ascii_str("From"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headername_headername_eq_case_insensitive() {
|
||||
assert_eq!(
|
||||
HeaderName::new_from_ascii_str("From"),
|
||||
HeaderName::new_from_ascii_str("from")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headername_str_eq_case_insensitive() {
|
||||
assert_eq!(HeaderName::new_from_ascii_str("From"), "from");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_headername_eq_case_insensitive() {
|
||||
assert_eq!("from", HeaderName::new_from_ascii_str("From"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headername_headername_ne() {
|
||||
assert_ne!(
|
||||
HeaderName::new_from_ascii_str("From"),
|
||||
HeaderName::new_from_ascii_str("To")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headername_str_ne() {
|
||||
assert_ne!(HeaderName::new_from_ascii_str("From"), "To");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_headername_ne() {
|
||||
assert_ne!("From", HeaderName::new_from_ascii_str("To"));
|
||||
}
|
||||
|
||||
// names taken randomly from https://it.wikipedia.org/wiki/Pinco_Pallino
|
||||
|
||||
#[test]
|
||||
@@ -612,17 +646,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(),
|
||||
));
|
||||
|
||||
// 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!(
|
||||
headers.to_string(),
|
||||
concat!(
|
||||
"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",
|
||||
" =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n",
|
||||
" =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>,\r\n",
|
||||
" =?utf-8?b?U2XDoW4gw5MgUnVkYcOt?= <sean@example.com>\r\n",
|
||||
" =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
|
||||
" =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -687,9 +718,6 @@ mod tests {
|
||||
"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!(
|
||||
headers.to_string(),
|
||||
concat!(
|
||||
@@ -699,8 +727,8 @@ mod tests {
|
||||
"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",
|
||||
" =?utf-8?b?INCY0LLQsNC90L7QstC40Yc=?= <ivanov@example.com>,\r\n",
|
||||
" =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>,\r\n",
|
||||
" =?utf-8?b?U2XDoW4gw5MgUnVkYcOt?= <sean@example.com>\r\n",
|
||||
" =?utf-8?b?SsSBbmlzIELEk3J6acWGxaE=?= <janis@example.com>, =?utf-8?b?U2U=?=\r\n",
|
||||
" =?utf-8?b?w6FuIMOTIFJ1ZGHDrQ==?= <sean@example.com>\r\n",
|
||||
"From: Someone <somewhere@example.com>\r\n",
|
||||
"Content-Transfer-Encoding: quoted-printable\r\n",
|
||||
)
|
||||
|
||||
@@ -170,7 +170,9 @@ fn phrase() -> impl Parser<char, Vec<char>, Error = Cheap<char>> {
|
||||
// mailbox = name-addr / addr-spec
|
||||
pub(crate) fn mailbox() -> impl Parser<char, (Option<String>, (String, String)), Error = Cheap<char>>
|
||||
{
|
||||
choice((name_addr(), addr_spec().map(|addr| (None, addr)))).then_ignore(end())
|
||||
choice((name_addr(), addr_spec().map(|addr| (None, addr))))
|
||||
.padded()
|
||||
.then_ignore(end())
|
||||
}
|
||||
|
||||
// name-addr = [display-name] angle-addr
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
};
|
||||
|
||||
use chumsky::prelude::*;
|
||||
use email_encoding::headers::EmailWriter;
|
||||
use email_encoding::headers::writer::EmailWriter;
|
||||
|
||||
use super::parsers;
|
||||
use crate::address::{Address, AddressError};
|
||||
@@ -72,7 +72,7 @@ impl Mailbox {
|
||||
pub(crate) fn encode(&self, w: &mut EmailWriter<'_>) -> FmtResult {
|
||||
if let Some(name) = &self.name {
|
||||
email_encoding::headers::quoted_string::encode(name, w)?;
|
||||
w.optional_breakpoint();
|
||||
w.space();
|
||||
w.write_char('<')?;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ impl Mailbox {
|
||||
|
||||
impl Display for Mailbox {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
if let Some(ref name) = self.name {
|
||||
if let Some(name) = &self.name {
|
||||
let name = name.trim();
|
||||
if !name.is_empty() {
|
||||
write_word(f, name)?;
|
||||
@@ -261,7 +261,7 @@ impl Mailboxes {
|
||||
for mailbox in self.iter() {
|
||||
if !mem::take(&mut first) {
|
||||
w.write_char(',')?;
|
||||
w.optional_breakpoint();
|
||||
w.space();
|
||||
}
|
||||
|
||||
mailbox.encode(w)?;
|
||||
@@ -444,8 +444,6 @@ fn write_quoted_string_char(f: &mut Formatter<'_>, c: char) -> FmtResult {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::convert::TryInto;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::Mailbox;
|
||||
@@ -558,6 +556,14 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_address_only_trim() {
|
||||
assert_eq!(
|
||||
" kayo@example.com ".parse(),
|
||||
Ok(Mailbox::new(None, "kayo@example.com".parse().unwrap()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_address_with_name() {
|
||||
assert_eq!(
|
||||
@@ -569,6 +575,17 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_address_with_name_trim() {
|
||||
assert_eq!(
|
||||
" K. <kayo@example.com> ".parse(),
|
||||
Ok(Mailbox::new(
|
||||
Some("K.".into()),
|
||||
"kayo@example.com".parse().unwrap()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_address_with_empty_name() {
|
||||
assert_eq!(
|
||||
@@ -580,7 +597,7 @@ mod test {
|
||||
#[test]
|
||||
fn parse_address_with_empty_name_trim() {
|
||||
assert_eq!(
|
||||
" <kayo@example.com>".parse(),
|
||||
" <kayo@example.com> ".parse(),
|
||||
Ok(Mailbox::new(None, "kayo@example.com".parse().unwrap()))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -420,7 +420,6 @@ mod test {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::message::header;
|
||||
|
||||
#[test]
|
||||
fn single_part_binary() {
|
||||
|
||||
@@ -54,7 +54,7 @@ impl fmt::Debug for Error {
|
||||
|
||||
builder.field("kind", &self.inner.kind);
|
||||
|
||||
if let Some(ref source) = self.inner.source {
|
||||
if let Some(source) = &self.inner.source {
|
||||
builder.field("source", source);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ impl fmt::Display for Error {
|
||||
Kind::Envelope => f.write_str("internal client error")?,
|
||||
};
|
||||
|
||||
if let Some(ref e) = self.inner.source {
|
||||
if let Some(e) = &self.inner.source {
|
||||
write!(f, ": {e}")?;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
//!
|
||||
//! * a service from your Cloud or hosting provider
|
||||
//! * an email server ([MTA] for Mail Transfer Agent, like Postfix or Exchange), running either
|
||||
//! locally on your servers or accessible over the network
|
||||
//! locally on your servers or accessible over the network
|
||||
//! * a dedicated external service, like Mailchimp, Mailgun, etc.
|
||||
//!
|
||||
//! In most cases, the best option is to:
|
||||
|
||||
@@ -52,7 +52,7 @@ impl fmt::Debug for Error {
|
||||
|
||||
builder.field("kind", &self.inner.kind);
|
||||
|
||||
if let Some(ref source) = self.inner.source {
|
||||
if let Some(source) = &self.inner.source {
|
||||
builder.field("source", source);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ impl fmt::Display for Error {
|
||||
Kind::Client => f.write_str("internal client error")?,
|
||||
};
|
||||
|
||||
if let Some(ref e) = self.inner.source {
|
||||
if let Some(e) = &self.inner.source {
|
||||
write!(f, ": {e}")?;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ where
|
||||
#[cfg(any(
|
||||
feature = "tokio1-native-tls",
|
||||
feature = "tokio1-rustls-tls",
|
||||
feature = "async-std1-native-tls",
|
||||
feature = "async-std1-rustls-tls"
|
||||
))]
|
||||
#[cfg_attr(
|
||||
@@ -117,7 +116,6 @@ where
|
||||
#[cfg(any(
|
||||
feature = "tokio1-native-tls",
|
||||
feature = "tokio1-rustls-tls",
|
||||
feature = "async-std1-native-tls",
|
||||
feature = "async-std1-rustls-tls"
|
||||
))]
|
||||
#[cfg_attr(
|
||||
@@ -353,7 +351,6 @@ impl AsyncSmtpTransportBuilder {
|
||||
#[cfg(any(
|
||||
feature = "tokio1-native-tls",
|
||||
feature = "tokio1-rustls-tls",
|
||||
feature = "async-std1-native-tls",
|
||||
feature = "async-std1-rustls-tls"
|
||||
))]
|
||||
#[cfg_attr(
|
||||
|
||||
@@ -373,4 +373,10 @@ impl AsyncSmtpConnection {
|
||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||
self.stream.get_ref().peer_certificate()
|
||||
}
|
||||
|
||||
/// All the X509 certificates of the chain (DER encoded)
|
||||
#[cfg(any(feature = "rustls-tls", feature = "boring-tls"))]
|
||||
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||
self.stream.get_ref().certificate_chain()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
use async_native_tls::TlsStream as AsyncStd1TlsStream;
|
||||
#[cfg(feature = "async-std1")]
|
||||
use async_std::net::{TcpStream as AsyncStd1TcpStream, ToSocketAddrs as AsyncStd1ToSocketAddrs};
|
||||
use futures_io::{
|
||||
@@ -36,7 +34,6 @@ use tokio1_rustls::client::TlsStream as Tokio1RustlsTlsStream;
|
||||
feature = "tokio1-native-tls",
|
||||
feature = "tokio1-rustls-tls",
|
||||
feature = "tokio1-boring-tls",
|
||||
feature = "async-std1-native-tls",
|
||||
feature = "async-std1-rustls-tls"
|
||||
))]
|
||||
use super::InnerTlsParameters;
|
||||
@@ -86,9 +83,6 @@ enum InnerAsyncNetworkStream {
|
||||
#[cfg(feature = "async-std1")]
|
||||
AsyncStd1Tcp(AsyncStd1TcpStream),
|
||||
/// Encrypted Tokio 1.x TCP stream
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
AsyncStd1NativeTls(AsyncStd1TlsStream<AsyncStd1TcpStream>),
|
||||
/// Encrypted Tokio 1.x TCP stream
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
AsyncStd1RustlsTls(AsyncStd1RustlsTlsStream<AsyncStd1TcpStream>),
|
||||
/// Can't be built
|
||||
@@ -106,23 +100,21 @@ impl AsyncNetworkStream {
|
||||
|
||||
/// Returns peer's address
|
||||
pub fn peer_addr(&self) -> IoResult<SocketAddr> {
|
||||
match self.inner {
|
||||
match &self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(ref s) => s.peer_addr(),
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(s) => s.peer_addr(),
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(ref s) => {
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(s) => {
|
||||
s.get_ref().get_ref().get_ref().peer_addr()
|
||||
}
|
||||
#[cfg(feature = "tokio1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(ref s) => s.get_ref().0.peer_addr(),
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => s.get_ref().0.peer_addr(),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(ref s) => s.get_ref().peer_addr(),
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => s.get_ref().peer_addr(),
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref s) => s.peer_addr(),
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(ref s) => s.get_ref().peer_addr(),
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => s.peer_addr(),
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref s) => s.get_ref().0.peer_addr(),
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => s.get_ref().0.peer_addr(),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Err(IoError::new(
|
||||
@@ -288,16 +280,13 @@ impl AsyncNetworkStream {
|
||||
.map_err(error::connection)?;
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(all(
|
||||
feature = "async-std1",
|
||||
not(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))
|
||||
))]
|
||||
#[cfg(all(feature = "async-std1", not(feature = "async-std1-rustls-tls")))]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
||||
let _ = tls_parameters;
|
||||
panic!("Trying to upgrade an AsyncNetworkStream without having enabled either the async-std1-native-tls or the async-std1-rustls-tls feature");
|
||||
panic!("Trying to upgrade an AsyncNetworkStream without having enabled the async-std1-rustls-tls feature");
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
||||
// get owned TcpStream
|
||||
let tcp_stream = mem::replace(&mut self.inner, InnerAsyncNetworkStream::None);
|
||||
@@ -385,11 +374,7 @@ impl AsyncNetworkStream {
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[cfg(any(
|
||||
feature = "async-std1-native-tls",
|
||||
feature = "async-std1-rustls-tls",
|
||||
feature = "async-std1-boring-tls"
|
||||
))]
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
async fn upgrade_asyncstd1_tls(
|
||||
tcp_stream: AsyncStd1TcpStream,
|
||||
mut tls_parameters: TlsParameters,
|
||||
@@ -400,22 +385,6 @@ impl AsyncNetworkStream {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerTlsParameters::NativeTls(connector) => {
|
||||
panic!("native-tls isn't supported with async-std yet. See https://github.com/lettre/lettre/pull/531#issuecomment-757893531");
|
||||
|
||||
/*
|
||||
#[cfg(not(feature = "async-std1-native-tls"))]
|
||||
panic!("built without the async-std1-native-tls feature");
|
||||
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
return {
|
||||
use async_native_tls::TlsConnector;
|
||||
|
||||
// TODO: fix
|
||||
let connector: TlsConnector = todo!();
|
||||
// let connector = TlsConnector::from(connector);
|
||||
let stream = connector.connect(&domain, tcp_stream).await?;
|
||||
Ok(InnerAsyncNetworkStream::AsyncStd1NativeTls(stream))
|
||||
};
|
||||
*/
|
||||
}
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
InnerTlsParameters::RustlsTls(config) => {
|
||||
@@ -445,7 +414,7 @@ impl AsyncNetworkStream {
|
||||
}
|
||||
|
||||
pub fn is_encrypted(&self) -> bool {
|
||||
match self.inner {
|
||||
match &self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(_) => false,
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
@@ -456,14 +425,54 @@ impl AsyncNetworkStream {
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(_) => true,
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => false,
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(_) => true,
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(_) => true,
|
||||
InnerAsyncNetworkStream::None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||
match &self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(_) => {
|
||||
Err(error::client("Connection is not encrypted"))
|
||||
}
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(_) => panic!("Unsupported"),
|
||||
#[cfg(feature = "tokio1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(stream) => Ok(stream
|
||||
.get_ref()
|
||||
.1
|
||||
.peer_certificates()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.to_vec())
|
||||
.collect()),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(stream) => Ok(stream
|
||||
.ssl()
|
||||
.peer_cert_chain()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.to_der().map_err(error::tls))
|
||||
.collect::<Result<Vec<_>, _>>()?),
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
||||
Err(error::client("Connection is not encrypted"))
|
||||
}
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream) => Ok(stream
|
||||
.get_ref()
|
||||
.1
|
||||
.peer_certificates()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.to_vec())
|
||||
.collect()),
|
||||
InnerAsyncNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||
match &self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
@@ -498,8 +507,6 @@ impl AsyncNetworkStream {
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
||||
Err(error::client("Connection is not encrypted"))
|
||||
}
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(t) => panic!("Unsupported"),
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream) => Ok(stream
|
||||
.get_ref()
|
||||
@@ -520,9 +527,9 @@ impl FuturesAsyncRead for AsyncNetworkStream {
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<IoResult<usize>> {
|
||||
match self.inner {
|
||||
match &mut self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => {
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(s) => {
|
||||
let mut b = Tokio1ReadBuf::new(buf);
|
||||
match Pin::new(s).poll_read(cx, &mut b) {
|
||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
|
||||
@@ -531,7 +538,7 @@ impl FuturesAsyncRead for AsyncNetworkStream {
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => {
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(s) => {
|
||||
let mut b = Tokio1ReadBuf::new(buf);
|
||||
match Pin::new(s).poll_read(cx, &mut b) {
|
||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
|
||||
@@ -540,7 +547,7 @@ impl FuturesAsyncRead for AsyncNetworkStream {
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "tokio1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => {
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => {
|
||||
let mut b = Tokio1ReadBuf::new(buf);
|
||||
match Pin::new(s).poll_read(cx, &mut b) {
|
||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
|
||||
@@ -549,7 +556,7 @@ impl FuturesAsyncRead for AsyncNetworkStream {
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => {
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => {
|
||||
let mut b = Tokio1ReadBuf::new(buf);
|
||||
match Pin::new(s).poll_read(cx, &mut b) {
|
||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
|
||||
@@ -558,15 +565,9 @@ impl FuturesAsyncRead for AsyncNetworkStream {
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_read(cx, buf),
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(ref mut s) => {
|
||||
Pin::new(s).poll_read(cx, buf)
|
||||
}
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_read(cx, buf),
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref mut s) => {
|
||||
Pin::new(s).poll_read(cx, buf)
|
||||
}
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_read(cx, buf),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Poll::Ready(Ok(0))
|
||||
@@ -581,25 +582,19 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<IoResult<usize>> {
|
||||
match self.inner {
|
||||
match &mut self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "tokio1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(ref mut s) => {
|
||||
Pin::new(s).poll_write(cx, buf)
|
||||
}
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref mut s) => {
|
||||
Pin::new(s).poll_write(cx, buf)
|
||||
}
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Poll::Ready(Ok(0))
|
||||
@@ -608,21 +603,19 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
|
||||
match self.inner {
|
||||
match &mut self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "tokio1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Poll::Ready(Ok(()))
|
||||
@@ -631,21 +624,19 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
|
||||
match self.inner {
|
||||
match &mut self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(ref mut s) => Pin::new(s).poll_shutdown(cx),
|
||||
InnerAsyncNetworkStream::Tokio1Tcp(s) => Pin::new(s).poll_shutdown(cx),
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(s) => Pin::new(s).poll_shutdown(cx),
|
||||
#[cfg(feature = "tokio1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => Pin::new(s).poll_shutdown(cx),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_shutdown(cx),
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_close(cx),
|
||||
#[cfg(feature = "async-std1-native-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(ref mut s) => Pin::new(s).poll_close(cx),
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_close(cx),
|
||||
#[cfg(feature = "async-std1-rustls-tls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(ref mut s) => Pin::new(s).poll_close(cx),
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_close(cx),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Poll::Ready(Ok(()))
|
||||
|
||||
@@ -307,4 +307,10 @@ impl SmtpConnection {
|
||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||
self.stream.get_ref().peer_certificate()
|
||||
}
|
||||
|
||||
/// All the X509 certificates of the chain (DER encoded)
|
||||
#[cfg(any(feature = "rustls-tls", feature = "boring-tls"))]
|
||||
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||
self.stream.get_ref().certificate_chain()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(super) use self::tls::InnerTlsParameters;
|
||||
pub use self::tls::TlsVersion;
|
||||
pub use self::{
|
||||
connection::SmtpConnection,
|
||||
tls::{Certificate, CertificateStore, Tls, TlsParameters, TlsParametersBuilder},
|
||||
tls::{Certificate, CertificateStore, Identity, Tls, TlsParameters, TlsParametersBuilder},
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
||||
@@ -139,7 +139,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "log")]
|
||||
#[cfg(feature = "tracing")]
|
||||
fn test_escape_crlf() {
|
||||
assert_eq!(escape_crlf("\r\n"), "<CRLF>");
|
||||
assert_eq!(escape_crlf("EHLO my_name\r\n"), "EHLO my_name<CRLF>");
|
||||
|
||||
@@ -55,14 +55,14 @@ impl NetworkStream {
|
||||
|
||||
/// Returns peer's address
|
||||
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
match self.inner {
|
||||
InnerNetworkStream::Tcp(ref s) => s.peer_addr(),
|
||||
match &self.inner {
|
||||
InnerNetworkStream::Tcp(s) => s.peer_addr(),
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(ref s) => s.get_ref().peer_addr(),
|
||||
InnerNetworkStream::NativeTls(s) => s.get_ref().peer_addr(),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
InnerNetworkStream::RustlsTls(ref s) => s.get_ref().peer_addr(),
|
||||
InnerNetworkStream::RustlsTls(s) => s.get_ref().peer_addr(),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(ref s) => s.get_ref().peer_addr(),
|
||||
InnerNetworkStream::BoringTls(s) => s.get_ref().peer_addr(),
|
||||
InnerNetworkStream::None => {
|
||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||
Ok(SocketAddr::V4(SocketAddrV4::new(
|
||||
@@ -75,14 +75,14 @@ impl NetworkStream {
|
||||
|
||||
/// Shutdowns the connection
|
||||
pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
|
||||
match self.inner {
|
||||
InnerNetworkStream::Tcp(ref s) => s.shutdown(how),
|
||||
match &self.inner {
|
||||
InnerNetworkStream::Tcp(s) => s.shutdown(how),
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(ref s) => s.get_ref().shutdown(how),
|
||||
InnerNetworkStream::NativeTls(s) => s.get_ref().shutdown(how),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
InnerNetworkStream::RustlsTls(ref s) => s.get_ref().shutdown(how),
|
||||
InnerNetworkStream::RustlsTls(s) => s.get_ref().shutdown(how),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(ref s) => s.get_ref().shutdown(how),
|
||||
InnerNetworkStream::BoringTls(s) => s.get_ref().shutdown(how),
|
||||
InnerNetworkStream::None => {
|
||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||
Ok(())
|
||||
@@ -208,7 +208,7 @@ impl NetworkStream {
|
||||
}
|
||||
|
||||
pub fn is_encrypted(&self) -> bool {
|
||||
match self.inner {
|
||||
match &self.inner {
|
||||
InnerNetworkStream::Tcp(_) => false,
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(_) => true,
|
||||
@@ -223,6 +223,32 @@ impl NetworkStream {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "rustls-tls", feature = "boring-tls"))]
|
||||
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||
match &self.inner {
|
||||
InnerNetworkStream::Tcp(_) => Err(error::client("Connection is not encrypted")),
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(_) => panic!("Unsupported"),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
InnerNetworkStream::RustlsTls(stream) => Ok(stream
|
||||
.conn
|
||||
.peer_certificates()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.to_vec())
|
||||
.collect()),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(stream) => Ok(stream
|
||||
.ssl()
|
||||
.peer_cert_chain()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.to_der().map_err(error::tls))
|
||||
.collect::<Result<Vec<_>, _>>()?),
|
||||
InnerNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||
match &self.inner {
|
||||
@@ -254,20 +280,14 @@ impl NetworkStream {
|
||||
}
|
||||
|
||||
pub fn set_read_timeout(&mut self, duration: Option<Duration>) -> io::Result<()> {
|
||||
match self.inner {
|
||||
InnerNetworkStream::Tcp(ref mut stream) => stream.set_read_timeout(duration),
|
||||
match &mut self.inner {
|
||||
InnerNetworkStream::Tcp(stream) => stream.set_read_timeout(duration),
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(ref mut stream) => {
|
||||
stream.get_ref().set_read_timeout(duration)
|
||||
}
|
||||
InnerNetworkStream::NativeTls(stream) => stream.get_ref().set_read_timeout(duration),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
InnerNetworkStream::RustlsTls(ref mut stream) => {
|
||||
stream.get_ref().set_read_timeout(duration)
|
||||
}
|
||||
InnerNetworkStream::RustlsTls(stream) => stream.get_ref().set_read_timeout(duration),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(ref mut stream) => {
|
||||
stream.get_ref().set_read_timeout(duration)
|
||||
}
|
||||
InnerNetworkStream::BoringTls(stream) => stream.get_ref().set_read_timeout(duration),
|
||||
InnerNetworkStream::None => {
|
||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||
Ok(())
|
||||
@@ -277,21 +297,15 @@ impl NetworkStream {
|
||||
|
||||
/// Set write timeout for IO calls
|
||||
pub fn set_write_timeout(&mut self, duration: Option<Duration>) -> io::Result<()> {
|
||||
match self.inner {
|
||||
InnerNetworkStream::Tcp(ref mut stream) => stream.set_write_timeout(duration),
|
||||
match &mut self.inner {
|
||||
InnerNetworkStream::Tcp(stream) => stream.set_write_timeout(duration),
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(ref mut stream) => {
|
||||
stream.get_ref().set_write_timeout(duration)
|
||||
}
|
||||
InnerNetworkStream::NativeTls(stream) => stream.get_ref().set_write_timeout(duration),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
InnerNetworkStream::RustlsTls(ref mut stream) => {
|
||||
stream.get_ref().set_write_timeout(duration)
|
||||
}
|
||||
InnerNetworkStream::RustlsTls(stream) => stream.get_ref().set_write_timeout(duration),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(ref mut stream) => {
|
||||
stream.get_ref().set_write_timeout(duration)
|
||||
}
|
||||
InnerNetworkStream::BoringTls(stream) => stream.get_ref().set_write_timeout(duration),
|
||||
InnerNetworkStream::None => {
|
||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||
Ok(())
|
||||
@@ -302,14 +316,14 @@ impl NetworkStream {
|
||||
|
||||
impl Read for NetworkStream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match self.inner {
|
||||
InnerNetworkStream::Tcp(ref mut s) => s.read(buf),
|
||||
match &mut self.inner {
|
||||
InnerNetworkStream::Tcp(s) => s.read(buf),
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(ref mut s) => s.read(buf),
|
||||
InnerNetworkStream::NativeTls(s) => s.read(buf),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
InnerNetworkStream::RustlsTls(ref mut s) => s.read(buf),
|
||||
InnerNetworkStream::RustlsTls(s) => s.read(buf),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(ref mut s) => s.read(buf),
|
||||
InnerNetworkStream::BoringTls(s) => s.read(buf),
|
||||
InnerNetworkStream::None => {
|
||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||
Ok(0)
|
||||
@@ -320,14 +334,14 @@ impl Read for NetworkStream {
|
||||
|
||||
impl Write for NetworkStream {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match self.inner {
|
||||
InnerNetworkStream::Tcp(ref mut s) => s.write(buf),
|
||||
match &mut self.inner {
|
||||
InnerNetworkStream::Tcp(s) => s.write(buf),
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(ref mut s) => s.write(buf),
|
||||
InnerNetworkStream::NativeTls(s) => s.write(buf),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
InnerNetworkStream::RustlsTls(ref mut s) => s.write(buf),
|
||||
InnerNetworkStream::RustlsTls(s) => s.write(buf),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(ref mut s) => s.write(buf),
|
||||
InnerNetworkStream::BoringTls(s) => s.write(buf),
|
||||
InnerNetworkStream::None => {
|
||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||
Ok(0)
|
||||
@@ -336,14 +350,14 @@ impl Write for NetworkStream {
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
match self.inner {
|
||||
InnerNetworkStream::Tcp(ref mut s) => s.flush(),
|
||||
match &mut self.inner {
|
||||
InnerNetworkStream::Tcp(s) => s.flush(),
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(ref mut s) => s.flush(),
|
||||
InnerNetworkStream::NativeTls(s) => s.flush(),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
InnerNetworkStream::RustlsTls(ref mut s) => s.flush(),
|
||||
InnerNetworkStream::RustlsTls(s) => s.flush(),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(ref mut s) => s.flush(),
|
||||
InnerNetworkStream::BoringTls(s) => s.flush(),
|
||||
InnerNetworkStream::None => {
|
||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||
Ok(())
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::{io, sync::Arc};
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
use boring::{
|
||||
pkey::PKey,
|
||||
ssl::{SslConnector, SslVersion},
|
||||
x509::store::X509StoreBuilder,
|
||||
};
|
||||
@@ -12,8 +13,10 @@ use native_tls::{Protocol, TlsConnector};
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
use rustls::{
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
crypto::WebPkiSupportedAlgorithms,
|
||||
crypto::{verify_tls12_signature, verify_tls13_signature},
|
||||
pki_types::{CertificateDer, ServerName, UnixTime},
|
||||
pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime},
|
||||
server::ParsedCertificate,
|
||||
ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
|
||||
};
|
||||
|
||||
@@ -108,7 +111,7 @@ pub enum CertificateStore {
|
||||
/// For native-tls, this will use the system certificate store on Windows, the keychain on
|
||||
/// macOS, and OpenSSL directories on Linux (usually `/etc/ssl`).
|
||||
///
|
||||
/// For rustls, this will also use the the system store if the `rustls-native-certs` feature is
|
||||
/// For rustls, this will also use the system store if the `rustls-native-certs` feature is
|
||||
/// enabled, or will fall back to `webpki-roots`.
|
||||
///
|
||||
/// The boring-tls backend uses the same logic as OpenSSL on all platforms.
|
||||
@@ -139,6 +142,7 @@ pub struct TlsParametersBuilder {
|
||||
domain: String,
|
||||
cert_store: CertificateStore,
|
||||
root_certs: Vec<Certificate>,
|
||||
identity: Option<Identity>,
|
||||
accept_invalid_hostnames: bool,
|
||||
accept_invalid_certs: bool,
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||
@@ -152,6 +156,7 @@ impl TlsParametersBuilder {
|
||||
domain,
|
||||
cert_store: CertificateStore::Default,
|
||||
root_certs: Vec::new(),
|
||||
identity: None,
|
||||
accept_invalid_hostnames: false,
|
||||
accept_invalid_certs: false,
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||
@@ -167,12 +172,20 @@ impl TlsParametersBuilder {
|
||||
|
||||
/// Add a custom root certificate
|
||||
///
|
||||
/// Can be used to safely connect to a server using a self signed certificate, for example.
|
||||
/// Can be used to safely connect to a server using a self-signed certificate, for example.
|
||||
pub fn add_root_certificate(mut self, cert: Certificate) -> Self {
|
||||
self.root_certs.push(cert);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a client certificate
|
||||
///
|
||||
/// Can be used to configure a client certificate to present to the server.
|
||||
pub fn identify_with(mut self, identity: Identity) -> Self {
|
||||
self.identity = Some(identity);
|
||||
self
|
||||
}
|
||||
|
||||
/// Controls whether certificates with an invalid hostname are accepted
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
@@ -184,10 +197,11 @@ impl TlsParametersBuilder {
|
||||
/// including those from other sites, are trusted.
|
||||
///
|
||||
/// This method introduces significant vulnerabilities to man-in-the-middle attacks.
|
||||
///
|
||||
/// Hostname verification can only be disabled with the `native-tls` TLS backend.
|
||||
#[cfg(any(feature = "native-tls", feature = "boring-tls"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "boring-tls"))))]
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||
)]
|
||||
pub fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
|
||||
self.accept_invalid_hostnames = accept_invalid_hostnames;
|
||||
self
|
||||
@@ -275,6 +289,10 @@ impl TlsParametersBuilder {
|
||||
};
|
||||
|
||||
tls_builder.min_protocol_version(Some(min_tls_version));
|
||||
if let Some(identity) = self.identity {
|
||||
tls_builder.identity(identity.native_tls);
|
||||
}
|
||||
|
||||
let connector = tls_builder.build().map_err(error::tls)?;
|
||||
Ok(TlsParameters {
|
||||
connector: InnerTlsParameters::NativeTls(connector),
|
||||
@@ -317,6 +335,15 @@ impl TlsParametersBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(identity) = self.identity {
|
||||
tls_builder
|
||||
.set_certificate(identity.boring_tls.0.as_ref())
|
||||
.map_err(error::tls)?;
|
||||
tls_builder
|
||||
.set_private_key(identity.boring_tls.1.as_ref())
|
||||
.map_err(error::tls)?;
|
||||
}
|
||||
|
||||
let min_tls_version = match self.min_tls_version {
|
||||
TlsVersion::Tlsv10 => SslVersion::TLS1,
|
||||
TlsVersion::Tlsv11 => SslVersion::TLS1_1,
|
||||
@@ -352,51 +379,73 @@ impl TlsParametersBuilder {
|
||||
};
|
||||
|
||||
let tls = ClientConfig::builder_with_protocol_versions(supported_versions);
|
||||
let provider = rustls::crypto::CryptoProvider::get_default()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Arc::new(rustls::crypto::ring::default_provider()));
|
||||
|
||||
let tls = if self.accept_invalid_certs {
|
||||
tls.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {}))
|
||||
} else {
|
||||
let mut root_cert_store = RootCertStore::empty();
|
||||
// Build TLS config
|
||||
let signature_algorithms = provider.signature_verification_algorithms;
|
||||
|
||||
#[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 (added, ignored) = store.add_parsable_certificates(native_certs);
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::debug!(
|
||||
"loaded platform certs with {added} valid and {ignored} ignored (invalid) certs"
|
||||
);
|
||||
Ok(())
|
||||
let mut root_cert_store = RootCertStore::empty();
|
||||
|
||||
#[cfg(feature = "rustls-native-certs")]
|
||||
fn load_native_roots(store: &mut RootCertStore) -> Result<(), Error> {
|
||||
let rustls_native_certs::CertificateResult { certs, errors, .. } =
|
||||
rustls_native_certs::load_native_certs();
|
||||
let errors_len = errors.len();
|
||||
|
||||
let (added, ignored) = store.add_parsable_certificates(certs);
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::debug!(
|
||||
"loaded platform certs with {errors_len} failing to load, {added} valid and {ignored} ignored (invalid) certs"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
fn load_webpki_roots(store: &mut RootCertStore) {
|
||||
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(not(feature = "rustls-native-certs"))]
|
||||
load_webpki_roots(&mut root_cert_store);
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
fn load_webpki_roots(store: &mut RootCertStore) {
|
||||
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(not(feature = "rustls-native-certs"))]
|
||||
load_webpki_roots(&mut root_cert_store);
|
||||
}
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
CertificateStore::WebpkiRoots => {
|
||||
load_webpki_roots(&mut root_cert_store);
|
||||
}
|
||||
CertificateStore::None => {}
|
||||
}
|
||||
for cert in self.root_certs {
|
||||
for rustls_cert in cert.rustls {
|
||||
root_cert_store.add(rustls_cert).map_err(error::tls)?;
|
||||
}
|
||||
CertificateStore::WebpkiRoots => {
|
||||
load_webpki_roots(&mut root_cert_store);
|
||||
}
|
||||
CertificateStore::None => {}
|
||||
}
|
||||
for cert in self.root_certs {
|
||||
for rustls_cert in cert.rustls {
|
||||
root_cert_store.add(rustls_cert).map_err(error::tls)?;
|
||||
}
|
||||
}
|
||||
|
||||
let tls = if self.accept_invalid_certs || self.accept_invalid_hostnames {
|
||||
let verifier = InvalidCertsVerifier {
|
||||
ignore_invalid_hostnames: self.accept_invalid_hostnames,
|
||||
ignore_invalid_certs: self.accept_invalid_certs,
|
||||
roots: root_cert_store,
|
||||
signature_algorithms,
|
||||
};
|
||||
tls.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(verifier))
|
||||
} else {
|
||||
tls.with_root_certificates(root_cert_store)
|
||||
};
|
||||
let tls = tls.with_no_client_auth();
|
||||
|
||||
let tls = if let Some(identity) = self.identity {
|
||||
let (client_certificates, private_key) = identity.rustls_tls;
|
||||
tls.with_client_auth_cert(client_certificates, private_key)
|
||||
.map_err(error::tls)?
|
||||
} else {
|
||||
tls.with_no_client_auth()
|
||||
};
|
||||
|
||||
Ok(TlsParameters {
|
||||
connector: InnerTlsParameters::RustlsTls(Arc::new(tls)),
|
||||
@@ -461,7 +510,7 @@ impl TlsParameters {
|
||||
}
|
||||
}
|
||||
|
||||
/// A client certificate that can be used with [`TlsParametersBuilder::add_root_certificate`]
|
||||
/// A certificate that can be used with [`TlsParametersBuilder::add_root_certificate`]
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
pub struct Certificate {
|
||||
@@ -528,20 +577,109 @@ impl Debug for Certificate {
|
||||
}
|
||||
}
|
||||
|
||||
/// An identity that can be used with [`TlsParametersBuilder::identify_with`]
|
||||
#[allow(missing_copy_implementations)]
|
||||
pub struct Identity {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: native_tls::Identity,
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
rustls_tls: (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: (boring::x509::X509, PKey<boring::pkey::Private>),
|
||||
}
|
||||
|
||||
impl Debug for Identity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Identity").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Identity {
|
||||
fn clone(&self) -> Self {
|
||||
Identity {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: self.native_tls.clone(),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
rustls_tls: (self.rustls_tls.0.clone(), self.rustls_tls.1.clone_key()),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: (self.boring_tls.0.clone(), self.boring_tls.1.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||
impl Identity {
|
||||
pub fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: Identity::from_pem_native_tls(pem, key)?,
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
rustls_tls: Identity::from_pem_rustls_tls(pem, key)?,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: Identity::from_pem_boring_tls(pem, key)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
fn from_pem_native_tls(pem: &[u8], key: &[u8]) -> Result<native_tls::Identity, Error> {
|
||||
native_tls::Identity::from_pkcs8(pem, key).map_err(error::tls)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
fn from_pem_rustls_tls(
|
||||
pem: &[u8],
|
||||
key: &[u8],
|
||||
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Error> {
|
||||
let mut key = key;
|
||||
let key = rustls_pemfile::private_key(&mut key).unwrap().unwrap();
|
||||
Ok((vec![pem.to_owned().into()], key))
|
||||
}
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
fn from_pem_boring_tls(
|
||||
pem: &[u8],
|
||||
key: &[u8],
|
||||
) -> Result<(boring::x509::X509, PKey<boring::pkey::Private>), Error> {
|
||||
let cert = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
|
||||
let key = boring::pkey::PKey::private_key_from_pem(key).map_err(error::tls)?;
|
||||
Ok((cert, key))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
#[derive(Debug)]
|
||||
struct InvalidCertsVerifier;
|
||||
struct InvalidCertsVerifier {
|
||||
ignore_invalid_hostnames: bool,
|
||||
ignore_invalid_certs: bool,
|
||||
roots: RootCertStore,
|
||||
signature_algorithms: WebPkiSupportedAlgorithms,
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
impl ServerCertVerifier for InvalidCertsVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &CertificateDer<'_>,
|
||||
_intermediates: &[CertificateDer<'_>],
|
||||
_server_name: &ServerName<'_>,
|
||||
end_entity: &CertificateDer<'_>,
|
||||
intermediates: &[CertificateDer<'_>],
|
||||
server_name: &ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
now: UnixTime,
|
||||
) -> Result<ServerCertVerified, TlsError> {
|
||||
let cert = ParsedCertificate::try_from(end_entity)?;
|
||||
|
||||
if !self.ignore_invalid_certs {
|
||||
rustls::client::verify_server_cert_signed_by_trust_anchor(
|
||||
&cert,
|
||||
&self.roots,
|
||||
intermediates,
|
||||
now,
|
||||
self.signature_algorithms.all,
|
||||
)?;
|
||||
}
|
||||
|
||||
if !self.ignore_invalid_hostnames {
|
||||
rustls::client::verify_server_name(&cert, server_name)?;
|
||||
}
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ impl fmt::Debug for Error {
|
||||
|
||||
builder.field("kind", &self.inner.kind);
|
||||
|
||||
if let Some(ref source) = self.inner.source {
|
||||
if let Some(source) = &self.inner.source {
|
||||
builder.field("source", source);
|
||||
}
|
||||
|
||||
@@ -129,22 +129,22 @@ impl fmt::Debug for Error {
|
||||
|
||||
impl fmt::Display for Error {
|
||||
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::Client => f.write_str("internal client error")?,
|
||||
Kind::Network => f.write_str("network error")?,
|
||||
Kind::Connection => f.write_str("Connection error")?,
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||
Kind::Tls => f.write_str("tls error")?,
|
||||
Kind::Transient(ref code) => {
|
||||
Kind::Transient(code) => {
|
||||
write!(f, "transient error ({code})")?;
|
||||
}
|
||||
Kind::Permanent(ref code) => {
|
||||
Kind::Permanent(code) => {
|
||||
write!(f, "permanent error ({code})")?;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ref e) = self.inner.source {
|
||||
if let Some(e) = &self.inner.source {
|
||||
write!(f, ": {e}")?;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::{
|
||||
collections::HashSet,
|
||||
fmt::{self, Display, Formatter},
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
result::Result,
|
||||
};
|
||||
|
||||
use crate::transport::smtp::{
|
||||
@@ -53,10 +52,10 @@ impl Default for ClientId {
|
||||
|
||||
impl Display for ClientId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Domain(ref value) => f.write_str(value),
|
||||
Self::Ipv4(ref value) => write!(f, "[{value}]"),
|
||||
Self::Ipv6(ref value) => write!(f, "[IPv6:{value}]"),
|
||||
match self {
|
||||
Self::Domain(value) => f.write_str(value),
|
||||
Self::Ipv4(value) => write!(f, "[{value}]"),
|
||||
Self::Ipv6(value) => write!(f, "[IPv6:{value}]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,11 +92,11 @@ pub enum Extension {
|
||||
|
||||
impl Display for Extension {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
match self {
|
||||
Extension::EightBitMime => f.write_str("8BITMIME"),
|
||||
Extension::SmtpUtfEight => f.write_str("SMTPUTF8"),
|
||||
Extension::StartTls => f.write_str("STARTTLS"),
|
||||
Extension::Authentication(ref mechanism) => write!(f, "AUTH {mechanism}"),
|
||||
Extension::Authentication(mechanism) => write!(f, "AUTH {mechanism}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,16 +226,16 @@ pub enum MailParameter {
|
||||
|
||||
impl Display for MailParameter {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
MailParameter::Body(ref value) => write!(f, "BODY={value}"),
|
||||
match self {
|
||||
MailParameter::Body(value) => write!(f, "BODY={value}"),
|
||||
MailParameter::Size(size) => write!(f, "SIZE={size}"),
|
||||
MailParameter::SmtpUtfEight => f.write_str("SMTPUTF8"),
|
||||
MailParameter::Other {
|
||||
ref keyword,
|
||||
value: Some(ref value),
|
||||
keyword,
|
||||
value: Some(value),
|
||||
} => write!(f, "{}={}", keyword, XText(value)),
|
||||
MailParameter::Other {
|
||||
ref keyword,
|
||||
keyword,
|
||||
value: None,
|
||||
} => f.write_str(keyword),
|
||||
}
|
||||
@@ -277,13 +276,13 @@ pub enum RcptParameter {
|
||||
|
||||
impl Display for RcptParameter {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
match &self {
|
||||
RcptParameter::Other {
|
||||
ref keyword,
|
||||
value: Some(ref value),
|
||||
keyword,
|
||||
value: Some(value),
|
||||
} => write!(f, "{keyword}={}", XText(value)),
|
||||
RcptParameter::Other {
|
||||
ref keyword,
|
||||
keyword,
|
||||
value: None,
|
||||
} => f.write_str(keyword),
|
||||
}
|
||||
@@ -292,14 +291,8 @@ impl Display for RcptParameter {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::*;
|
||||
use crate::transport::smtp::{
|
||||
authentication::Mechanism,
|
||||
response::{Category, Code, Detail, Response, Severity},
|
||||
};
|
||||
use crate::transport::smtp::response::{Category, Code, Detail, Severity};
|
||||
|
||||
#[test]
|
||||
fn test_clientid_fmt() {
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::{
|
||||
fmt::{Display, Formatter, Result},
|
||||
result,
|
||||
str::FromStr,
|
||||
string::ToString,
|
||||
};
|
||||
|
||||
use nom::{
|
||||
|
||||
@@ -317,9 +317,9 @@ impl SmtpClient {
|
||||
/// Handles encryption and authentication
|
||||
pub fn connection(&self) -> Result<SmtpConnection, Error> {
|
||||
#[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"))]
|
||||
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters),
|
||||
Tls::Wrapper(tls_parameters) => Some(tls_parameters),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -333,13 +333,13 @@ impl SmtpClient {
|
||||
)?;
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||
match self.info.tls {
|
||||
Tls::Opportunistic(ref tls_parameters) => {
|
||||
match &self.info.tls {
|
||||
Tls::Opportunistic(tls_parameters) => {
|
||||
if conn.can_starttls() {
|
||||
conn.starttls(tls_parameters, &self.info.hello_name)?;
|
||||
}
|
||||
}
|
||||
Tls::Required(ref tls_parameters) => {
|
||||
Tls::Required(tls_parameters) => {
|
||||
conn.starttls(tls_parameters, &self.info.hello_name)?;
|
||||
}
|
||||
_ => (),
|
||||
|
||||
Reference in New Issue
Block a user