diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-10-05 18:43:08 +0300 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-10-05 21:10:00 +0300 |
commit | 23ca41e3e878abb9e9206e984b6c1e1d2b905aaa (patch) | |
tree | 0a3c053b304fa349cfa999d3bdb2b1814ca7632a /src/components | |
parent | b9c07bacef256e781a2f6884f9e01f8f211d4741 (diff) |
add libgpgme feature
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/mail/compose.rs | 4 | ||||
-rw-r--r-- | src/components/mail/pgp.rs | 171 | ||||
-rw-r--r-- | src/components/mail/view.rs | 1391 |
3 files changed, 1090 insertions, 476 deletions
diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index f3a94e38..10e8700f 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -428,7 +428,7 @@ impl Composer { write_string_to_grid( &format!( "☑ sign with {}", - account_settings!(context[self.account_hash].pgp.key) + account_settings!(context[self.account_hash].pgp.sign_key) .as_ref() .map(|s| s.as_str()) .unwrap_or("default key") @@ -1473,7 +1473,7 @@ pub fn send_draft( account_settings!(context[account_hash].pgp.gpg_binary) .as_ref() .map(|s| s.as_str()), - account_settings!(context[account_hash].pgp.key) + account_settings!(context[account_hash].pgp.sign_key) .as_ref() .map(|s| s.as_str()), ); diff --git a/src/components/mail/pgp.rs b/src/components/mail/pgp.rs index 264c9584..837c8d20 100644 --- a/src/components/mail/pgp.rs +++ b/src/components/mail/pgp.rs @@ -20,15 +20,38 @@ */ use super::*; +use melib::email::pgp as melib_pgp; use std::io::Write; use std::process::{Command, Stdio}; -pub fn verify_signature(a: &Attachment, context: &mut Context) -> Vec<u8> { - match melib::signatures::verify_signature(a) { - Ok((bytes, sig)) => { - let bytes_file = create_temp_file(&bytes, None, None, true); - let signature_file = create_temp_file(sig, None, None, true); - match Command::new( +pub fn verify_signature(a: &Attachment, context: &mut Context) -> Result<Vec<u8>> { + let (bytes, sig) = + melib_pgp::verify_signature(a).chain_err_summary(|| "Could not verify signature.")?; + let bytes_file = create_temp_file(&bytes, None, None, true); + let signature_file = create_temp_file(sig, None, None, true); + let binary = context + .settings + .pgp + .gpg_binary + .as_ref() + .map(String::as_str) + .unwrap_or("gpg2"); + Ok(Command::new(binary) + .args(&[ + "--output", + "-", + "--verify", + signature_file.path.to_str().unwrap(), + bytes_file.path.to_str().unwrap(), + ]) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .and_then(|gpg| gpg.wait_with_output()) + .map(|gpg| gpg.stderr) + .chain_err_summary(|| { + format!( + "Failed to launch {} to verify PGP signature", context .settings .pgp @@ -37,50 +60,7 @@ pub fn verify_signature(a: &Attachment, context: &mut Context) -> Vec<u8> { .map(String::as_str) .unwrap_or("gpg2"), ) - .args(&[ - "--output", - "-", - "--verify", - signature_file.path.to_str().unwrap(), - bytes_file.path.to_str().unwrap(), - ]) - .stdin(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - { - Ok(gpg) => { - return gpg.wait_with_output().unwrap().stderr; - } - Err(err) => { - context.replies.push_back(UIEvent::Notification( - Some(format!( - "Failed to launch {} to verify PGP signature", - context - .settings - .pgp - .gpg_binary - .as_ref() - .map(String::as_str) - .unwrap_or("gpg2"), - )), - format!( - "{}\nsee meli.conf(5) for configuration setting pgp.gpg_binary", - &err - ), - Some(NotificationType::Error(melib::error::ErrorKind::External)), - )); - } - } - } - Err(err) => { - context.replies.push_back(UIEvent::Notification( - Some("Could not verify signature.".to_string()), - err.to_string(), - Some(NotificationType::Error(err.kind)), - )); - } - } - Vec::new() + })?) } /// Returns multipart/signed @@ -89,7 +69,8 @@ pub fn sign( gpg_binary: Option<&str>, pgp_key: Option<&str>, ) -> Result<AttachmentBuilder> { - let mut command = Command::new(gpg_binary.unwrap_or("gpg2")); + let binary = gpg_binary.unwrap_or("gpg2"); + let mut command = Command::new(binary); command.args(&[ "--digest-algo", "sha512", @@ -102,23 +83,27 @@ pub fn sign( command.args(&["--local-user", key]); } let a: Attachment = a.into(); - let mut gpg = command + + let sig_attachment = command .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::null()) - .spawn()?; - - let sig_attachment = { - gpg.stdin - .as_mut() - .unwrap() - .write_all(&melib::signatures::convert_attachment_to_rfc_spec( - a.into_raw().as_bytes(), + .spawn() + .and_then(|mut gpg| { + gpg.stdin + .as_mut() + .expect("Could not get gpg stdin") + .write_all(&melib_pgp::convert_attachment_to_rfc_spec( + a.into_raw().as_bytes(), + ))?; + let gpg = gpg.wait_with_output()?; + Ok(Attachment::new( + ContentType::PGPSignature, + Default::default(), + gpg.stdout, )) - .unwrap(); - let gpg = gpg.wait_with_output().unwrap(); - Attachment::new(ContentType::PGPSignature, Default::default(), gpg.stdout) - }; + }) + .chain_err_summary(|| format!("Failed to launch {} to verify PGP signature", binary))?; let a: AttachmentBuilder = a.into(); let parts = vec![a, sig_attachment.into()]; @@ -134,3 +119,61 @@ pub fn sign( ) .into()) } + +pub async fn decrypt( + raw: Vec<u8>, + gpg_binary: Option<String>, + decrypt_key: Option<String>, +) -> Result<(melib_pgp::DecryptionMetadata, Vec<u8>)> { + let bin = gpg_binary.as_ref().map(|s| s.as_str()).unwrap_or("gpg2"); + let mut command = Command::new(bin); + command.args(&["--digest-algo", "sha512", "--output", "-"]); + if let Some(ref key) = decrypt_key { + command.args(&["--local-user", key]); + } + + let stdout = command + .args(&["--decrypt"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .and_then(|mut gpg| { + gpg.stdin + .as_mut() + .expect("Could not get gpg stdin") + .write_all(&raw)?; + let gpg = gpg.wait_with_output()?; + Ok(gpg.stdout) + }) + .chain_err_summary(|| format!("Failed to launch {} to verify PGP signature", bin))?; + Ok((melib_pgp::DecryptionMetadata::default(), stdout)) +} + +pub async fn verify(a: Attachment, gpg_binary: Option<String>) -> Result<Vec<u8>> { + let (bytes, sig) = + melib_pgp::verify_signature(&a).chain_err_summary(|| "Could not verify signature.")?; + let bytes_file = create_temp_file(&bytes, None, None, true); + let signature_file = create_temp_file(sig, None, None, true); + Ok( + Command::new(gpg_binary.as_ref().map(String::as_str).unwrap_or("gpg2")) + .args(&[ + "--output", + "-", + "--verify", + signature_file.path.to_str().unwrap(), + bytes_file.path.to_str().unwrap(), + ]) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .and_then(|gpg| gpg.wait_with_output()) + .map(|gpg| gpg.stderr) + .chain_err_summary(|| { + format!( + "Failed to launch {} to verify PGP signature", + gpg_binary.as_ref().map(String::as_str).unwrap_or("gpg2"), + ) + })?, + ) +} 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.pus |