From c7eacb63b570dac229ce291fa948473674e17fd4 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Mon, 28 Aug 2023 11:35:22 -0700 Subject: [PATCH] mailparsing: replace mail-parser dep --- Cargo.lock | 10 --- crates/kumod/Cargo.toml | 1 - crates/kumod/src/http_server/inject_v1.rs | 81 +++++++++++-------- crates/mailparsing/src/mimepart.rs | 95 +++++++++++++++++++++++ 4 files changed, 143 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3547b8ca..a26a1d26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2456,7 +2456,6 @@ dependencies = [ "kumo-server-runtime", "lazy_static", "lruttl", - "mail-parser", "maildir", "mailparsing", "maplit", @@ -2722,15 +2721,6 @@ dependencies = [ "gethostname", ] -[[package]] -name = "mail-parser" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4158a1c18963244e083888b21465846dfb68d6170850ed1ab4742edd57c9d47" -dependencies = [ - "encoding_rs", -] - [[package]] name = "maildir" version = "0.6.3" diff --git a/crates/kumod/Cargo.toml b/crates/kumod/Cargo.toml index 9c5711ab..a44d786d 100644 --- a/crates/kumod/Cargo.toml +++ b/crates/kumod/Cargo.toml @@ -68,5 +68,4 @@ zstd = "0.12" [dev-dependencies] k9 = "0.11" -mail-parser = "0.8" maplit = "1.0" diff --git a/crates/kumod/src/http_server/inject_v1.rs b/crates/kumod/src/http_server/inject_v1.rs index 53438af5..ee67b0a1 100644 --- a/crates/kumod/src/http_server/inject_v1.rs +++ b/crates/kumod/src/http_server/inject_v1.rs @@ -587,31 +587,34 @@ This is a test message to James Smythe .unwrap(); println!("{generated}"); - let parsed = mail_parser::Message::parse(&generated.as_bytes()).unwrap(); - println!("{parsed:?}"); + let parsed = MimePart::parse(generated.as_str()).unwrap(); + println!("{parsed:#?}"); - assert!(parsed.header("MIME-Version").is_some()); + assert!(parsed.headers().mime_version().unwrap().is_some()); + + let structure = parsed.simplified_structure().unwrap(); + eprintln!("{structure:?}"); k9::snapshot!( - parsed.body_html(0), + structure.html, r#" Some( - "I am the html text, James Smythe. 👾 ", + "I am the html text, James Smythe. 👾 \r +", ) "# ); k9::snapshot!( - parsed.body_text(0), + structure.text, r#" Some( - "I am the plain text, James Smythe. 😀", + "I am the plain text, James Smythe. 😀\r +", ) "# ); - k9::assert_equal!(parsed.html_body_count(), 1); - k9::assert_equal!(parsed.text_body_count(), 1); - k9::assert_equal!(parsed.attachment_count(), 1); + k9::assert_equal!(structure.attachments.len(), 1); } #[tokio::test] @@ -651,40 +654,52 @@ Some( .unwrap(); println!("{generated}"); - let parsed = mail_parser::Message::parse(&generated.as_bytes()).unwrap(); + let parsed = MimePart::parse(generated.as_str()).unwrap(); println!("{parsed:?}"); k9::snapshot!( - parsed.header("To"), + parsed.headers().to(), r#" -Some( - Address( - Addr { - name: Some( - "James Smythe", - ), - address: Some( - "user@example.com", - ), - }, +Ok( + Some( + AddressList( + [ + Mailbox( + Mailbox { + name: Some( + "James Smythe", + ), + address: AddrSpec { + local_part: "user", + domain: "example.com", + }, + }, + ), + ], + ), ), ) "# ); k9::snapshot!( - parsed.header("From"), + parsed.headers().from(), r#" -Some( - Address( - Addr { - name: Some( - "Sender Name", - ), - address: Some( - "from@example.com", - ), - }, +Ok( + Some( + MailboxList( + [ + Mailbox { + name: Some( + "Sender Name", + ), + address: AddrSpec { + local_part: "from", + domain: "example.com", + }, + }, + ], + ), ), ) "# diff --git a/crates/mailparsing/src/mimepart.rs b/crates/mailparsing/src/mimepart.rs index 7039b665..7f4894cd 100644 --- a/crates/mailparsing/src/mimepart.rs +++ b/crates/mailparsing/src/mimepart.rs @@ -434,6 +434,101 @@ impl<'a> MimePart<'a> { outro: "".into(), } } + + /// Returns a SimplifiedStructure representation of the mime tree, + /// with the (probable) primary text/plain and text/html parts + /// pulled out, and the remaining parts recorded as a flat + /// attachments array + pub fn simplified_structure(&'a self) -> Result> { + let info = Rfc2045Info::new(&self.headers, false)?; + + if let Some(ct) = &info.content_type { + if ct.value == "text/plain" { + return Ok(SimplifiedStructure { + text: match self.body()? { + DecodedBody::Text(t) => Some(t), + DecodedBody::Binary(_) => { + return Err(MailParsingError::BodyParse( + "expected text/plain part to be text, but it is binary".to_string(), + )) + } + }, + html: None, + headers: &self.headers, + attachments: vec![], + }); + } + if ct.value == "text/html" { + return Ok(SimplifiedStructure { + html: match self.body()? { + DecodedBody::Text(t) => Some(t), + DecodedBody::Binary(_) => { + return Err(MailParsingError::BodyParse( + "expected text/html part to be text, but it is binary".to_string(), + )) + } + }, + text: None, + headers: &self.headers, + attachments: vec![], + }); + } + if ct.value.starts_with("multipart/") { + let mut text = None; + let mut html = None; + let mut attachments = vec![]; + + for p in &self.parts { + if let Ok(mut s) = p.simplified_structure() { + if s.text.is_some() && text.is_none() { + text = s.text; + } + if s.html.is_some() && html.is_none() { + html = s.html; + } + attachments.append(&mut s.attachments); + } + } + + return Ok(SimplifiedStructure { + html, + text, + headers: &self.headers, + attachments, + }); + } + + return Ok(SimplifiedStructure { + html: None, + text: None, + headers: &self.headers, + attachments: vec![self.clone()], + }); + } + + // Assume text/plain content-type + Ok(SimplifiedStructure { + text: match self.body()? { + DecodedBody::Text(t) => Some(t), + DecodedBody::Binary(_) => { + return Err(MailParsingError::BodyParse( + "expected text/plain part to be text, but it is binary".to_string(), + )) + } + }, + html: None, + headers: &self.headers, + attachments: vec![], + }) + } +} + +#[derive(Debug, Clone)] +pub struct SimplifiedStructure<'a> { + pub text: Option>, + pub html: Option>, + pub headers: &'a HeaderMap<'a>, + pub attachments: Vec>, } #[derive(Debug, Clone, PartialEq)]