From acc4ff489842b240972c517c94702ea51af07803 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 8 Apr 2021 07:55:20 +0200 Subject: [PATCH] Replace hyperx Date header with our own implementation (#597) --- Cargo.toml | 3 +- src/message/header/date.rs | 140 ++++++++++++++++++++++++++++++++++++ src/message/header/mod.rs | 16 +++-- src/message/mod.rs | 20 +++--- testdata/email_with_png.eml | 2 +- tests/transport_file.rs | 32 ++++++--- tests/transport_sendmail.rs | 3 - tests/transport_stub.rs | 3 - 8 files changed, 186 insertions(+), 33 deletions(-) create mode 100644 src/message/header/date.rs diff --git a/Cargo.toml b/Cargo.toml index 1e25edf..ef4553a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/src/message/header/date.rs b/src/message/header/date.rs new file mode 100644 index 0000000..23480d2 --- /dev/null +++ b/src/message/header/date.rs @@ -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 + 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::() + .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 for Date { + fn from(st: SystemTime) -> Self { + Self::new(st) + } +} + +impl From 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::(), + 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::(), + Some(&Date::from( + SystemTime::UNIX_EPOCH + Duration::from_secs(784887152), + )) + ); + } +} diff --git a/src/message/header/mod.rs b/src/message/header/mod.rs index e7f4ca2..10f9ee8 100644 --- a/src/message/header/mod.rs +++ b/src/message/header/mod.rs @@ -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, -}; diff --git a/src/message/mod.rs b/src/message/mod.rs index 68e4187..19c8024 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -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?= \r\n", "To: Pony O.P. \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) diff --git a/testdata/email_with_png.eml b/testdata/email_with_png.eml index 638f730..72221ef 100644 --- a/testdata/email_with_png.eml +++ b/testdata/email_with_png.eml @@ -1,4 +1,4 @@ -Date: Tue, 15 Nov 1994 08:12:31 GMT +Date: Tue, 15 Nov 1994 08:12:31 -0000 From: NoBody Reply-To: Yuin To: Hei diff --git a/tests/transport_file.rs b/tests/transport_file.rs index 5a59049..38425bd 100644 --- a/tests/transport_file.rs +++ b/tests/transport_file.rs @@ -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 ".parse().unwrap()) .to("Hei ".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 \r\n", "To: Hei \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 ".parse().unwrap()) .to("Hei ".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 \r\n", "To: Hei \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 ".parse().unwrap()) .to("Hei ".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 \r\n", "To: Hei \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 ".parse().unwrap()) .to("Hei ".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 \r\n", "To: Hei \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 ".parse().unwrap()) .to("Hei ".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 \r\n", "To: Hei \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!" diff --git a/tests/transport_sendmail.rs b/tests/transport_sendmail.rs index 0f44673..78cfe69 100644 --- a/tests/transport_sendmail.rs +++ b/tests/transport_sendmail.rs @@ -38,7 +38,6 @@ mod tokio_02 { .reply_to("Yuin ".parse().unwrap()) .to("Hei ".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 ".parse().unwrap()) .to("Hei ".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 ".parse().unwrap()) .to("Hei ".parse().unwrap()) .subject("Happy new year") - .date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap()) .body(String::from("Be happy!")) .unwrap(); diff --git a/tests/transport_stub.rs b/tests/transport_stub.rs index db05621..39837d0 100644 --- a/tests/transport_stub.rs +++ b/tests/transport_stub.rs @@ -36,7 +36,6 @@ mod tokio_02 { .reply_to("Yuin ".parse().unwrap()) .to("Hei ".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 ".parse().unwrap()) .to("Hei ".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 ".parse().unwrap()) .to("Hei ".parse().unwrap()) .subject("Happy new year") - .date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap()) .body(String::from("Be happy!")) .unwrap();