Compare commits
1 Commits
v0.11.10
...
paolobarbo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3b5d760f8 |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,14 +1,3 @@
|
||||
<a name="v0.11.10"></a>
|
||||
### v0.11.10 (2024-10-23)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
* Ignore disconnect errors when `pool` feature of SMTP transport is disabled ([#999])
|
||||
* Use case insensitive comparisons for matching login challenge requests ([#1000])
|
||||
|
||||
[#999]: https://github.com/lettre/lettre/pull/999
|
||||
[#1000]: https://github.com/lettre/lettre/pull/1000
|
||||
|
||||
<a name="v0.11.9"></a>
|
||||
### v0.11.9 (2024-09-13)
|
||||
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1178,7 +1178,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "lettre"
|
||||
version = "0.11.10"
|
||||
version = "0.11.9"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
|
||||
@@ -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.10"
|
||||
version = "0.11.9"
|
||||
description = "Email client"
|
||||
readme = "README.md"
|
||||
homepage = "https://lettre.rs"
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://deps.rs/crate/lettre/0.11.10">
|
||||
<img src="https://deps.rs/crate/lettre/0.11.10/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>
|
||||
|
||||
@@ -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.10")]
|
||||
#![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)]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::{mem, ops::Deref};
|
||||
use std::{mem, ops::Deref, sync::Arc};
|
||||
|
||||
use crate::message::header::ContentTransferEncoding;
|
||||
|
||||
/// A [`Message`][super::Message] or [`SinglePart`][super::SinglePart] body that has already been encoded.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Body {
|
||||
buf: Vec<u8>,
|
||||
buf: Arc<[u8]>,
|
||||
encoding: ContentTransferEncoding,
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Body {
|
||||
|
||||
let encoding = buf.encoding(false);
|
||||
buf.encode_crlf();
|
||||
Self::new_impl(buf.into(), encoding)
|
||||
Self::new_impl(Vec::from(buf).into(), encoding)
|
||||
}
|
||||
|
||||
/// Encode the supplied `buf`, using the provided `encoding`.
|
||||
@@ -77,7 +77,7 @@ impl Body {
|
||||
}
|
||||
|
||||
buf.encode_crlf();
|
||||
Ok(Self::new_impl(buf.into(), encoding))
|
||||
Ok(Self::new_impl(Vec::from(buf).into(), encoding))
|
||||
}
|
||||
|
||||
/// Builds a new `Body` using a pre-encoded buffer.
|
||||
@@ -87,11 +87,14 @@ impl Body {
|
||||
/// `buf` shouldn't contain non-ascii characters, lines longer than 1000 characters or nul bytes.
|
||||
#[inline]
|
||||
pub fn dangerous_pre_encoded(buf: Vec<u8>, encoding: ContentTransferEncoding) -> Self {
|
||||
Self { buf, encoding }
|
||||
Self {
|
||||
buf: buf.into(),
|
||||
encoding,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the supplied `buf` using the provided `encoding`
|
||||
fn new_impl(buf: Vec<u8>, encoding: ContentTransferEncoding) -> Self {
|
||||
fn new_impl(buf: Arc<[u8]>, encoding: ContentTransferEncoding) -> Self {
|
||||
match encoding {
|
||||
ContentTransferEncoding::SevenBit
|
||||
| ContentTransferEncoding::EightBit
|
||||
@@ -133,7 +136,16 @@ impl Body {
|
||||
|
||||
/// Consumes `Body` and returns the inner `Vec<u8>`
|
||||
#[inline]
|
||||
#[deprecated(
|
||||
note = "The inner memory is not stored into `Vec<u8>` anymore. Consider using `into_inner`"
|
||||
)]
|
||||
pub fn into_vec(self) -> Vec<u8> {
|
||||
self.buf.to_vec()
|
||||
}
|
||||
|
||||
/// Consumes `Body` and returns the inner `Arc<[u8]>`
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> Arc<[u8]> {
|
||||
self.buf
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub use self::{
|
||||
special::*,
|
||||
textual::*,
|
||||
};
|
||||
use super::EmailFormat;
|
||||
use crate::BoxError;
|
||||
|
||||
mod content;
|
||||
@@ -154,6 +155,19 @@ impl Display for Headers {
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailFormat for Headers {
|
||||
fn format<'a>(&'a self, out: &mut impl Extend<Cow<'a, [u8]>>) {
|
||||
for value in &self.headers {
|
||||
out.extend([
|
||||
Cow::Borrowed(value.name.as_bytes()),
|
||||
Cow::Borrowed(b": "),
|
||||
Cow::Borrowed(value.encoded_value.as_bytes()),
|
||||
Cow::Borrowed(b"\r\n"),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A possible error when converting a `HeaderName` from another type.
|
||||
// comes from `http` crate
|
||||
#[allow(missing_copy_implementations)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{io::Write, iter::repeat_with};
|
||||
use std::{borrow::Cow, iter::repeat_with, sync::Arc};
|
||||
|
||||
use mime::Mime;
|
||||
|
||||
@@ -28,7 +28,7 @@ impl Part {
|
||||
}
|
||||
|
||||
impl EmailFormat for Part {
|
||||
fn format(&self, out: &mut Vec<u8>) {
|
||||
fn format<'a>(&'a self, out: &mut impl Extend<Cow<'a, [u8]>>) {
|
||||
match self {
|
||||
Part::Single(part) => part.format(out),
|
||||
Part::Multi(part) => part.format(out),
|
||||
@@ -71,7 +71,7 @@ impl SinglePartBuilder {
|
||||
|
||||
SinglePart {
|
||||
headers: self.headers,
|
||||
body: body.into_vec(),
|
||||
body: body.into_inner(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ impl Default for SinglePartBuilder {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SinglePart {
|
||||
headers: Headers,
|
||||
body: Vec<u8>,
|
||||
body: Arc<[u8]>,
|
||||
}
|
||||
|
||||
impl SinglePart {
|
||||
@@ -138,24 +138,18 @@ impl SinglePart {
|
||||
|
||||
/// Get message content formatted for sending
|
||||
pub fn formatted(&self) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
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");
|
||||
self.format_to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailFormat for SinglePart {
|
||||
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);
|
||||
fn format<'a>(&'a self, out: &mut impl Extend<Cow<'a, [u8]>>) {
|
||||
self.headers.format(out);
|
||||
out.extend([
|
||||
Cow::Borrowed("\r\n".as_bytes()),
|
||||
Cow::Borrowed(&self.body),
|
||||
Cow::Borrowed(b"\r\n"),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,33 +378,36 @@ impl MultiPart {
|
||||
|
||||
/// Get message content formatted for SMTP
|
||||
pub fn formatted(&self) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
self.format(&mut out);
|
||||
out
|
||||
self.format_to_vec()
|
||||
}
|
||||
|
||||
/// Format only the multipart body
|
||||
fn format_body(&self, out: &mut Vec<u8>) {
|
||||
fn format_body<'a>(&'a self, out: &mut impl Extend<Cow<'a, [u8]>>) {
|
||||
let boundary = self.boundary();
|
||||
|
||||
for part in &self.parts {
|
||||
out.extend_from_slice(b"--");
|
||||
out.extend_from_slice(boundary.as_bytes());
|
||||
out.extend_from_slice(b"\r\n");
|
||||
out.extend([
|
||||
Cow::Borrowed("--".as_bytes()),
|
||||
// FIXME: this clone shouldn't exist
|
||||
Cow::Owned(boundary.clone().into()),
|
||||
Cow::Borrowed("\r\n".as_bytes()),
|
||||
]);
|
||||
part.format(out);
|
||||
}
|
||||
|
||||
out.extend_from_slice(b"--");
|
||||
out.extend_from_slice(boundary.as_bytes());
|
||||
out.extend_from_slice(b"--\r\n");
|
||||
out.extend([
|
||||
Cow::Borrowed("--".as_bytes()),
|
||||
Cow::Owned(boundary.into()),
|
||||
Cow::Borrowed("--\r\n".as_bytes()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
fn format<'a>(&'a self, out: &mut impl Extend<Cow<'a, [u8]>>) {
|
||||
self.headers.format(out);
|
||||
out.extend([Cow::Borrowed("\r\n".as_bytes())]);
|
||||
|
||||
self.format_body(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
//! ```
|
||||
//! </details>
|
||||
|
||||
use std::{io::Write, iter, time::SystemTime};
|
||||
use std::{borrow::Cow, iter, sync::Arc, time::SystemTime};
|
||||
|
||||
pub use attachment::Attachment;
|
||||
pub use body::{Body, IntoBody, MaybeString};
|
||||
@@ -226,7 +226,23 @@ const DEFAULT_MESSAGE_ID_DOMAIN: &str = "localhost";
|
||||
/// Something that can be formatted as an email message
|
||||
trait EmailFormat {
|
||||
// Use a writer?
|
||||
fn format(&self, out: &mut Vec<u8>);
|
||||
fn format<'a>(&'a self, out: &mut impl Extend<Cow<'a, [u8]>>);
|
||||
|
||||
fn format_to_vec(&self) -> Vec<u8> {
|
||||
struct Formatter(Vec<u8>);
|
||||
|
||||
impl<'a> Extend<Cow<'a, [u8]>> for Formatter {
|
||||
fn extend<T: IntoIterator<Item = Cow<'a, [u8]>>>(&mut self, iter: T) {
|
||||
for chunk in iter {
|
||||
self.0.extend_from_slice(&chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut formatted = Formatter(Vec::new());
|
||||
self.format(&mut formatted);
|
||||
formatted.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for messages
|
||||
@@ -454,7 +470,7 @@ impl MessageBuilder {
|
||||
let body = body.into_body(maybe_encoding);
|
||||
|
||||
self.headers.set(body.encoding());
|
||||
self.build(MessageBody::Raw(body.into_vec()))
|
||||
self.build(MessageBody::Raw(body.into_inner()))
|
||||
}
|
||||
|
||||
/// Create message using mime body ([`MultiPart`][self::MultiPart])
|
||||
@@ -489,7 +505,7 @@ pub struct Message {
|
||||
#[derive(Clone, Debug)]
|
||||
enum MessageBody {
|
||||
Mime(Part),
|
||||
Raw(Vec<u8>),
|
||||
Raw(Arc<[u8]>),
|
||||
}
|
||||
|
||||
impl Message {
|
||||
@@ -515,9 +531,7 @@ impl Message {
|
||||
|
||||
/// Get message content formatted for SMTP
|
||||
pub fn formatted(&self) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
self.format(&mut out);
|
||||
out
|
||||
self.format_to_vec()
|
||||
}
|
||||
|
||||
#[cfg(feature = "dkim")]
|
||||
@@ -593,15 +607,13 @@ impl Message {
|
||||
}
|
||||
|
||||
impl EmailFormat for Message {
|
||||
fn format(&self, out: &mut Vec<u8>) {
|
||||
write!(out, "{}", self.headers)
|
||||
.expect("A Write implementation panicked while formatting headers");
|
||||
fn format<'a>(&'a self, out: &mut impl Extend<Cow<'a, [u8]>>) {
|
||||
self.headers.format(out);
|
||||
|
||||
match &self.body {
|
||||
MessageBody::Mime(p) => p.format(out),
|
||||
MessageBody::Raw(r) => {
|
||||
out.extend_from_slice(b"\r\n");
|
||||
out.extend_from_slice(r)
|
||||
out.extend([Cow::Borrowed("\r\n".as_bytes()), Cow::Borrowed(r)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ impl AsyncTransport for AsyncSmtpTransport<Tokio1Executor> {
|
||||
let result = conn.send(envelope, email).await?;
|
||||
|
||||
#[cfg(not(feature = "pool"))]
|
||||
conn.abort().await;
|
||||
conn.quit().await?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -98,17 +98,13 @@ impl Mechanism {
|
||||
let decoded_challenge = challenge
|
||||
.ok_or_else(|| error::client("This mechanism does expect a challenge"))?;
|
||||
|
||||
if contains_ignore_ascii_case(
|
||||
decoded_challenge,
|
||||
["User Name", "Username:", "Username", "User Name\0"],
|
||||
) {
|
||||
if ["User Name", "Username:", "Username", "User Name\0"]
|
||||
.contains(&decoded_challenge)
|
||||
{
|
||||
return Ok(credentials.authentication_identity.clone());
|
||||
}
|
||||
|
||||
if contains_ignore_ascii_case(
|
||||
decoded_challenge,
|
||||
["Password", "Password:", "Password\0"],
|
||||
) {
|
||||
if ["Password", "Password:", "Password\0"].contains(&decoded_challenge) {
|
||||
return Ok(credentials.secret.clone());
|
||||
}
|
||||
|
||||
@@ -125,15 +121,6 @@ impl Mechanism {
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_ignore_ascii_case<'a>(
|
||||
haystack: &str,
|
||||
needles: impl IntoIterator<Item = &'a str>,
|
||||
) -> bool {
|
||||
needles
|
||||
.into_iter()
|
||||
.any(|item| item.eq_ignore_ascii_case(haystack))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Credentials, Mechanism};
|
||||
@@ -168,23 +155,6 @@ mod test {
|
||||
assert!(mechanism.response(&credentials, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_login_case_insensitive() {
|
||||
let mechanism = Mechanism::Login;
|
||||
|
||||
let credentials = Credentials::new("alice".to_owned(), "wonderland".to_owned());
|
||||
|
||||
assert_eq!(
|
||||
mechanism.response(&credentials, Some("username")).unwrap(),
|
||||
"alice"
|
||||
);
|
||||
assert_eq!(
|
||||
mechanism.response(&credentials, Some("password")).unwrap(),
|
||||
"wonderland"
|
||||
);
|
||||
assert!(mechanism.response(&credentials, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xoauth2() {
|
||||
let mechanism = Mechanism::Xoauth2;
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Transport for SmtpTransport {
|
||||
let result = conn.send(envelope, email)?;
|
||||
|
||||
#[cfg(not(feature = "pool"))]
|
||||
conn.abort();
|
||||
conn.quit()?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user