summaryrefslogtreecommitdiffstats
path: root/src/components/mail/view.rs
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2020-10-05 18:43:08 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2020-10-05 21:10:00 +0300
commit23ca41e3e878abb9e9206e984b6c1e1d2b905aaa (patch)
tree0a3c053b304fa349cfa999d3bdb2b1814ca7632a /src/components/mail/view.rs
parentb9c07bacef256e781a2f6884f9e01f8f211d4741 (diff)
add libgpgme feature
Diffstat (limited to 'src/components/mail/view.rs')
-rw-r--r--src/components/mail/view.rs1391
1 files changed, 981 insertions, 410 deletions
diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs
index 726cfef2..3329c82f 100644
--- a/src/components/mail/view.rs
+++ b/src/components/mail/view.rs
@@ -22,6 +22,7 @@
use super::*;
use crate::conf::accounts::JobRequest;
use crate::jobs::{oneshot, JobId};
+use melib::email::attachment_types::ContentType;
use melib::list_management;
use melib::parser::BytesExt;
use smallvec::SmallVec;
@@ -29,6 +30,7 @@ use std::collections::HashSet;
use std::io::Write;
use std::convert::TryFrom;
+use std::os::unix::fs::PermissionsExt;
use std::process::{Command, Stdio};
mod html;
@@ -39,7 +41,7 @@ pub use self::thread::*;
mod envelope;
pub use self::envelope::*;
-use linkify::{Link, LinkFinder};
+use linkify::LinkFinder;
use xdg_utils::query_default_app;
#[derive(PartialEq, Copy, Clone, Debug)]
@@ -54,7 +56,7 @@ enum ViewMode {
Url,
Attachment(usize),
Source(Source),
- Ansi(RawBuffer),
+ //Ansi(RawBuffer),
Subview,
ContactSelector(UIDialog<Card>),
}
@@ -66,12 +68,14 @@ impl Default for ViewMode {
}
impl ViewMode {
+ /*
fn is_ansi(&self) -> bool {
match self {
ViewMode::Ansi(_) => true,
_ => false,
}
}
+ */
fn is_attachment(&self) -> bool {
match self {
ViewMode::Attachment(_) => true,
@@ -86,6 +90,56 @@ impl ViewMode {
}
}
+#[derive(Debug)]
+pub enum AttachmentDisplay {
+ InlineText {
+ inner: Attachment,
+ text: String,
+ },
+ InlineOther {
+ inner: Attachment,
+ },
+ Attachment {
+ inner: Attachment,
+ },
+ SignedPending {
+ inner: Attachment,
+ display: Vec<AttachmentDisplay>,
+ chan:
+ std::result::Result<oneshot::Receiver<Result<()>>, oneshot::Receiver<Result<Vec<u8>>>>,
+ job_id: JobId,
+ },
+ SignedFailed {
+ inner: Attachment,
+ display: Vec<AttachmentDisplay>,
+ error: MeliError,
+ },
+ SignedUnverified {
+ inner: Attachment,
+ display: Vec<AttachmentDisplay>,
+ },
+ SignedVerified {
+ inner: Attachment,
+ display: Vec<AttachmentDisplay>,
+ description: String,
+ },
+ EncryptedPending {
+ inner: Attachment,
+ chan: oneshot::Receiver<Result<(melib::pgp::DecryptionMetadata, Vec<u8>)>>,
+ job_id: JobId,
+ },
+ EncryptedFailed {
+ inner: Attachment,
+ error: MeliError,
+ },
+ EncryptedSuccess {
+ inner: Attachment,
+ plaintext: Attachment,
+ plaintext_display: Vec<AttachmentDisplay>,
+ description: String,
+ },
+}
+
/// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more
/// menus
#[derive(Debug, Default)]
@@ -98,6 +152,7 @@ pub struct MailView {
mode: ViewMode,
expand_headers: bool,
attachment_tree: String,
+ attachment_paths: Vec<Vec<usize>>,
headers_no: usize,
headers_cursor: usize,
force_draw_headers: bool,
@@ -117,7 +172,7 @@ pub enum PendingReplyAction {
}
#[derive(Debug)]
-pub enum MailViewState {
+enum MailViewState {
Init {
pending_action: Option<PendingReplyAction>,
},
@@ -132,10 +187,25 @@ pub enum MailViewState {
Loaded {
bytes: Vec<u8>,
body: Attachment,
+ display: Vec<AttachmentDisplay>,
body_text: String,
+ links: Vec<Link>,
},
}
+#[derive(Copy, Clone, Debug)]
+enum LinkKind {
+ Url,
+ Email,
+}
+
+#[derive(Debug, Copy, Clone)]
+struct Link {
+ start: usize,
+ end: usize,
+ kind: LinkKind,
+}
+
impl Default for MailViewState {
fn default() -> Self {
MailViewState::Init {
@@ -152,6 +222,7 @@ impl Clone for MailView {
pager: self.pager.clone(),
mode: ViewMode::Normal,
attachment_tree: self.attachment_tree.clone(),
+ attachment_paths: self.attachment_paths.clone(),
state: MailViewState::default(),
active_jobs: self.active_jobs.clone(),
..*self
@@ -182,6 +253,7 @@ impl MailView {
mode: ViewMode::Normal,
expand_headers: false,
attachment_tree: String::new(),
+ attachment_paths: vec![],
headers_no: 5,
headers_cursor: 0,
@@ -235,11 +307,24 @@ impl MailView {
.populate_headers(&bytes);
}
let body = AttachmentBuilder::new(&bytes).build();
- let body_text = self.attachment_to_text(&body, context);
+ let display = Self::attachment_to(
+ &body,
+ context,
+ self.coordinates,
+ &mut self.active_jobs,
+ );
+ let (paths, attachment_tree_s) =
+ self.attachment_displays_to_tree(&display);
+ self.attachment_tree = attachment_tree_s;
+ self.attachment_paths = paths;
+ let body_text =
+ self.attachment_displays_to_text(&display, context);
self.state = MailViewState::Loaded {
+ display,
body,
bytes,
body_text,
+ links: vec![],
};
}
Err(err) => {
@@ -331,132 +416,418 @@ impl MailView {
.push_back(UIEvent::Action(Tab(New(Some(composer)))));
}
- /// Returns the string to be displayed in the Viewer
- fn attachment_to_text(&mut self, body: &Attachment, context: &mut Context) -> String {
- let finder = LinkFinder::new();
- let coordinates = self.coordinates;
- let body_text = String::from_utf8_lossy(&decode_rec(
- body,
- Some(Box::new(move |a: &Attachment, v: &mut Vec<u8>| {
- if a.content_type().is_text_html() {
- /* FIXME: duplication with view/html.rs */
- if let Some(filter_invocation) =
- mailbox_settings!(context[coordinates.0][&coordinates.1].pager.html_filter)
- .as_ref()
- {
- let command_obj = Command::new("sh")
- .args(&["-c", filter_invocation])
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .spawn();
- match command_obj {
- Err(err) => {
- context.replies.push_back(UIEvent::Notification(
- Some(format!(
- "Failed to start html filter process: {}",
- filter_invocation,
- )),
- err.to_string(),
- Some(NotificationType::Error(melib::ErrorKind::External)),
- ));
- return;
- }
- Ok(mut html_filter) => {
- html_filter
- .stdin
- .as_mut()
- .unwrap()
- .write_all(&v)
- .expect("Failed to write to stdin");
- *v = format!(
+ fn attachment_displays_to_text(
+ &self,
+ displays: &[AttachmentDisplay],
+ context: &mut Context,
+ ) -> String {
+ let mut acc = String::new();
+ for d in displays {
+ use AttachmentDisplay::*;
+ match d {
+ InlineText { inner: _, text } => acc.push_str(&text),
+ InlineOther { inner } => {
+ if !acc.ends_with("\n\n") {
+ acc.push_str("\n\n");
+ }
+ acc.push_str(&inner.to_string());
+ if !acc.ends_with("\n\n") {
+ acc.push_str("\n\n");
+ }
+ }
+ Attachment { inner: _ } => {}
+ SignedPending {
+ inner: _,
+ display,
+ chan: _,
+ job_id: _,
+ } => {
+ acc.push_str("Waiting for signature verification.\n\n");
+ acc.push_str(&self.attachment_displays_to_text(display, context));
+ }
+ SignedUnverified { inner: _, display } => {
+ acc.push_str("Unverified signature.\n\n");
+ acc.push_str(&self.attachment_displays_to_text(display, context))
+ }
+ SignedFailed {
+ inner: _,
+ display,
+ error,
+ } => {
+ acc.push_str(&format!("Failed to verify signature: {}.\n\n", error));
+ acc.push_str(&self.attachment_displays_to_text(display, context));
+ }
+ SignedVerified {
+ inner: _,
+ display,
+ description,
+ } => {
+ if description.is_empty() {
+ acc.push_str("Verified signature.\n\n");
+ } else {
+ acc.push_str(&description);
+ acc.push_str("\n\n");
+ }
+ acc.push_str(&self.attachment_displays_to_text(display, context));
+ }
+ EncryptedPending { .. } => acc.push_str("Waiting for decryption result."),
+ EncryptedFailed { inner: _, error } => {
+ acc.push_str(&format!("Decryption failed: {}.", &error))
+ }
+ EncryptedSuccess {
+ inner: _,
+ plaintext: _,
+ plaintext_display,
+ description,
+ } => {
+ if description.is_empty() {
+ acc.push_str("Succesfully decrypted.\n\n");
+ } else {
+ acc.push_str(&description);
+ acc.push_str("\n\n");
+ }
+ acc.push_str(&self.attachment_displays_to_text(plaintext_display, context));
+ }
+ }
+ }
+ acc
+ }
+
+ fn attachment_displays_to_tree(
+ &self,
+ displays: &[AttachmentDisplay],
+ ) -> (Vec<Vec<usize>>, String) {
+ let mut acc = String::new();
+ let mut branches = SmallVec::new();
+ let mut paths = Vec::with_capacity(displays.len());
+ let mut cur_path = vec![];
+ let mut idx = 0;
+ for (i, d) in displays.iter().enumerate() {
+ use AttachmentDisplay::*;
+ cur_path.push(i);
+ match d {
+ InlineText { inner, text: _ }
+ | InlineOther { inner }
+ | Attachment { inner }
+ | SignedPending {
+ inner,
+ display: _,
+ chan: _,
+ job_id: _,
+ }
+ | SignedUnverified { inner, display: _ }
+ | SignedFailed {
+ inner,
+ display: _,
+ error: _,
+ }
+ | SignedVerified {
+ inner,
+ display: _,
+ description: _,
+ }
+ | EncryptedPending {
+ inner,
+ chan: _,
+ job_id: _,
+ }
+ | EncryptedFailed { inner, error: _ }
+ | EncryptedSuccess {
+ inner: _,
+ plaintext: inner,
+ plaintext_display: _,
+ description: _,
+ } => {
+ attachment_tree(
+ (&mut idx, (0, inner)),
+ &mut branches,
+ &mut paths,
+ &mut cur_path,
+ i + 1 < displays.len(),
+ &mut acc,
+ );
+ }
+ }
+ cur_path.pop();
+ idx += 1;
+ }
+ (paths, acc)
+ }
+
+ fn attachment_to(
+ body: &Attachment,
+ context: &mut Context,
+ coordinates: (AccountHash, MailboxHash, EnvelopeHash),
+ active_jobs: &mut HashSet<JobId>,
+ ) -> Vec<AttachmentDisplay> {
+ let mut ret = vec![];
+ fn rec(
+ a: &Attachment,
+ context: &mut Context,
+ coordinates: (AccountHash, MailboxHash, EnvelopeHash),
+ acc: &mut Vec<AttachmentDisplay>,
+ active_jobs: &mut HashSet<JobId>,
+ ) {
+ if a.content_disposition.kind.is_attachment() {
+ acc.push(AttachmentDisplay::Attachment { inner: a.clone() });
+ } else if a.content_type().is_text_html() {
+ let bytes = decode(a, None);
+ let filter_invocation =
+ mailbox_settings!(context[coordinates.0][&coordinates.1].pager.html_filter)
+ .as_ref()
+ .map(|s| s.as_str())
+ .unwrap_or("w3m -I utf-8 -T text/html");
+ let command_obj = Command::new("sh")
+ .args(&["-c", filter_invocation])
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn();
+ match command_obj {
+ Err(err) => {
+ context.replies.push_back(UIEvent::Notification(
+ Some(format!(
+ "Failed to start html filter process: {}",
+ filter_invocation,
+ )),
+ err.to_string(),
+ Some(NotificationType::Error(melib::ErrorKind::External)),
+ ));
+ let mut s = format!(
+ "Failed to start html filter process: `{}`. Press `v` to open in web browser. \n\n",
+ filter_invocation
+ );
+ s.push_str(&String::from_utf8_lossy(&bytes));
+ acc.push(AttachmentDisplay::InlineText {
+ inner: a.clone(),
+ text: s,
+ });
+ }
+ Ok(mut html_filter) => {
+ html_filter
+ .stdin
+ .as_mut()
+ .unwrap()
+ .write_all(&bytes)
+ .expect("Failed to write to stdin");
+ let mut s = format!(
"Text piped through `{}`. Press `v` to open in web browser. \n\n",
filter_invocation
- )
- .into_bytes();
- v.extend(html_filter.wait_with_output().unwrap().stdout);
+ );
+ s.push_str(&String::from_utf8_lossy(
+ &html_filter.wait_with_output().unwrap().stdout,
+ ));
+ acc.push(AttachmentDisplay::InlineText {
+ inner: a.clone(),
+ text: s,
+ });
+ }
+ }
+ } else if a.is_text() {
+ let bytes = decode(a, None);
+ acc.push(AttachmentDisplay::InlineText {
+ inner: a.clone(),
+ text: String::from_utf8_lossy(&bytes).to_string(),
+ });
+ } else if let ContentType::Multipart {
+ ref kind,
+ ref parts,
+ ..
+ } = a.content_type
+ {
+ match kind {
+ MultipartType::Alternative => {
+ if let Some(text_attachment_pos) =
+ parts.iter().position(|a| a.content_type == "text/plain")
+ {
+ let bytes = decode(&parts[text_attachment_pos], None);
+ acc.push(AttachmentDisplay::InlineText {
+ inner: a.clone(),
+ text: String::from_utf8_lossy(&bytes).to_string(),
+ });
+ } else {
+ for a in parts {
+ rec(a, context, coordinates, acc, active_jobs);
}
}
- } else {
- match Command::new("w3m")
- .args(&["-I", "utf-8", "-T", "text/html"])
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .spawn()
- {
- Ok(mut html_filter) => {
- html_filter
- .stdin
- .as_mut()
- .unwrap()
- .write_all(&v)
- .expect("Failed to write to html filter stdin");
- *v = String::from(
- "Text piped through `w3m`. Press `v` to open in web browser. \n\n",
- )
- .into_bytes();
- v.extend(html_filter.wait_with_output().unwrap().stdout);
+ }
+ MultipartType::Signed => {
+ if *mailbox_settings!(
+ context[coordinates.0][&coordinates.1]
+ .pgp
+ .auto_verify_signatures
+ ) {
+ if let Some(bin) = mailbox_settings!(
+ context[coordinates.0][&coordinates.1].pgp.gpg_binary
+ ) {
+ let verify_fut = crate::components::mail::pgp::verify(
+ a.clone(),
+ Some(bin.to_string()),
+ );
+ let (chan, _handle, job_id) =
+ context.job_executor.spawn_blocking(verify_fut);
+ active_jobs.insert(job_id);
+ acc.push(AttachmentDisplay::SignedPending {
+ inner: a.clone(),
+ display: {
+ let mut v = vec![];
+ rec(&parts[0], context, coordinates, &mut v, active_jobs);
+ v
+ },
+ chan: Err(chan),
+ job_id,
+ });
+ } else {
+ #[cfg(not(feature = "gpgme"))]
+ {
+ acc.push(AttachmentDisplay::SignedUnverified {
+ inner: a.clone(),
+ display: {
+ let mut v = vec![];
+ rec(
+ &parts[0],
+ context,
+ coordinates,
+ &mut v,
+ active_jobs,
+ );
+ v
+ },
+ });
+ }
+ #[cfg(feature = "gpgme")]
+ match melib::gpgme::Context::new().and_then(|mut ctx| {
+ let sig = ctx.new_data_mem(&parts[1].raw())?;
+ let mut f = std::fs::File::create("/tmp/sig").unwrap();
+ f.write_all(&parts[1].raw())?;
+ let mut f = std::fs::File::create("/tmp/data").unwrap();
+ f.write_all(&parts[0].raw())?;
+ let data = ctx.new_data_mem(&parts[0].raw())?;
+ ctx.verify(sig, data)
+ }) {
+ Ok(verify_fut) => {
+ let (chan, _handle, job_id) =
+ context.job_executor.spawn_specialized(verify_fut);
+ active_jobs.insert(job_id);
+ acc.push(AttachmentDisplay::SignedPending {
+ inner: a.clone(),
+ display: {
+ let mut v = vec![];
+ rec(
+ &parts[0],
+ context,
+ coordinates,
+ &mut v,
+ active_jobs,
+ );
+ v
+ },
+ chan: Ok(chan),
+ job_id,
+ });
+ }
+ Err(error) => {
+ acc.push(AttachmentDisplay::SignedFailed {
+ inner: a.clone(),
+ display: {
+ let mut v = vec![];
+ rec(
+ &parts[0],
+ context,
+ coordinates,
+ &mut v,
+ active_jobs,
+ );
+ v
+ },
+ error,
+ });
+ }
+ }
}
- Err(err) => {
- context.replies.push_back(UIEvent::Notification(
- Some("Failed to launch w3m to use as html filter".to_string()),
- err.to_string(),
- Some(NotificationType::Error(melib::ErrorKind::External)),
- ));
+ } else {
+ acc.push(AttachmentDisplay::SignedUnverified {
+ inner: a.clone(),
+ display: {
+ let mut v = vec![];
+ rec(&parts[0], context, coordinates, &mut v, active_jobs);
+ v
+ },
+ });
+ }
+ }
+ MultipartType::Encrypted => {
+ for a in parts {
+ if a.content_type == "application/octet-stream" {
+ if *mailbox_settings!(
+ context[coordinates.0][&coordinates.1].pgp.auto_decrypt
+ ) {
+ let _verify = *mailbox_settings!(
+ context[coordinates.0][&coordinates.1]
+ .pgp
+ .auto_verify_signatures
+ );
+ if let Some(bin) = mailbox_settings!(
+ context[coordinates.0][&coordinates.1].pgp.gpg_binary
+ ) {
+ let decrypt_fut = crate::components::mail::pgp::decrypt(
+ a.raw().to_vec(),
+ Some(bin.to_string()),
+ None,
+ );
+ let (chan, _handle, job_id) =
+ context.job_executor.spawn_blocking(decrypt_fut);
+ active_jobs.insert(job_id);
+ acc.push(AttachmentDisplay::EncryptedPending {
+ inner: a.clone(),
+ chan,
+ job_id,
+ });
+ } else {
+ #[cfg(not(feature = "gpgme"))]
+ {
+ acc.push(AttachmentDisplay::EncryptedFailed {
+ inner: a.clone(),
+ error: MeliError::new("Cannot decrypt: define `gpg_binary` in configuration."),
+ });
+ }
+ #[cfg(feature = "gpgme")]
+ match melib::gpgme::Context::new().and_then(|mut ctx| {
+ let cipher = ctx.new_data_mem(&a.raw())?;
+ ctx.decrypt(cipher)
+ }) {
+ Ok(decrypt_fut) => {
+ let (chan, _handle, job_id) = context
+ .job_executor
+ .spawn_specialized(decrypt_fut);
+ active_jobs.insert(job_id);
+ acc.push(AttachmentDisplay::EncryptedPending {
+ inner: a.clone(),
+ chan,
+ job_id,
+ });
+ }
+ Err(error) => {
+ acc.push(AttachmentDisplay::EncryptedFailed {
+ inner: a.clone(),
+ error,
+ });
+ }
+ }
+ }
+ }
}
}
}
- } else if a.is_signed() {
- v.clear();
- if context.settings.pgp.auto_verify_signatures {
- v.extend(crate::mail::pgp::verify_signature(a, context).into_iter());
+ _ => {
+ for a in parts {
+ rec(a, context, coordinates, acc, active_jobs);
+ }
}
}
- })),
- ))
- .into_owned();
- if body.count_attachments() > 1 {
- self.attachment_tree.clear();
- attachment_tree(
- (&mut 0, (0, &body)),
- &mut SmallVec::new(),
- false,
- &mut self.attachment_tree,
- );
- }
- match self.mode {
- ViewMode::Normal
- | ViewMode::Subview
- | ViewMode::ContactSelector(_)
- | ViewMode::Source(Source::Decoded) => {
- format!("{}\n\n{}", body_text, self.attachment_tree)
- }
- ViewMode::Source(Source::Raw) => String::from_utf8_lossy(body.body()).into_owned(),
- ViewMode::Url => {
- let mut t = body_text;
- for (lidx, l) in finder.links(&body.text()).enumerate() {
- let offset = if lidx < 10 {
- lidx * 3
- } else if lidx < 100 {
- 26 + (lidx - 9) * 4
- } else if lidx < 1000 {
- 385 + (lidx - 99) * 5
- } else {
- panic!("FIXME: Message body with more than 100 urls, fix this");
- };
- t.insert_str(l.start() + offset, &format!("[{}]", lidx));
- }
- t.push_str("\n\n");
- t.push_str(&self.attachment_tree);
- t
}
- ViewMode::Attachment(aidx) => {
- let attachments = body.attachments();
- let mut ret = "Viewing attachment. Press `r` to return \n".to_string();
- ret.push_str(&attachments[aidx].text());
- ret
- }
- ViewMode::Ansi(_) => "Viewing attachment. Press `r` to return \n".to_string(),
- }
+ };
+ rec(body, context, coordinates, &mut ret, active_jobs);
+ ret
}
pub fn update(
@@ -471,167 +842,94 @@ impl MailView {
self.set_dirty(true);
}
- fn open_attachment(&mut self, lidx: usize, context: &mut Context, use_mailcap: bool) {
- let attachments = if let MailViewState::Loaded { ref body, .. } = self.state {
- body.attachments()
- } else if let MailViewState::Error { ref err } = self.state {
- context.replies.push_back(UIEvent::Notification(
- Some("Failed to open e-mail".to_string()),
- err.to_string(),
- Some(NotificationType::Error(err.kind)),
- ));
- log(
- format!("Failed to open envelope: {}", err.to_string()),
- ERROR,
- );
- self.init_futures(context);
- return;
+ fn open_attachment(
+ &'_ self,
+ lidx: usize,
+ context: &mut Context,
+ ) -> Option<&'_ melib::Attachment> {
+ if lidx == 0 {
+ return None;
+ }
+ let display = if let MailViewState::Loaded { ref display, .. } = self.state {
+ display
} else {
- return;
+ return None;
};
- if let Some(u) = attachments.get(lidx) {
- if use_mailcap {
- if let Ok(()) = crate::mailcap::MailcapEntry::execute(u, context) {
- self.set_dirty(true);
- } else {
- context
- .replies
- .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
- "no mailcap entry found for {}",
- u.content_type()
- ))));
- }
- } else {
- match u.content_type() {
- ContentType::MessageRfc822 => {
- match Mail::new(u.body().to_vec(), Some(Flag::SEEN)) {
- Ok(wrapper) => {
- context.replies.push_back(UIEvent::Action(Tab(New(Some(
- Box::new(EnvelopeView::new(
- wrapper,
- None,
- None,
- self.coordinates.0,
- )),
- )))));
- }
- Err(e) => {
- context.replies.push_back(UIEvent::StatusEvent(
- StatusEvent::DisplayMessage(format!("{}", e)),
- ));
- }
- }
- }
-
- ContentType::Text { .. } | ContentType::PGPSignature => {
- self.mode = ViewMode::Attachment(lidx);
- self.initialised = false;
- self.dirty = true;
- }
- ContentType::Multipart { .. } => {
- context.replies.push_back(UIEvent::StatusEvent(
- StatusEvent::DisplayMessage(
- "Multipart attachments are not supported yet.".to_string(),
- ),
- ));
+ if let Some(path) =
+ self.attachment_paths.get(lidx).and_then(
+ |path| {
+ if path.len() > 0 {
+ Some(path)
+ } else {
+ None
}
- ContentType::Other { ref name, .. } => {
- let attachment_type = u.mime_type();
- let binary = query_default_app(&attachment_type);
- let mut name_opt = name.as_ref().and_then(|n| {
- melib::email::parser::encodings::phrase(n.as_bytes(), false)
- .map(|(_, v)| v)
- .ok()
- .and_then(|n| String::from_utf8(n).ok())
- });
- if name_opt.is_none() {
- name_opt = name.as_ref().map(|n| n.clone());
- }
- if let Ok(binary) = binary {
- let p = create_temp_file(
- &decode(u, None),
- name_opt.as_ref().map(String::as_str),
- None,
- true,
- );
- match debug!(context.plugin_manager.activate_hook(
- "attachment-view",
- p.path().display().to_string().into_bytes()
- )) {
- Ok(crate::plugins::FilterResult::Ansi(s)) => {
- if let Some(buf) = crate::terminal::ansi::ansi_to_cellbuffer(&s)
- {
- let raw_buf = RawBuffer::new(buf, name_opt);
- self.mode = ViewMode::Ansi(raw_buf);
- self.initialised = false;
- self.dirty = true;
- return;
- }
- }
- Ok(crate::plugins::FilterResult::UiMessage(s)) => {
- context.replies.push_back(UIEvent::Notification(
- None,
- s,
- Some(NotificationType::Error(melib::ErrorKind::None)),
- ));
- }
- _ => {}
- }
- match Command::new(&binary)
- .arg(p.path())
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .spawn()
- {
- Ok(child) => {
- context.temp_files.push(p);
- context.children.push(child);
- }
- Err(err) => {
- context.replies.push_back(UIEvent::StatusEvent(
- StatusEvent::DisplayMessage(format!(
- "Failed to start {}: {}",
-