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)]