Add support for inline images

This commit is contained in:
2024-07-05 10:38:12 -07:00
parent 55d7aec516
commit 3a5a9bd66a
4 changed files with 119 additions and 33 deletions

View File

@@ -337,6 +337,7 @@ impl QueryRoot {
.headers
.get_first_value("date")
.and_then(|d| mailparse::dateparse(&d).ok());
let cid_prefix = format!("/cid/{id}/");
let body = match extract_body(&m, &id)? {
Body::PlainText(PlainText { text, content_tree }) => {
let text = if text.len() > MAX_RAW_MESSAGE_SIZE {
@@ -355,7 +356,7 @@ impl QueryRoot {
// Trim newlines to prevent excessive white space at the beginning/end of
// presenation. Leave tabs and spaces incase plain text attempts to center a
// header on the first line.
sanitize_html(&linkify_html(&text.trim_matches('\n')))?
sanitize_html(&linkify_html(&text.trim_matches('\n')), &cid_prefix)?
),
content_tree: if debug_content_tree {
render_content_type_tree(&m)
@@ -365,7 +366,7 @@ impl QueryRoot {
})
}
Body::Html(Html { html, content_tree }) => Body::Html(Html {
html: sanitize_html(&html)?,
html: sanitize_html(&html, &cid_prefix)?,
content_tree: if debug_content_tree {
render_content_type_tree(&m)
} else {
@@ -671,7 +672,13 @@ fn flatten_body_parts(parts: &[Body]) -> Body {
fn extract_related(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Error> {
// TODO(wathiede): collect related things and change return type to new Body arm.
let handled_types = vec![MULTIPART_ALTERNATIVE, TEXT_HTML, TEXT_PLAIN];
let handled_types = vec![
MULTIPART_ALTERNATIVE,
TEXT_HTML,
TEXT_PLAIN,
IMAGE_JPEG,
IMAGE_PNG,
];
let mut unhandled_types: Vec<_> = m
.subparts
.iter()
@@ -679,8 +686,21 @@ fn extract_related(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body,
.filter(|mt| !handled_types.contains(&mt))
.collect();
unhandled_types.sort();
warn!("{MULTIPART_RELATED} contains the following unhandled mimetypes {unhandled_types:?}");
if !unhandled_types.is_empty() {
warn!("{MULTIPART_RELATED} contains the following unhandled mimetypes {unhandled_types:?}");
}
for (i, sp) in m.subparts.iter().enumerate() {
if sp.ctype.mimetype == IMAGE_PNG || sp.ctype.mimetype == IMAGE_JPEG {
info!("sp.ctype {:#?}", sp.ctype);
//info!("sp.headers {:#?}", sp.headers);
if let Some(cid) = sp.headers.get_first_value("Content-Id") {
let mut part_id = part_addr.clone();
part_id.push(i.to_string());
info!("cid: {cid} part_id {part_id:?}");
}
}
}
for sp in &m.subparts {
if sp.ctype.mimetype == MULTIPART_ALTERNATIVE {
return extract_alternative(m, part_addr);
@@ -705,17 +725,29 @@ fn extract_related(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body,
.into())
}
fn walk_attachments<T, F: Fn(&ParsedMail, &[usize]) -> Option<T>>(
fn walk_attachments<T, F: Fn(&ParsedMail, &[usize]) -> Option<T> + Copy>(
m: &ParsedMail,
visitor: F,
) -> Option<T> {
let mut cur_addr = Vec::new();
walk_attachments_inner(m, visitor, &mut cur_addr)
}
fn walk_attachments_inner<T, F: Fn(&ParsedMail, &[usize]) -> Option<T> + Copy>(
m: &ParsedMail,
visitor: F,
cur_addr: &mut Vec<usize>,
) -> Option<T> {
for (idx, sp) in m.subparts.iter().enumerate() {
cur_addr.push(idx);
let val = visitor(sp, &cur_addr);
if val.is_some() {
return val;
}
let val = walk_attachments_inner(sp, visitor, cur_addr);
if val.is_some() {
return val;
}
cur_addr.pop();
}
None
@@ -784,26 +816,18 @@ fn get_attachment_filename(header_value: &str) -> &str {
}
fn get_content_type<'a>(headers: &[MailHeader<'a>]) -> Option<String> {
for h in headers {
if h.get_key() == "Content-Type" {
let v = h.get_value();
if let Some(idx) = v.find(';') {
return Some(v[..idx].to_string());
} else {
return Some(v);
}
if let Some(v) = headers.get_first_value("Content-Type") {
if let Some(idx) = v.find(';') {
return Some(v[..idx].to_string());
} else {
return Some(v);
}
}
None
}
fn get_content_id<'a>(headers: &[MailHeader<'a>]) -> Option<String> {
for h in headers {
if h.get_key() == "Content-ID" {
return Some(h.get_value());
}
}
None
headers.get_first_value("Content-Id")
}
fn render_content_type_tree(m: &ParsedMail) -> String {
@@ -903,6 +927,34 @@ fn email_addresses(path: &str, m: &ParsedMail, header_name: &str) -> Result<Vec<
Ok(addrs)
}
pub fn cid_attachment_bytes(nm: &Notmuch, id: &str, cid: &str) -> Result<Attachment, ServerError> {
let files = nm.files(id)?;
let Some(path) = files.first() else {
warn!("failed to find files for message {id}");
return Err(ServerError::PartNotFound);
};
let file = File::open(&path)?;
let mmap = unsafe { MmapOptions::new().map(&file)? };
let m = parse_mail(&mmap)?;
if let Some(attachment) = walk_attachments(&m, |sp, cur_idx| {
info!("{cid} {:?}", get_content_id(&sp.headers));
if let Some(h_cid) = get_content_id(&sp.headers) {
let h_cid = &h_cid[1..h_cid.len() - 1];
if h_cid == cid {
let attachment = extract_attachment(&sp, id, &[]).unwrap_or(Attachment {
..Attachment::default()
});
return Some(attachment);
}
}
None
}) {
return Ok(attachment);
}
Err(ServerError::PartNotFound)
}
pub fn attachment_bytes(nm: &Notmuch, id: &str, idx: &[usize]) -> Result<Attachment, ServerError> {
let files = nm.files(id)?;
let Some(path) = files.first() else {