diff --git a/Cargo.lock b/Cargo.lock index 1786e4f..b42f5c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,12 +56,6 @@ dependencies = [ "term", ] -[[package]] -name = "ascii_utils" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" - [[package]] name = "async-stream" version = "0.3.2" @@ -166,12 +160,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" - [[package]] name = "base64" version = "0.13.0" @@ -506,18 +494,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] -name = "email" -version = "0.0.20" +name = "email-encoding" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4" +checksum = "6690291166824e467790ac08ba42f241791567e8337bbf00c5a6e87889629f98" dependencies = [ - "base64 0.9.3", - "chrono", - "encoding", - "lazy_static", - "rand 0.4.6", - "time 0.1.43", - "version_check 0.1.5", + "base64 0.13.0", ] [[package]] @@ -615,12 +597,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] -name = "fast_chemail" -version = "0.9.6" +name = "fastrand" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ - "ascii_utils", + "instant", ] [[package]] @@ -944,7 +926,6 @@ dependencies = [ "multipart", "num_cpus", "regex", - "rfc2047", "ring", "rocket", "rocket_codegen", @@ -1356,17 +1337,19 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lettre" -version = "0.10.0-pre" -source = "git+https://github.com/lettre/lettre?rev=245c600c82ee18b766e8729f005ff453a55dce34#245c600c82ee18b766e8729f005ff453a55dce34" +version = "0.10.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5144148f337be14dabfc0f0d85b691a68ac6c77ef22a5c47c5504b70a7c9fcf3" dependencies = [ - "base64 0.11.0", - "email", - "fast_chemail", - "log 0.4.14", + "base64 0.13.0", + "email-encoding", + "fastrand", + "httpdate", + "idna 0.2.3", "mime 0.3.16", - "serde", - "serde_json", - "time 0.2.27", + "once_cell", + "quoted_printable", + "regex", "uuid 0.8.2", ] @@ -2097,17 +2080,10 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.4.6" +name = "quoted_printable" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi 0.3.9", -] +checksum = "3fee2dce59f7a43418e3382c766554c614e06a552d53a8f07ef499ea4b332c0f" [[package]] name = "rand" @@ -2379,12 +2355,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "rfc2047" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6986b5de4fa30c92b50020fdedf45bf9296d9842df4355b62f92dcc18bcdee15" - [[package]] name = "ring" version = "0.13.5" diff --git a/Cargo.toml b/Cargo.toml index fd422da..bd0664a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,8 +39,8 @@ gettext-macros = "0.6.1" gettext-utils = "0.1" gettext = "0.4" glob = "0.3" -rfc2047 = "0.1" hyperx = "1.4" +lettre = { version = "0.10.0-rc.5", default-features = false, features = ["builder", "file-transport", "sendmail-transport"] } [dependencies.rocket_i18n] git = "https://github.com/Plume-org/rocket_i18n" @@ -48,14 +48,6 @@ branch = "go-async" default-features = false features = ["rocket"] -[dependencies.lettre] -version = "0.10.0-pre" -default-features = false -# smtp-transport doesn't build (openssl problem) -features = ["builder", "file-transport", "sendmail-transport"] -git = "https://github.com/lettre/lettre" -rev = "245c600c82ee18b766e8729f005ff453a55dce34" - [build-dependencies] vergen = "3" diff --git a/src/mail.rs b/src/mail.rs index 94fc9b7..d622e57 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -2,16 +2,14 @@ use std::path::{Path, PathBuf}; use crate::counters; use handlebars::Handlebars; -use lettre::builder::{EmailBuilder, Mailbox, MimeMultipartType, PartBuilder}; -use lettre::{file::FileTransport, SendmailTransport, Transport as LettreTransport}; +use lettre::message::{Mailbox, MultiPart, SinglePart, header}; +use lettre::{FileTransport, SendmailTransport, Transport as LettreTransport}; use serde::Serialize; use uuid::Uuid; use gettext_macros::i18n; use rocket_i18n::I18n; -use rfc2047::rfc2047_encode; - use crate::template_helpers; use crate::database::types::Email; @@ -81,8 +79,9 @@ impl Service { .host_str() .ok_or_else(|| anyhow!("No host in base-URI"))? .to_string(); + let from = from.parse().map_err(|_| anyhow!("From must be valid email address"))?; Ok(Self { - from: from.into(), + from, domain, templates, transport, @@ -203,7 +202,7 @@ impl Service { fn send( &self, - to: &[&Email], + tos: &[&Email], subject: &str, template: &str, locale: &str, @@ -212,46 +211,44 @@ impl Service { let (html, txt) = self.render_template(template, locale, ctx)?; if cfg!(debug_assertions) { - for recipient in to.iter() { + for recipient in tos.iter() { println!("To: {}", recipient); } println!("{}", &txt); } - // build this ourselves, as a temporary workaround for https://github.com/lettre/lettre/issues/400 - let text = PartBuilder::new() - .body(txt) - .header(("Content-Type", "text/plain; charset=utf-8")) - .header(("Content-Transfer-Encoding", "8bit")) - .build(); - - let html = PartBuilder::new() - .body(html) - .header(("Content-Type", "text/html; charset=utf-8")) - .header(("Content-Transfer-Encoding", "8bit")) - .build(); - - let email = EmailBuilder::new() + let mut email = lettre::Message::builder() .from(self.from.clone()) - .subject(rfc2047_encode(subject)) - .message_id(format!("<{}@{}>", Uuid::new_v4(), self.domain)) - .message_type(MimeMultipartType::Alternative) - .header(("Content-Transfer-Encoding", "8bit")) - .child(text) - .child(html); + .subject(subject) + .message_id(Some(format!("<{}@{}>", Uuid::new_v4(), self.domain))) + .header(header::ContentTransferEncoding::EightBit); - let email = to.iter().fold(email, |email, to| email.to(to.to_string())); + for to in tos.iter() { + email = email.to(to.as_str().parse().unwrap()); + } - let email = email.build()?; + let email = email.multipart( + MultiPart::alternative() + .singlepart( + SinglePart::builder() + .header(header::ContentTransferEncoding::EightBit) + .header(header::ContentType::TEXT_PLAIN) + .body(txt) + ) + .singlepart(SinglePart::builder() + .header(header::ContentTransferEncoding::EightBit) + .header(header::ContentType::TEXT_HTML) + .body(html)), + )?; match self.transport { Transport::Sendmail => { - let mut transport = SendmailTransport::new(); - transport.send(email)?; + let transport = SendmailTransport::new(); + transport.send(&email)?; } Transport::Filemail(ref path) => { - let mut transport = FileTransport::new(path); - transport.send(email)?; + let transport = FileTransport::new(path); + transport.send(&email)?; } } @@ -259,31 +256,17 @@ impl Service { } } -// for some reason, this is no longer public in lettre itself -// FIXME replace with builtin struct on lettre update -// see https://github.com/lettre/lettre/blob/master/lettre/src/file/mod.rs#L41 -#[cfg(test)] -#[derive(Deserialize)] -struct SerializableEmail { - #[serde(alias = "envelope")] - _envelope: lettre::Envelope, - #[serde(alias = "message_id")] - _message_id: String, - message: Vec, -} - /// Returns and removes the first mail it finds from the given /// directory. #[cfg(test)] pub fn pop_mail(dir: &Path) -> Result> { - use std::fs; + use std::{fs, fs::read_to_string}; for entry in fs::read_dir(dir)? { let entry = entry?; if entry.file_type()?.is_file() { - let fh = fs::File::open(entry.path())?; + let body = read_to_string(entry.path())?.replace("\r\n", "\n"); fs::remove_file(entry.path())?; - let mail: SerializableEmail = ::serde_json::from_reader(fh)?; - let body = String::from_utf8_lossy(&mail.message).to_string(); + println!("{}", body); return Ok(Some(body)); } } @@ -346,15 +329,14 @@ mod test { (h, v) }) .collect(); - assert!(headers.contains(&("Content-Transfer-Encoding", "8bit"))); assert!(headers.contains(&("Content-Type", "text/plain; charset=utf-8"))); assert!(headers.contains(&("Content-Type", "text/html; charset=utf-8"))); - assert!(headers.contains(&("From", ""))); - assert!(headers.contains(&("To", ""))); + assert!(headers.contains(&("From", "test@localhost"))); + assert!(headers.contains(&("To", "recipient@example.org"))); assert_header(&headers, "Content-Type", |v| { v.starts_with("multipart/alternative") }); - assert_header(&headers, "Date", |v| v.contains("+0000")); + assert_header(&headers, "Date", |v| v.contains("-0000")); assert_header(&headers, "Message-ID", |v| v.contains("@localhost>")); } @@ -413,7 +395,7 @@ mod test { assert!(mail_content.contains("test/about")); assert!(mail_content.contains("あなたのメールアド")); assert!(mail_content.contains( - "Subject: =?utf-8?q?localhost=E3=81=AE=E3=81=82=E3=81=AA=E3=81=9F=E3=81=AE?=" + "Subject: =?utf-8?b?bG9jYWxob3N044Gu44GC44Gq44Gf44Gu6Y2144Gu44Gf44KB44GrbG9jYWxob3N044KS5qSc6Ki844GZ44KL?=" )); } @@ -467,7 +449,7 @@ mod test { assert!(mail_content.contains("test/about")); assert!(mail_content.contains("この鍵の掲示されたア")); assert!(mail_content.contains( - "Subject: =?utf-8?q?localhost=E3=81=AE=E9=8D=B5=E3=82=92=E7=AE=A1=E7=90=86?=" + "Subject: =?utf-8?b?bG9jYWxob3N044Gu6Y2144KS566h55CG44GZ44KL?=" )); } diff --git a/src/web/mod.rs b/src/web/mod.rs index 412f282..75207fe 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -887,7 +887,7 @@ pub mod tests { check_verify_link(&client, &token, "foo@invalid.example.com", "de"); let mail_content = pop_mail(&filemail_into).unwrap().unwrap(); assert!(mail_content.contains("Dies ist eine automatisierte Nachricht")); - assert!(mail_content.contains("Subject: =?utf-8?q?Best=C3=A4tige?= foo@invalid.example.com\r\n\t=?utf-8?q?f=C3=BCr?= deinen =?utf-8?q?Schl=C3=BCssel?= auf local.connection")); + assert!(mail_content.contains("Subject: =?utf-8?b?QmVzdMOkdGlnZQ==?= foo@invalid.example.com \n =?utf-8?b?ZsO8cg==?= deinen =?utf-8?b?U2NobMO8c3NlbA==?= auf \n local.connection")); } #[test]