use mailparse::{DispositionType, MailHeaderMap, ParsedMail}; #[derive(PartialEq, Eq, Debug, Default)] pub struct Body { pub text: Option, } pub trait ParsedMailExt { fn is_attachment(&self) -> bool; fn subject(&self) -> Option; fn date(&self) -> Result>, anyhow::Error>; fn body(&self) -> Result, anyhow::Error>; } impl<'a> ParsedMailExt for ParsedMail<'a> { fn is_attachment(&self) -> bool { self.get_content_disposition().disposition == DispositionType::Attachment } fn subject(&self) -> Option { self.headers.get_first_value("Subject") } fn date(&self) -> Result>, anyhow::Error> { let date = self .headers .get_first_value("Date") .map(|s| chrono::DateTime::parse_from_rfc2822(&s)) .transpose()?; Ok(date) } fn body(&self) -> Result, anyhow::Error> { let mimetype: mime::Mime = self.ctype.mimetype.parse()?; if self.is_attachment() { return Ok(None); } if mimetype == mime::TEXT_PLAIN { return Ok(Some(Body { text: Some(self.get_body()?), })); } for subpart in &self.subparts { if let Some(body) = subpart.body()? { return Ok(Some(body)); } } Ok(None) } } #[cfg(test)] mod test { use chrono::TimeZone; use super::*; const TEXT_PLAIN: &str = r#"From: Andrey Golovizin To: ag@sologoc.com Subject: Plain text body Date: Sat, 17 Jul 2021 18:04:10 +0200 Message-ID: <2148453.iZASKD2KPV@sakuragaoka> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="iso-8859-1" Prost=FD text. "#; const MULTIPART_ALTERNATIVE: &str = r#"From: Andrey Golovizin Subject: Plain text with HTML Date: Sat, 17 Jul 2021 18:13:26 +0200 Message-ID: <3609140.kQq0lBPeGt@sakuragaoka> MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="nextPart3377186.iIbC2pHGDl" Content-Transfer-Encoding: 7Bit This is a multi-part message in MIME format. --nextPart3377186.iIbC2pHGDl Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="iso-8859-1" Prost=FD text. --nextPart3377186.iIbC2pHGDl Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset="UTF-8"

N=C4=9Bjaky HTML text.

--nextPart3377186.iIbC2pHGDl-- "#; const MULTIPART_MIXED_ALTERNATIVE: &str = r#"From: Andrey Golovizin Subject: Plain text with HTML and attachment Date: Sat, 17 Jul 2021 21:39:08 +0200 Message-ID: <3054314.5fSG56mABF@sakuragaoka> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="nextPart1698715.VLH7GnMWUR" Content-Transfer-Encoding: 7Bit This is a multi-part message in MIME format. --nextPart1698715.VLH7GnMWUR Content-Type: multipart/alternative; boundary="nextPart5630828.MhkbZ0Pkbq" Content-Transfer-Encoding: 7Bit This is a multi-part message in MIME format. --nextPart5630828.MhkbZ0Pkbq Content-Transfer-Encoding: base64 Content-Type: text/plain; charset="UTF-8" UHJvc3TDvSB0ZXh0Lg== --nextPart5630828.MhkbZ0Pkbq Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset="UTF-8"

Tu=C4=8Dn=C3=BD HTML text

--nextPart5630828.MhkbZ0Pkbq-- --nextPart1698715.VLH7GnMWUR Content-Disposition: attachment; filename="test.txt" Content-Transfer-Encoding: base64 Content-Type: text/plain; charset="UTF-8"; name="test.txt" TsSbamFrw6EgcMWZw61sb2hhLgo= --nextPart1698715.VLH7GnMWUR-- "#; #[test] fn test_plain_text_body() -> Result<(), anyhow::Error> { let message = mailparse::parse_mail(TEXT_PLAIN.as_bytes())?; assert_eq!(message.subject(), Some("Plain text body".to_string())); assert_eq!( message.date()?, Some( chrono::FixedOffset::east(3600 * 2) .ymd(2021, 7, 17) .and_hms(18, 4, 10), ) ); assert_eq!( message.body()?, Some(Body { text: Some("Prostý text.".to_string()) }), ); Ok(()) } #[test] fn test_multipart_alternative_text_body() -> Result<(), anyhow::Error> { let message = mailparse::parse_mail(MULTIPART_ALTERNATIVE.as_bytes())?; assert_eq!(message.subject(), Some("Plain text with HTML".to_string())); assert_eq!( message.date()?, Some( chrono::FixedOffset::east(3600 * 2) .ymd(2021, 7, 17) .and_hms(18, 13, 26), ) ); assert_eq!( message.body()?, Some(Body { text: Some("Prostý text.".to_string()) }), ); Ok(()) } #[test] fn test_multipart_mixed_alternative_text_body() -> Result<(), anyhow::Error> { let message = mailparse::parse_mail(MULTIPART_MIXED_ALTERNATIVE.as_bytes())?; assert_eq!( message.subject(), Some("Plain text with HTML and attachment".to_string()) ); assert_eq!( message.date()?, Some( chrono::FixedOffset::east(3600 * 2) .ymd(2021, 7, 17) .and_hms(21, 39, 8), ) ); assert_eq!( message.body()?, Some(Body { text: Some("Prostý text.".to_string()) }), ); Ok(()) } }