diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs index 7f54ea80..88475d20 100644 --- a/melib/src/email/attachments.rs +++ b/melib/src/email/attachments.rs @@ -172,7 +172,15 @@ impl AttachmentBuilder { let mut name: Option = None; for (n, v) in params { if n.eq_ignore_ascii_case(b"name") { - name = Some(String::from_utf8_lossy(v).into()); + if let Ok(v) = crate::email::parser::phrase(v.trim()) + .to_full_result() + .as_ref() + .and_then(|r| Ok(String::from_utf8_lossy(r).to_string())) + { + name = Some(v); + } else { + name = Some(String::from_utf8_lossy(v).into()); + } break; } } @@ -334,6 +342,10 @@ impl fmt::Display for Attachment { ContentType::OctetStream { ref name } => { write!(f, "{}", name.clone().unwrap_or_else(|| self.mime_type())) } + ContentType::Other { + name: Some(ref name), + .. + } => write!(f, "\"{}\", [{}]", name, self.mime_type()), ContentType::Other { .. } => write!(f, "Data attachment of type {}", self.mime_type()), ContentType::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()), ContentType::Multipart { diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index df5f06a5..9a9f6028 100644 --- a/ui/src/components/mail/view.rs +++ b/ui/src/components/mail/view.rs @@ -957,6 +957,112 @@ impl Component for MailView { UIEvent::EnvelopeRename(old_hash, new_hash) if self.coordinates.2 == old_hash => { self.coordinates.2 = new_hash; } + UIEvent::Action(View(ViewAction::SaveAttachment(a_i, ref path))) => { + use std::io::Write; + let account = &mut context.accounts[self.coordinates.0]; + let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2); + let op = account.operation(envelope.hash()); + + let attachments = match envelope.body(op) { + Ok(body) => body.attachments(), + Err(e) => { + context.replies.push_back(UIEvent::Notification( + Some("Failed to open e-mail".to_string()), + e.to_string(), + Some(NotificationType::ERROR), + )); + log( + format!( + "Failed to open envelope {}: {}", + envelope.message_id_display(), + e.to_string() + ), + ERROR, + ); + return true; + } + }; + if let Some(u) = attachments.get(a_i) { + match u.content_type() { + ContentType::MessageRfc822 + | ContentType::Text { .. } + | ContentType::PGPSignature => { + debug!(path); + let mut f = match std::fs::File::create(path) { + Err(e) => { + context.replies.push_back(UIEvent::Notification( + Some(format!("Failed to create file at {}", path)), + e.to_string(), + Some(NotificationType::ERROR), + )); + log( + format!( + "Failed to create file at {}: {}", + path, + e.to_string() + ), + ERROR, + ); + return true; + } + Ok(f) => f, + }; + + f.write_all(&decode(u, None)).unwrap(); + f.flush().unwrap(); + } + + ContentType::Multipart { .. } => { + context.replies.push_back(UIEvent::StatusEvent( + StatusEvent::DisplayMessage( + "Multipart attachments are not supported yet.".to_string(), + ), + )); + return true; + } + ContentType::OctetStream { name: ref _name } + | ContentType::Other { + name: ref _name, .. + } => { + let mut f = match std::fs::File::create(path.trim()) { + Err(e) => { + context.replies.push_back(UIEvent::Notification( + Some(format!("Failed to create file at {}", path)), + e.to_string(), + Some(NotificationType::ERROR), + )); + log( + format!( + "Failed to create file at {}: {}", + path, + e.to_string() + ), + ERROR, + ); + return true; + } + Ok(f) => f, + }; + + f.write_all(&decode(u, None)).unwrap(); + f.flush().unwrap(); + } + } + context.replies.push_back(UIEvent::Notification( + None, + format!("Saved at {}", &path), + Some(NotificationType::INFO), + )); + } else { + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!( + "Attachment `{}` not found.", + a_i + )))); + return true; + } + } UIEvent::Action(MailingListAction(ref e)) => { let unsafe_context = context as *mut Context; let account = &context.accounts[self.coordinates.0]; diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 91e06a12..29f1d5a5 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -531,7 +531,7 @@ impl Component for Pager { UIEvent::ChangeMode(UIMode::Normal) => { self.dirty = true; } - UIEvent::Action(Pager(Pipe(ref bin, ref args))) => { + UIEvent::Action(View(Pipe(ref bin, ref args))) => { use std::io::Write; use std::process::{Command, Stdio}; let mut command_obj = match Command::new(bin) @@ -987,6 +987,7 @@ impl Component for StatusBar { self.cmd_history.push(self.ex_buffer.as_str().to_string()); } self.ex_buffer.clear(); + self.ex_buffer_cmd_history_pos.take(); } UIMode::Execute => { self.height = 2; @@ -1019,6 +1020,7 @@ impl Component for StatusBar { UIEvent::ExInput(Key::Ctrl('u')) => { self.dirty = true; self.ex_buffer.clear(); + self.ex_buffer_cmd_history_pos.take(); return true; } UIEvent::ExInput(k @ Key::Backspace) | UIEvent::ExInput(k @ Key::Ctrl('h')) => { diff --git a/ui/src/execute.rs b/ui/src/execute.rs index 8741058f..6387a32e 100644 --- a/ui/src/execute.rs +++ b/ui/src/execute.rs @@ -32,8 +32,8 @@ pub use crate::actions::Action::{self, *}; pub use crate::actions::ComposeAction::{self, *}; pub use crate::actions::ListingAction::{self, *}; pub use crate::actions::MailingListAction::{self, *}; -pub use crate::actions::PagerAction::{self, *}; pub use crate::actions::TabAction::{self, *}; +pub use crate::actions::ViewAction::{self, *}; use std::str::FromStr; /* Create a const table with every command part that can be auto-completed and its description */ @@ -180,12 +180,12 @@ define_commands!([ >> is_a!(" ") >> args: separated_list!(is_a!(" "), is_not!(" ")) >> ({ - Pager(Pipe(bin.to_string(), args.into_iter().map(|v| String::from_utf8(v.to_vec()).unwrap()).collect::>())) + View(Pipe(bin.to_string(), args.into_iter().map(|v| String::from_utf8(v.to_vec()).unwrap()).collect::>())) })) | do_parse!( ws!(tag!("pipe")) >> bin: ws!(map_res!(is_not!(" "), std::str::from_utf8)) >> ({ - Pager(Pipe(bin.to_string(), Vec::new())) + View(Pipe(bin.to_string(), Vec::new())) }) )) ); @@ -320,6 +320,19 @@ define_commands!([ ) ); ) + }, + { tags: ["save-attachment "], + desc: "save-attachment INDEX PATH", + parser:( + named!( save_attachment, + do_parse!( + ws!(tag!("save-attachment")) + >> idx: map_res!(map_res!(is_not!(" "), std::str::from_utf8), usize::from_str) + >> path: ws!(map_res!(call!(not_line_ending), std::str::from_utf8)) + >> (View(SaveAttachment(idx, path.to_string()))) + ) + ); + ) } ]); @@ -379,6 +392,8 @@ named!( named!(account_action, alt_complete!(reindex)); +named!(view, alt_complete!(pipe | save_attachment)); + named!(pub parse_command, - alt_complete!( goto | listing_action | sort | subsort | close | mailinglist | setenv | printenv | pipe | compose_action | create_folder | sub_folder | unsub_folder | delete_folder | rename_folder | account_action ) + alt_complete!( goto | listing_action | sort | subsort | close | mailinglist | setenv | printenv | view | compose_action | create_folder | sub_folder | unsub_folder | delete_folder | rename_folder | account_action ) ); diff --git a/ui/src/execute/actions.rs b/ui/src/execute/actions.rs index a76a678c..07f79c57 100644 --- a/ui/src/execute/actions.rs +++ b/ui/src/execute/actions.rs @@ -63,8 +63,9 @@ pub enum MailingListAction { } #[derive(Debug)] -pub enum PagerAction { +pub enum ViewAction { Pipe(String, Vec), + SaveAttachment(usize, String), } #[derive(Debug)] @@ -88,7 +89,7 @@ pub enum Action { Tab(TabAction), ToggleThreadSnooze, MailingListAction(MailingListAction), - Pager(PagerAction), + View(ViewAction), SetEnv(String, String), PrintEnv(String), Compose(ComposeAction),