Skip to content

Commit 64f34c6

Browse files
committed
fix(server): cache HTML attachments at ~/.cache/mailbrus/html/<id>.html
The unified open_attachment handler wrote every part to /tmp/<id>_<name>, which left HTML attachments at a path the browser was free to sniff as text/plain. - Branch on mime: text/html parts go to $XDG_CACHE_HOME/mailbrus/html/<safe-id>.html (stable, single ".html" suffix); all other parts keep the existing temp_dir behavior. - Open HTML via a file:// URL so it routes through the browser junction (same fix as b41854a, which the new handler had lost). - Other attachments still open with open::that_detached on the bare path.
1 parent 8967d4e commit 64f34c6

1 file changed

Lines changed: 47 additions & 10 deletions

File tree

mailbrus-server/src/handlers/messages.rs

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,16 @@ pub async fn get_attachment(Path((id, part_index)): Path<(String, usize)>) -> Re
303303
}
304304
}
305305

306+
fn html_cache_dir() -> std::path::PathBuf {
307+
std::env::var("XDG_CACHE_HOME")
308+
.map(std::path::PathBuf::from)
309+
.unwrap_or_else(|_| {
310+
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
311+
std::path::PathBuf::from(home).join(".cache")
312+
})
313+
.join("mailbrus/html")
314+
}
315+
306316
pub async fn open_attachment(Path((id, part_index)): Path<(String, usize)>) -> Response {
307317
match tokio::task::spawn_blocking(move || {
308318
let reader = MaildirReader::open()?;
@@ -312,23 +322,50 @@ pub async fn open_attachment(Path((id, part_index)): Path<(String, usize)>) -> R
312322
.await
313323
{
314324
Ok(Ok((id, raw))) => match extract_part(&raw, part_index) {
315-
Some((_mime, name, bytes)) => {
325+
Some((mime, name, bytes)) => {
316326
let safe_id: String = id
317327
.chars()
318328
.map(|c| if c.is_alphanumeric() || c == '-' || c == '_' { c } else { '_' })
319329
.collect();
320-
let safe_name: String = name
321-
.chars()
322-
.map(|c| if c.is_alphanumeric() || matches!(c, '.' | '-' | '_') { c } else { '_' })
323-
.collect();
324-
let tmp_dir = std::env::temp_dir();
325-
let path = tmp_dir.join(format!("{safe_id}_{safe_name}"));
330+
let is_html = mime.eq_ignore_ascii_case("text/html");
331+
// HTML attachments get a stable per-message cache path
332+
// ~/.cache/mailbrus/html/<id>.html so the browser opens them
333+
// as HTML — a `/tmp/<id>_<name>` path can be sniffed as text
334+
// by browsers when the leading bytes don't look like a doc.
335+
let path = if is_html {
336+
let dir = html_cache_dir();
337+
if let Err(e) = std::fs::create_dir_all(&dir) {
338+
warn!("[attach] failed to create cache dir {:?}: {}", dir, e);
339+
return json_error(
340+
StatusCode::INTERNAL_SERVER_ERROR,
341+
"cannot create cache dir",
342+
);
343+
}
344+
dir.join(format!("{safe_id}.html"))
345+
} else {
346+
let safe_name: String = name
347+
.chars()
348+
.map(|c| if c.is_alphanumeric() || matches!(c, '.' | '-' | '_') { c } else { '_' })
349+
.collect();
350+
std::env::temp_dir().join(format!("{safe_id}_{safe_name}"))
351+
};
326352
if let Err(e) = std::fs::write(&path, &bytes) {
327-
warn!("[attach] failed to write tmp file {:?}: {}", path, e);
328-
return json_error(StatusCode::INTERNAL_SERVER_ERROR, "cannot write tmp file");
353+
warn!("[attach] failed to write file {:?}: {}", path, e);
354+
return json_error(
355+
StatusCode::INTERNAL_SERVER_ERROR,
356+
"cannot write attachment file",
357+
);
329358
}
330359
info!("[attach] saved {} → {:?}", id, path);
331-
match open::that_detached(&path) {
360+
// HTML opens via file:// URL so the OS routes it through the
361+
// browser junction (see commit b41854a).
362+
let open_result = if is_html {
363+
let file_url = format!("file://{}", path.display());
364+
open::that_detached(&file_url)
365+
} else {
366+
open::that_detached(&path)
367+
};
368+
match open_result {
332369
Ok(()) => Json(json!({"ok": true, "path": path.to_string_lossy()})).into_response(),
333370
Err(e) => {
334371
warn!("[attach] could not open {:?}: {}", path, e);

0 commit comments

Comments
 (0)