diff --git a/meli/src/mail/compose.rs b/meli/src/mail/compose.rs index 1633e0f0..7b2d2768 100644 --- a/meli/src/mail/compose.rs +++ b/meli/src/mail/compose.rs @@ -41,6 +41,7 @@ use crate::{ accounts::JobRequest, jobs::{IsAsync, JoinHandle}, terminal::embedded::Terminal, + types::{sanitize_filename, File}, }; #[cfg(feature = "gpgme")] @@ -1867,10 +1868,25 @@ impl Component for Composer { account_settings!(context[self.account_hash].composing.wrap_header_preamble) .clone(), ); + let filename = format!( + "{date}_{subject}_{to}_{in_reply_to}", + date = self.draft.headers.get(HeaderName::DATE).unwrap_or_default(), + subject = self + .draft + .headers + .get(HeaderName::SUBJECT) + .unwrap_or_default(), + to = self.draft.headers.get(HeaderName::TO).unwrap_or_default(), + in_reply_to = self + .draft + .headers + .get(HeaderName::IN_REPLY_TO) + .unwrap_or_default() + ); let f = match File::create_temp_file( self.draft.to_edit_string().as_bytes(), - None, + sanitize_filename(filename).as_deref(), None, Some("eml"), true, diff --git a/meli/src/types.rs b/meli/src/types.rs index 17d6cf15..3d82ba7d 100644 --- a/meli/src/types.rs +++ b/meli/src/types.rs @@ -33,12 +33,8 @@ //! [`UIEvent`] is the type passed around //! [`Component`]'s when something happens. -#[macro_use] -mod helpers; - use std::{borrow::Cow, sync::Arc}; -pub use helpers::*; use indexmap::IndexMap; use melib::{ backends::{AccountHash, BackendEvent, MailboxHash}, @@ -54,6 +50,10 @@ use super::{ }; use crate::components::{Component, ComponentId, ScrollUpdate}; +#[macro_use] +mod helpers; +pub use helpers::*; + pub type UIMessage = Box; #[derive(Debug)] diff --git a/meli/src/types/helpers.rs b/meli/src/types/helpers.rs index 92f5840e..bbc1749a 100644 --- a/meli/src/types/helpers.rs +++ b/meli/src/types/helpers.rs @@ -91,7 +91,11 @@ impl File { dir.push("meli"); std::fs::DirBuilder::new().recursive(true).create(&dir)?; if let Some(filename) = filename { - dir.push(filename) + dir.push(filename); + while dir.try_exists().unwrap_or_default() { + dir.pop(); + dir.push(format!("{filename}_{}", Uuid::new_v4().as_simple())); + } } else { let u = Uuid::new_v4(); dir.push(u.as_simple().to_string()); @@ -136,6 +140,25 @@ pub fn pipe() -> Result<(OwnedFd, OwnedFd)> { }) } +/// Create a shell-friendly filename by removing control characters and +/// replacing characters that need escaping. +pub fn sanitize_filename(og: String) -> Option { + use regex::Regex; + + let regex = Regex::new(r"(?m)[[:space:]]+").ok()?; // _ + let mut ret = regex.replace_all(&og, "_").to_string(); + let regex = Regex::new(r"(?m)[[:punct:]]").ok()?; // - + ret = regex.replace_all(&ret, "-").to_string(); + let regex = Regex::new(r"(?m)[[:cntrl:]]*").ok()?; // + ret = regex.replace_all(&ret, "").to_string(); + let regex = Regex::new(r"(?m)[[:blank:]]*").ok()?; // + ret = regex.replace_all(&ret, "").to_string(); + let regex = Regex::new(r"^[[:punct:]]*").ok()?; // + ret = regex.replace_all(&ret, "").to_string(); + let regex = Regex::new(r"[[:punct:]]*$").ok()?; // + Some(regex.replace_all(&ret, "").to_string()) +} + #[cfg(test)] mod tests { use super::*; @@ -180,4 +203,12 @@ mod tests { _ = tempdir.close(); } + + #[test] + fn test_file_sanitize_filename() { + assert_eq!( + sanitize_filename("Re: Some long subject - \"User Dot. Name\" Sent from my bPad 2024-09-07, on a sunny Saturday".to_string()), + Some("Re--Some-long-subject----User-Dot--Name---user1-example-com--Sent-from-my-bPad-2024-09-07--on-a-sunny-Saturday".to_string()) + ); + } }