notmuch_more/parse/
body.rs1use anyhow::anyhow;
2use email::mimeheaders::MimeContentType;
3use email::MimeMultipartType;
4use itertools::Itertools;
5use serde::Serialize;
6
7use crate::NotmuchMoreError;
8
9#[derive(Clone, Debug, Default, Serialize)]
10pub struct EmlBody {
11 pub alternatives: Vec<EmlBody>,
12 pub content: String,
13 pub content_encoded: Option<Vec<u8>>,
14 pub disposition: String,
15 pub extra: Vec<EmlBody>,
16 pub filename: Option<String>,
17 pub is_cleaned_html: bool,
18 pub mimetype: String,
19 pub signature: Option<Box<EmlBody>>,
20 pub size: Option<String>,
21}
22
23pub(crate) fn parse_body_part(part: &mailparse::ParsedMail) -> Result<EmlBody, NotmuchMoreError> {
24 let mimect: MimeContentType = part
25 .ctype
26 .mimetype
27 .split_once('/')
28 .map(|(s1, s2)| (s1.into(), s2.into()))
29 .ok_or_else(|| anyhow!("Failed to parse mimetype: {}", part.ctype.mimetype))?;
30
31 let content_disp = part.get_content_disposition();
32
33 match MimeMultipartType::from_content_type(mimect) {
34 None => match part.ctype.mimetype.as_str() {
35 "text/html" => Ok(EmlBody {
36 content: ammonia::Builder::default()
37 .set_tag_attribute_value("a", "target", "_blank")
38 .rm_tag_attributes("img", &["src"])
39 .clean(&part.get_body()?)
40 .to_string(),
41 disposition: format!("{:?}", content_disp.disposition),
42 filename: content_disp.params.get("filename").map(|f| f.into()),
43 is_cleaned_html: true,
44 mimetype: part.ctype.mimetype.to_owned(),
45 size: content_disp.params.get("size").map(|f| f.into()),
46 ..Default::default()
47 }),
48 _ => Ok(EmlBody {
49 content: part.get_body()?,
50 content_encoded: Some(part.get_body_raw()?),
51 disposition: format!("{:?}", content_disp.disposition),
52 filename: content_disp.params.get("filename").map(|f| f.into()),
53 mimetype: part.ctype.mimetype.to_owned(),
54 size: content_disp.params.get("size").map(|f| f.into()),
55 ..Default::default()
56 }),
57 },
58
59 Some(MimeMultipartType::Alternative) => {
60 let mut first = parse_body_part(&part.subparts[0])?;
61 first.alternatives = part.subparts[1..]
62 .iter()
63 .map(parse_body_part)
64 .collect::<Result<_, _>>()?;
65 Ok(first)
66 }
67
68 Some(MimeMultipartType::Mixed) => {
69 let mut first = parse_body_part(&part.subparts[0])?;
70 first.extra = part.subparts[1..]
71 .iter()
72 .map(parse_body_part)
73 .collect::<Result<_, _>>()?;
74
75 Ok(first)
76 }
77
78 Some(MimeMultipartType::Signed) => {
79 let mut first = parse_body_part(&part.subparts[0])?;
80 first.signature = Some(Box::new(parse_body_part(
81 part.subparts[1..]
82 .iter()
83 .exactly_one()
84 .map_err(|_| anyhow!("Expected exactly one signature for signed part"))?,
85 )?));
86
87 Ok(first)
88 }
89
90 Some(t) => Err(anyhow!("Not implemented: {:?}", t).into()),
91 }
92}