Replace hyperx Date header with our own implementation (#597)
This commit is contained in:
@@ -22,6 +22,7 @@ idna = "0.2"
|
||||
tracing = { version = "0.1.16", default-features = false, features = ["std"], optional = true } # feature
|
||||
|
||||
# builder
|
||||
httpdate = { version = "1", optional = true }
|
||||
hyperx = { version = "1", optional = true, features = ["headers"] }
|
||||
mime = { version = "0.3.4", optional = true }
|
||||
fastrand = { version = "1.4", optional = true }
|
||||
@@ -81,7 +82,7 @@ name = "transport_smtp"
|
||||
|
||||
[features]
|
||||
default = ["smtp-transport", "native-tls", "hostname", "r2d2", "builder"]
|
||||
builder = ["mime", "base64", "hyperx", "fastrand", "quoted_printable"]
|
||||
builder = ["httpdate", "mime", "base64", "hyperx", "fastrand", "quoted_printable"]
|
||||
|
||||
# transports
|
||||
file-transport = ["uuid"]
|
||||
|
||||
140
src/message/header/date.rs
Normal file
140
src/message/header/date.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use std::{fmt::Result as FmtResult, str::from_utf8, time::SystemTime};
|
||||
|
||||
use httpdate::HttpDate;
|
||||
use hyperx::{
|
||||
header::{Formatter as HeaderFormatter, Header, RawLike},
|
||||
Error as HeaderError, Result as HyperResult,
|
||||
};
|
||||
|
||||
/// Message `Date` header
|
||||
///
|
||||
/// Defined in [RFC2822](https://tools.ietf.org/html/rfc2822#section-3.3)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Date(HttpDate);
|
||||
|
||||
impl Date {
|
||||
/// Build a `Date` from [`SystemTime`]
|
||||
pub fn new(st: SystemTime) -> Self {
|
||||
Self(st.into())
|
||||
}
|
||||
|
||||
/// Get the current date
|
||||
///
|
||||
/// Shortcut for `Date::new(SystemTime::now())`
|
||||
pub fn now() -> Self {
|
||||
Self::new(SystemTime::now())
|
||||
}
|
||||
}
|
||||
|
||||
impl Header for Date {
|
||||
fn header_name() -> &'static str {
|
||||
"Date"
|
||||
}
|
||||
|
||||
// FIXME HeaderError->HeaderError, same for result
|
||||
fn parse_header<'a, T>(raw: &'a T) -> HyperResult<Self>
|
||||
where
|
||||
T: RawLike<'a>,
|
||||
Self: Sized,
|
||||
{
|
||||
raw.one()
|
||||
.ok_or(HeaderError::Header)
|
||||
.and_then(|r| from_utf8(r).map_err(|_| HeaderError::Header))
|
||||
.and_then(|s| {
|
||||
let mut s = String::from(s);
|
||||
if s.ends_with(" -0000") {
|
||||
// The httpdate crate expects the `Date` to end in ` GMT`, but email
|
||||
// uses `-0000`, so we crudely fix this issue here.
|
||||
|
||||
s.truncate(s.len() - "-0000".len());
|
||||
s.push_str("GMT");
|
||||
}
|
||||
|
||||
s.parse::<HttpDate>()
|
||||
.map(Self)
|
||||
.map_err(|_| HeaderError::Header)
|
||||
})
|
||||
}
|
||||
|
||||
fn fmt_header(&self, f: &mut HeaderFormatter<'_, '_>) -> FmtResult {
|
||||
let mut s = self.0.to_string();
|
||||
if s.ends_with(" GMT") {
|
||||
// The httpdate crate always appends ` GMT` to the end of the string,
|
||||
// but this is considered an obsolete date format for email
|
||||
// https://tools.ietf.org/html/rfc2822#appendix-A.6.2,
|
||||
// so we replace `GMT` with `-0000`
|
||||
s.truncate(s.len() - "GMT".len());
|
||||
s.push_str("-0000");
|
||||
}
|
||||
|
||||
f.fmt_line(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SystemTime> for Date {
|
||||
fn from(st: SystemTime) -> Self {
|
||||
Self::new(st)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Date> for SystemTime {
|
||||
fn from(this: Date) -> SystemTime {
|
||||
this.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use hyperx::header::Headers;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use super::Date;
|
||||
|
||||
#[test]
|
||||
fn format_date() {
|
||||
let mut headers = Headers::new();
|
||||
|
||||
// Tue, 15 Nov 1994 08:12:31 GMT
|
||||
headers.set(Date::from(
|
||||
SystemTime::UNIX_EPOCH + Duration::from_secs(784887151),
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
format!("{}", headers),
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n"
|
||||
);
|
||||
|
||||
// Tue, 15 Nov 1994 08:12:32 GMT
|
||||
headers.set(Date::from(
|
||||
SystemTime::UNIX_EPOCH + Duration::from_secs(784887152),
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
format!("{}", headers),
|
||||
"Date: Tue, 15 Nov 1994 08:12:32 -0000\r\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_date() {
|
||||
let mut headers = Headers::new();
|
||||
|
||||
headers.set_raw("Date", "Tue, 15 Nov 1994 08:12:31 -0000");
|
||||
|
||||
assert_eq!(
|
||||
headers.get::<Date>(),
|
||||
Some(&Date::from(
|
||||
SystemTime::UNIX_EPOCH + Duration::from_secs(784887151),
|
||||
))
|
||||
);
|
||||
|
||||
headers.set_raw("Date", "Tue, 15 Nov 1994 08:12:32 -0000");
|
||||
|
||||
assert_eq!(
|
||||
headers.get::<Date>(),
|
||||
Some(&Date::from(
|
||||
SystemTime::UNIX_EPOCH + Duration::from_secs(784887152),
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
//! Headers widely used in email messages
|
||||
|
||||
pub use hyperx::header::{
|
||||
Charset, ContentDisposition, ContentLocation, ContentType, DispositionParam, DispositionType,
|
||||
Header, Headers,
|
||||
};
|
||||
|
||||
pub use self::date::Date;
|
||||
pub use self::{content::*, mailbox::*, special::*, textual::*};
|
||||
|
||||
mod content;
|
||||
mod date;
|
||||
mod mailbox;
|
||||
mod special;
|
||||
mod textual;
|
||||
|
||||
pub use self::{content::*, mailbox::*, special::*, textual::*};
|
||||
|
||||
pub use hyperx::header::{
|
||||
Charset, ContentDisposition, ContentLocation, ContentType, Date, DispositionParam,
|
||||
DispositionType, Header, Headers, HttpDate as EmailDate,
|
||||
};
|
||||
|
||||
@@ -254,7 +254,7 @@ use std::{convert::TryFrom, io::Write, iter, time::SystemTime};
|
||||
|
||||
use crate::{
|
||||
address::Envelope,
|
||||
message::header::{ContentTransferEncoding, EmailDate, Header, Headers, MailboxesHeader},
|
||||
message::header::{ContentTransferEncoding, Header, Headers, MailboxesHeader},
|
||||
Error as EmailError,
|
||||
};
|
||||
|
||||
@@ -300,16 +300,16 @@ impl MessageBuilder {
|
||||
|
||||
/// Add `Date` header to message
|
||||
///
|
||||
/// Shortcut for `self.header(header::Date(date))`.
|
||||
pub fn date(self, date: EmailDate) -> Self {
|
||||
self.header(header::Date(date))
|
||||
/// Shortcut for `self.header(header::Date::new(st))`.
|
||||
pub fn date(self, st: SystemTime) -> Self {
|
||||
self.header(header::Date::new(st))
|
||||
}
|
||||
|
||||
/// Set `Date` header using current date/time
|
||||
///
|
||||
/// Shortcut for `self.date(SystemTime::now())`.
|
||||
pub fn date_now(self) -> Self {
|
||||
self.date(SystemTime::now().into())
|
||||
self.date(SystemTime::now())
|
||||
}
|
||||
|
||||
/// Set `Subject` header to message
|
||||
@@ -558,6 +558,8 @@ fn make_message_id() -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use super::{header, mailbox::Mailbox, make_message_id, Message, MultiPart, SinglePart};
|
||||
|
||||
#[test]
|
||||
@@ -587,7 +589,8 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn email_message() {
|
||||
let date = "Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap();
|
||||
// Tue, 15 Nov 1994 08:12:31 GMT
|
||||
let date = SystemTime::UNIX_EPOCH + Duration::from_secs(784887151);
|
||||
|
||||
let email = Message::builder()
|
||||
.date(date)
|
||||
@@ -608,7 +611,7 @@ mod test {
|
||||
assert_eq!(
|
||||
String::from_utf8(email.formatted()).unwrap(),
|
||||
concat!(
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n",
|
||||
"From: =?utf-8?b?0JrQsNC4?= <kayo@example.com>\r\n",
|
||||
"To: Pony O.P. <pony@domain.tld>\r\n",
|
||||
"Subject: =?utf-8?b?0Y/So9CwINC10Lsg0LHQtdC705nQvSE=?=\r\n",
|
||||
@@ -621,7 +624,8 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn email_with_png() {
|
||||
let date = "Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap();
|
||||
// Tue, 15 Nov 1994 08:12:31 GMT
|
||||
let date = SystemTime::UNIX_EPOCH + Duration::from_secs(784887151);
|
||||
let img = std::fs::read("./docs/lettre.png").unwrap();
|
||||
let m = Message::builder()
|
||||
.date(date)
|
||||
|
||||
2
testdata/email_with_png.eml
vendored
2
testdata/email_with_png.eml
vendored
@@ -1,4 +1,4 @@
|
||||
Date: Tue, 15 Nov 1994 08:12:31 GMT
|
||||
Date: Tue, 15 Nov 1994 08:12:31 -0000
|
||||
From: NoBody <nobody@domain.tld>
|
||||
Reply-To: Yuin <yuin@domain.tld>
|
||||
To: Hei <hei@domain.tld>
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
#[cfg(all(feature = "file-transport", feature = "builder"))]
|
||||
fn default_date() -> std::time::SystemTime {
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
// Tue, 15 Nov 1994 08:12:31 GMT
|
||||
SystemTime::UNIX_EPOCH + Duration::from_secs(784887151)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(feature = "file-transport", feature = "builder"))]
|
||||
mod sync {
|
||||
use crate::default_date;
|
||||
use lettre::{FileTransport, Message, Transport};
|
||||
use std::{
|
||||
env::temp_dir,
|
||||
@@ -15,7 +24,7 @@ mod sync {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.date(default_date())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
@@ -32,7 +41,7 @@ mod sync {
|
||||
"Reply-To: Yuin <yuin@domain.tld>\r\n",
|
||||
"To: Hei <hei@domain.tld>\r\n",
|
||||
"Subject: Happy new year\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n",
|
||||
"Content-Transfer-Encoding: 7bit\r\n",
|
||||
"\r\n",
|
||||
"Be happy!"
|
||||
@@ -50,7 +59,7 @@ mod sync {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.date(default_date())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
@@ -70,7 +79,7 @@ mod sync {
|
||||
"Reply-To: Yuin <yuin@domain.tld>\r\n",
|
||||
"To: Hei <hei@domain.tld>\r\n",
|
||||
"Subject: Happy new year\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n",
|
||||
"Content-Transfer-Encoding: 7bit\r\n",
|
||||
"\r\n",
|
||||
"Be happy!"
|
||||
@@ -95,6 +104,7 @@ mod sync {
|
||||
#[cfg(test)]
|
||||
#[cfg(all(feature = "file-transport", feature = "builder", feature = "tokio02"))]
|
||||
mod tokio_02 {
|
||||
use crate::default_date;
|
||||
use lettre::{AsyncFileTransport, AsyncTransport, Message, Tokio02Executor};
|
||||
use std::{
|
||||
env::temp_dir,
|
||||
@@ -111,7 +121,7 @@ mod tokio_02 {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.date(default_date())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
@@ -128,7 +138,7 @@ mod tokio_02 {
|
||||
"Reply-To: Yuin <yuin@domain.tld>\r\n",
|
||||
"To: Hei <hei@domain.tld>\r\n",
|
||||
"Subject: Happy new year\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n",
|
||||
"Content-Transfer-Encoding: 7bit\r\n",
|
||||
"\r\n",
|
||||
"Be happy!"
|
||||
@@ -141,6 +151,7 @@ mod tokio_02 {
|
||||
#[cfg(test)]
|
||||
#[cfg(all(feature = "file-transport", feature = "builder", feature = "tokio1"))]
|
||||
mod tokio_1 {
|
||||
use crate::default_date;
|
||||
use lettre::{AsyncFileTransport, AsyncTransport, Message, Tokio1Executor};
|
||||
use std::{
|
||||
env::temp_dir,
|
||||
@@ -158,7 +169,7 @@ mod tokio_1 {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.date(default_date())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
@@ -175,7 +186,7 @@ mod tokio_1 {
|
||||
"Reply-To: Yuin <yuin@domain.tld>\r\n",
|
||||
"To: Hei <hei@domain.tld>\r\n",
|
||||
"Subject: Happy new year\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n",
|
||||
"Content-Transfer-Encoding: 7bit\r\n",
|
||||
"\r\n",
|
||||
"Be happy!"
|
||||
@@ -192,6 +203,7 @@ mod tokio_1 {
|
||||
feature = "async-std1"
|
||||
))]
|
||||
mod asyncstd_1 {
|
||||
use crate::default_date;
|
||||
use lettre::{AsyncFileTransport, AsyncStd1Executor, AsyncTransport, Message};
|
||||
use std::{
|
||||
env::temp_dir,
|
||||
@@ -206,7 +218,7 @@ mod asyncstd_1 {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.date(default_date())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
@@ -223,7 +235,7 @@ mod asyncstd_1 {
|
||||
"Reply-To: Yuin <yuin@domain.tld>\r\n",
|
||||
"To: Hei <hei@domain.tld>\r\n",
|
||||
"Subject: Happy new year\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n",
|
||||
"Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n",
|
||||
"Content-Transfer-Encoding: 7bit\r\n",
|
||||
"\r\n",
|
||||
"Be happy!"
|
||||
|
||||
@@ -38,7 +38,6 @@ mod tokio_02 {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
@@ -66,7 +65,6 @@ mod tokio_1 {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
@@ -93,7 +91,6 @@ mod asyncstd_1 {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ mod tokio_02 {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
@@ -61,7 +60,6 @@ mod tokio_1 {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
@@ -84,7 +82,6 @@ mod asyncstd_1 {
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user