diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-10-08 16:52:13 +0300 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-10-11 16:53:04 +0300 |
commit | a2f11c341d66715ac5a311b7a68264200e5e2ade (patch) | |
tree | cb385ccec1eb10b6ead5a19a8d64608135c59469 /src/components/mail | |
parent | afee1e2be5fe0232e8c3c66a3a03a21ccc7635d6 (diff) |
compose: add async draft filter stack in sending mail
Add a stack of "filter" closures that edit a draft before sending it.
Add PGP signing filter. An encryption filter will be added in a future
commit.
Diffstat (limited to 'src/components/mail')
-rw-r--r-- | src/components/mail/compose.rs | 160 | ||||
-rw-r--r-- | src/components/mail/pgp.rs | 68 |
2 files changed, 177 insertions, 51 deletions
diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index c877c661..463490be 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -30,6 +30,7 @@ use crate::terminal::embed::EmbedGrid; use indexmap::IndexSet; use nix::sys::wait::WaitStatus; use std::convert::TryInto; +use std::future::Future; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -81,7 +82,6 @@ pub struct Composer { embed_area: Area, embed: Option<EmbedStatus>, sign_mail: ToggleFlag, - encrypt_mail: ToggleFlag, dirty: bool, has_changes: bool, initialized: bool, @@ -104,7 +104,6 @@ impl Default for Composer { mode: ViewMode::Edit, sign_mail: ToggleFlag::Unset, - encrypt_mail: ToggleFlag::Unset, dirty: true, has_changes: false, embed_area: ((0, 0), (0, 0)), @@ -453,33 +452,15 @@ impl Composer { None, ); } - if self.encrypt_mail.is_true() { - write_string_to_grid( - &format!( - "☑ encrypt with {}", - account_settings!(context[self.account_hash].pgp.encrypt_key) - .as_ref() - .map(|s| s.as_str()) - .unwrap_or("default key") - ), - grid, - theme_default.fg, - theme_default.bg, - theme_default.attrs, - (pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)), - None, - ); - } else { - write_string_to_grid( - "☐ don't encrypt", - grid, - theme_default.fg, - theme_default.bg, - theme_default.attrs, - (pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)), - None, - ); - } + write_string_to_grid( + "☐ don't encrypt", + grid, + theme_default.fg, + theme_default.bg, + theme_default.attrs, + (pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)), + None, + ); if attachments_no == 0 { write_string_to_grid( "no attachments", @@ -552,11 +533,6 @@ impl Component for Composer { context[self.account_hash].pgp.auto_sign )); } - if self.encrypt_mail.is_unset() { - self.encrypt_mail = ToggleFlag::InternalVal(*account_settings!( - context[self.account_hash].pgp.auto_encrypt - )); - } if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty() { self.draft.set_header( @@ -752,16 +728,16 @@ impl Component for Composer { { if let Some(true) = result.downcast_ref::<bool>() { self.update_draft(); - match send_draft( + match send_draft_async( self.sign_mail, context, self.account_hash, self.draft.clone(), SpecialUsageMailbox::Sent, Flag::SEEN, - false, ) { - Ok(Some((job_id, handle, chan))) => { + Ok(job) => { + let (chan, handle, job_id) = context.job_executor.spawn_blocking(job); self.mode = ViewMode::WaitingForSendResult( UIDialog::new( "Waiting for confirmation.. The tab will close automatically on successful submission.", @@ -773,22 +749,12 @@ impl Component for Composer { Some(Box::new(move |id: ComponentId, results: &[char]| { Some(UIEvent::FinishedUIDialog( id, - Box::new(results.get(0).map(|c| *c).unwrap_or('c')), + Box::new(results.get(0).cloned().unwrap_or('c')), )) })), context, ), handle, job_id, chan); } - Ok(None) => { - context.replies.push_back(UIEvent::Notification( - Some("Sent.".into()), - String::new(), - Some(NotificationType::Info), - )); - context - .replies - .push_back(UIEvent::Action(Tab(Kill(self.id)))); - } Err(err) => { context.replies.push_back(UIEvent::Notification( None, @@ -1358,9 +1324,6 @@ impl Component for Composer { return true; } Action::Compose(ComposeAction::ToggleEncrypt) => { - let is_true = self.encrypt_mail.is_true(); - self.encrypt_mail = ToggleFlag::from(!is_true); - self.dirty = true; return true; } _ => {} @@ -1601,3 +1564,98 @@ pub fn save_draft( } } } + +pub fn send_draft_async( + sign_mail: ToggleFlag, + context: &mut Context, + account_hash: AccountHash, + mut draft: Draft, + mailbox_type: SpecialUsageMailbox, + flags: Flag, +) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> { + let format_flowed = *account_settings!(context[account_hash].composing.format_flowed); + let event_sender = context.sender.clone(); + let mut filters_stack: Vec< + Box< + dyn FnOnce( + AttachmentBuilder, + ) + -> Pin<Box<dyn Future<Output = Result<AttachmentBuilder>> + Send>> + + Send, + >, + > = vec![]; + if sign_mail.is_true() { + filters_stack.push(Box::new(crate::components::mail::pgp::sign_filter( + account_settings!(context[account_hash].pgp.gpg_binary) + .as_ref() + .map(|s| s.to_string()), + account_settings!(context[account_hash].pgp.sign_key) + .as_ref() + .map(|s| s.to_string()), + )?)); + } + let send_mail = account_settings!(context[account_hash].composing.send_mail).clone(); + let send_cb = context.accounts[&account_hash].send_async(send_mail); + let mut content_type = ContentType::default(); + if format_flowed { + if let ContentType::Text { + ref mut parameters, .. + } = content_type + { + parameters.push((b"format".to_vec(), b"flowed".to_vec())); + } + } + let mut body: AttachmentBuilder = Attachment::new( + content_type, + Default::default(), + std::mem::replace(&mut draft.body, String::new()).into_bytes(), + ) + .into(); + if !draft.attachments.is_empty() { + let mut parts = std::mem::replace(&mut draft.attachments, Vec::new()); + parts.insert(0, body); + let boundary = ContentType::make_boundary(&parts); + body = Attachment::new( + ContentType::Multipart { + boundary: boundary.into_bytes(), + kind: MultipartType::Mixed, + parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(), + }, + Default::default(), + Vec::new(), + ) + .into(); + } + Ok(Box::pin(async move { + for f in filters_stack { + body = f(body).await?; + } + + draft.attachments.insert(0, body); + let message = Arc::new(draft.finalise()?); + let ret = send_cb(message.clone()).await; + let is_ok = ret.is_ok(); + event_sender + .send(ThreadEvent::UIEvent(UIEvent::Callback(CallbackFn( + Box::new(move |context| { + save_draft( + message.as_bytes(), + context, + if is_ok { + mailbox_type + } else { + SpecialUsageMailbox::Drafts + }, + if is_ok { + flags + } else { + Flag::SEEN | Flag::DRAFT + }, + account_hash, + ); + }), + )))) + .unwrap(); + ret + })) +} diff --git a/src/components/mail/pgp.rs b/src/components/mail/pgp.rs index 837c8d20..e0e40895 100644 --- a/src/components/mail/pgp.rs +++ b/src/components/mail/pgp.rs @@ -21,7 +21,9 @@ use super::*; use melib::email::pgp as melib_pgp; +use std::future::Future; use std::io::Write; +use std::pin::Pin; use std::process::{Command, Stdio}; pub fn verify_signature(a: &Attachment, context: &mut Context) -> Result<Vec<u8>> { @@ -177,3 +179,69 @@ pub async fn verify(a: Attachment, gpg_binary: Option<String>) -> Result<Vec<u8> })?, ) } + +pub fn sign_filter( + gpg_binary: Option<String>, + pgp_key: Option<String>, +) -> Result< + impl FnOnce(AttachmentBuilder) -> Pin<Box<dyn Future<Output = Result<AttachmentBuilder>> + Send>> + + Send, +> { + let binary = gpg_binary.unwrap_or("gpg2".to_string()); + let mut command = Command::new(&binary); + command.args(&[ + "--digest-algo", + "sha512", + "--output", + "-", + "--detach-sig", + "--armor", + ]); + if let Some(key) = pgp_key.as_ref() { + command.args(&["--local-user", key]); + } + Ok( + move |a: AttachmentBuilder| -> Pin<Box<dyn Future<Output = Result<AttachmentBuilder>>+Send>> { + Box::pin(async move { + let a: Attachment = a.into(); + + let sig_attachment = command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .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, + )) + }) + .chain_err_summary(|| { + format!("Failed to launch {} to verify PGP signature", binary) + })?; + + let a: AttachmentBuilder = a.into(); + let parts = vec![a, sig_attachment.into()]; + let boundary = ContentType::make_boundary(&parts); + Ok(Attachment::new( + ContentType::Multipart { + boundary: boundary.into_bytes(), + kind: MultipartType::Signed, + parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(), + }, + Default::default(), + Vec::new(), + ) + .into()) + }) + }, + ) +} |