server: handle application/* as an attachment

This commit is contained in:
Bill Thiede 2025-08-18 12:11:31 -07:00
parent 834e873862
commit 574de65c35
3 changed files with 45 additions and 8 deletions

View File

@ -119,9 +119,10 @@ async fn download_attachment(
} else { } else {
format!("id:{}", id) format!("id:{}", id)
}; };
info!("download attachment {mid} {idx}"); info!("download attachment message id '{mid}' idx '{idx}'");
let idx: Vec<_> = idx let idx: Vec<_> = idx
.split('.') .split('.')
.filter(|s| !s.is_empty())
.map(|s| s.parse().expect("not a usize")) .map(|s| s.parse().expect("not a usize"))
.collect(); .collect();
let attachment = attachment_bytes(&nm, &mid, &idx)?; let attachment = attachment_bytes(&nm, &mid, &idx)?;

View File

@ -87,6 +87,7 @@ pub fn extract_body(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body,
// APPLICATION_ZIP and APPLICATION_GZIP are handled in the thread function // APPLICATION_ZIP and APPLICATION_GZIP are handled in the thread function
APPLICATION_ZIP => extract_unhandled(m), APPLICATION_ZIP => extract_unhandled(m),
APPLICATION_GZIP => extract_unhandled(m), APPLICATION_GZIP => extract_unhandled(m),
mt if mt.starts_with("application/") => Ok(Body::text("".to_string())),
_ => extract_unhandled(m), _ => extract_unhandled(m),
}; };
if let Err(err) = ret { if let Err(err) = ret {
@ -362,7 +363,7 @@ pub fn extract_mixed(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body
.subparts .subparts
.iter() .iter()
.map(|sp| sp.ctype.mimetype.as_str()) .map(|sp| sp.ctype.mimetype.as_str())
.filter(|mt| !handled_types.contains(mt)) .filter(|mt| !handled_types.contains(mt) && !mt.starts_with("application/"))
.collect(); .collect();
unhandled_types.sort(); unhandled_types.sort();
if !unhandled_types.is_empty() { if !unhandled_types.is_empty() {
@ -434,7 +435,11 @@ pub fn extract_mixed(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body
))); )));
} }
} }
mt => parts.push(unhandled_html(MULTIPART_MIXED, mt)), mt => {
if !mt.starts_with("application/") {
parts.push(unhandled_html(MULTIPART_MIXED, mt))
}
}
} }
part_addr.pop(); part_addr.pop();
} }
@ -500,7 +505,7 @@ pub fn extract_related(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Bo
.subparts .subparts
.iter() .iter()
.map(|sp| sp.ctype.mimetype.as_str()) .map(|sp| sp.ctype.mimetype.as_str())
.filter(|mt| !handled_types.contains(mt)) .filter(|mt| !handled_types.contains(mt) && !mt.starts_with("application/"))
.collect(); .collect();
unhandled_types.sort(); unhandled_types.sort();
if !unhandled_types.is_empty() { if !unhandled_types.is_empty() {
@ -580,6 +585,13 @@ pub fn walk_attachments_inner<T, F: Fn(&ParsedMail, &[usize]) -> Option<T> + Cop
// get the bytes for serving attachments of HTTP // get the bytes for serving attachments of HTTP
pub fn extract_attachments(m: &ParsedMail, id: &str) -> Result<Vec<Attachment>, ServerError> { pub fn extract_attachments(m: &ParsedMail, id: &str) -> Result<Vec<Attachment>, ServerError> {
let mut attachments = Vec::new(); let mut attachments = Vec::new();
if m.ctype.mimetype.starts_with("application/") {
if let Some(attachment) = extract_attachment(m, id, &[]) {
attachments.push(attachment);
}
}
for (idx, sp) in m.subparts.iter().enumerate() { for (idx, sp) in m.subparts.iter().enumerate() {
if let Some(attachment) = extract_attachment(sp, id, &[idx]) { if let Some(attachment) = extract_attachment(sp, id, &[idx]) {
// Filter out inline attachements, they're flattened into the body of the message. // Filter out inline attachements, they're flattened into the body of the message.
@ -602,12 +614,30 @@ pub fn extract_attachment(m: &ParsedMail, id: &str, idx: &[usize]) -> Option<Att
pct.map(|pct| pct.params.get("name").map(|f| f.clone())), pct.map(|pct| pct.params.get("name").map(|f| f.clone())),
) { ) {
// Use filename from Content-Disposition // Use filename from Content-Disposition
(Some(filename), _) => filename, (Some(filename), _) => Some(filename),
// Use filename from Content-Type // Use filename from Content-Type
(_, Some(Some(name))) => name, (_, Some(Some(name))) => Some(name),
// No known filename, assume it's not an attachment // No known filename
_ => return None, _ => None,
}; };
let filename = if let Some(fname) = filename {
fname
} else {
if m.ctype.mimetype.starts_with("application/") {
// Generate a default filename
format!(
"attachment-{}",
idx.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(".")
)
} else {
return None;
}
};
info!("filename {}", filename); info!("filename {}", filename);
// TODO: grab this from somewhere // TODO: grab this from somewhere

View File

@ -479,6 +479,12 @@ pub fn attachment_bytes(nm: &Notmuch, id: &str, idx: &[usize]) -> Result<Attachm
let file = File::open(&path)?; let file = File::open(&path)?;
let mmap = unsafe { MmapOptions::new().map(&file)? }; let mmap = unsafe { MmapOptions::new().map(&file)? };
let m = parse_mail(&mmap)?; let m = parse_mail(&mmap)?;
if idx.is_empty() {
let Some(attachment) = extract_attachment(&m, id, &[]) else {
return Err(ServerError::PartNotFound);
};
return Ok(attachment);
}
if let Some(attachment) = walk_attachments(&m, |sp, cur_idx| { if let Some(attachment) = walk_attachments(&m, |sp, cur_idx| {
if cur_idx == idx { if cur_idx == idx {
let attachment = extract_attachment(&sp, id, idx).unwrap_or(Attachment { let attachment = extract_attachment(&sp, id, idx).unwrap_or(Attachment {