Add GPG signing and sig verifying
parent
963fdd1575
commit
e35a93336a
2
meli.1
2
meli.1
|
@ -180,6 +180,8 @@ in composer, add
|
|||
as an attachment
|
||||
.It Ic remove-attachment Ar INDEX
|
||||
remove attachment with given index
|
||||
.It Ic toggle sign
|
||||
toggle between signing and not signing this message. If the gpg invocation fails then the mail won't be sent.
|
||||
.El
|
||||
.Pp
|
||||
generic commands:
|
||||
|
|
|
@ -74,6 +74,27 @@ impl<'a> From<&'a [u8]> for Charset {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for Charset {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
match self {
|
||||
Charset::Ascii => write!(f, "us-ascii"),
|
||||
Charset::UTF8 => write!(f, "utf-8"),
|
||||
Charset::UTF16 => write!(f, "utf-16"),
|
||||
Charset::ISO8859_1 => write!(f, "iso-8859-1"),
|
||||
Charset::ISO8859_2 => write!(f, "iso-8859-2"),
|
||||
Charset::ISO8859_7 => write!(f, "iso-8859-7"),
|
||||
Charset::ISO8859_15 => write!(f, "iso-8859-15"),
|
||||
Charset::Windows1251 => write!(f, "windows-1251"),
|
||||
Charset::Windows1252 => write!(f, "windows-1252"),
|
||||
Charset::Windows1253 => write!(f, "windows-1253"),
|
||||
Charset::GBK => write!(f, "GBK"),
|
||||
Charset::GB2312 => write!(f, "gb2312"),
|
||||
Charset::BIG5 => write!(f, "BIG5"),
|
||||
Charset::ISO2022JP => write!(f, "ISO-2022-JP"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MultipartType {
|
||||
Mixed,
|
||||
|
@ -264,7 +285,7 @@ pub enum ContentTransferEncoding {
|
|||
|
||||
impl Default for ContentTransferEncoding {
|
||||
fn default() -> Self {
|
||||
ContentTransferEncoding::_7Bit
|
||||
ContentTransferEncoding::_8Bit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -273,6 +273,23 @@ impl From<Attachment> for AttachmentBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<AttachmentBuilder> for Attachment {
|
||||
fn from(val: AttachmentBuilder) -> Self {
|
||||
let AttachmentBuilder {
|
||||
content_type,
|
||||
content_transfer_encoding,
|
||||
raw,
|
||||
body,
|
||||
} = val;
|
||||
Attachment {
|
||||
content_type,
|
||||
content_transfer_encoding,
|
||||
raw,
|
||||
body,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Immutable attachment type.
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Attachment {
|
||||
|
@ -546,6 +563,73 @@ impl Attachment {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_raw(&self) -> String {
|
||||
let mut ret = String::with_capacity(2 * self.raw.len());
|
||||
fn into_raw_helper(a: &Attachment, ret: &mut String) {
|
||||
ret.extend(
|
||||
format!(
|
||||
"Content-Transfer-Encoding: {}\n",
|
||||
a.content_transfer_encoding
|
||||
)
|
||||
.chars(),
|
||||
);
|
||||
match &a.content_type {
|
||||
ContentType::Text { kind: _, charset } => {
|
||||
ret.extend(
|
||||
format!("Content-Type: {}; charset={}\n\n", a.content_type, charset)
|
||||
.chars(),
|
||||
);
|
||||
ret.extend(String::from_utf8_lossy(a.body()).chars());
|
||||
}
|
||||
ContentType::Multipart {
|
||||
boundary,
|
||||
kind,
|
||||
parts,
|
||||
} => {
|
||||
let boundary = String::from_utf8_lossy(boundary);
|
||||
ret.extend(format!("Content-Type: {}; boundary={}", kind, boundary).chars());
|
||||
if *kind == MultipartType::Signed {
|
||||
ret.extend(
|
||||
"; micalg=pgp-sha512; protocol=\"application/pgp-signature\"".chars(),
|
||||
);
|
||||
}
|
||||
ret.push('\n');
|
||||
|
||||
let boundary_start = format!("\n--{}\n", boundary);
|
||||
for p in parts {
|
||||
ret.extend(boundary_start.chars());
|
||||
into_raw_helper(p, ret);
|
||||
}
|
||||
ret.extend(format!("--{}--\n\n", boundary).chars());
|
||||
}
|
||||
ContentType::MessageRfc822 => {
|
||||
ret.extend(format!("Content-Type: {}\n\n", a.content_type).chars());
|
||||
ret.extend(String::from_utf8_lossy(a.body()).chars());
|
||||
}
|
||||
ContentType::PGPSignature => {
|
||||
ret.extend(format!("Content-Type: {}\n\n", a.content_type).chars());
|
||||
ret.extend(String::from_utf8_lossy(a.body()).chars());
|
||||
}
|
||||
ContentType::OctetStream { ref name } => {
|
||||
if let Some(name) = name {
|
||||
ret.extend(
|
||||
format!("Content-Type: {}; name={}\n\n", a.content_type, name).chars(),
|
||||
);
|
||||
} else {
|
||||
ret.extend(format!("Content-Type: {}\n\n", a.content_type).chars());
|
||||
}
|
||||
ret.push_str(&BASE64_MIME.encode(a.body()).trim());
|
||||
}
|
||||
_ => {
|
||||
ret.extend(format!("Content-Type: {}\n\n", a.content_type).chars());
|
||||
ret.extend(String::from_utf8_lossy(a.body()).chars());
|
||||
}
|
||||
}
|
||||
}
|
||||
into_raw_helper(self, &mut ret);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interpret_format_flowed(_t: &str) -> String {
|
||||
|
|
|
@ -18,11 +18,11 @@ use fnv::FnvHashMap;
|
|||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Draft {
|
||||
headers: FnvHashMap<String, String>,
|
||||
header_order: Vec<String>,
|
||||
body: String,
|
||||
pub headers: FnvHashMap<String, String>,
|
||||
pub header_order: Vec<String>,
|
||||
pub body: String,
|
||||
|
||||
attachments: Vec<AttachmentBuilder>,
|
||||
pub attachments: Vec<AttachmentBuilder>,
|
||||
}
|
||||
|
||||
impl Default for Draft {
|
||||
|
@ -259,7 +259,19 @@ impl Draft {
|
|||
}
|
||||
ret.push_str("MIME-Version: 1.0\n");
|
||||
|
||||
if !self.attachments.is_empty() {
|
||||
if self.attachments.is_empty() {
|
||||
let content_type: ContentType = Default::default();
|
||||
let content_transfer_encoding: ContentTransferEncoding = ContentTransferEncoding::_8Bit;
|
||||
ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", content_type).chars());
|
||||
ret.extend(
|
||||
format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(),
|
||||
);
|
||||
ret.push('\n');
|
||||
ret.push_str(&self.body);
|
||||
} else if self.attachments.len() == 1 && self.body.is_empty() {
|
||||
let attachment: Attachment = self.attachments.remove(0).into();
|
||||
ret.extend(attachment.into_raw().chars());
|
||||
} else {
|
||||
let mut parts = Vec::with_capacity(self.attachments.len() + 1);
|
||||
let attachments = std::mem::replace(&mut self.attachments, Vec::new());
|
||||
let mut body_attachment = AttachmentBuilder::default();
|
||||
|
@ -267,24 +279,6 @@ impl Draft {
|
|||
parts.push(body_attachment);
|
||||
parts.extend(attachments.into_iter());
|
||||
build_multipart(&mut ret, MultipartType::Mixed, parts);
|
||||
} else {
|
||||
if self.body.is_ascii() {
|
||||
ret.push('\n');
|
||||
ret.push_str(&self.body);
|
||||
} else {
|
||||
let content_type: ContentType = Default::default();
|
||||
let content_transfer_encoding: ContentTransferEncoding =
|
||||
ContentTransferEncoding::Base64;
|
||||
|
||||
ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", content_type).chars());
|
||||
ret.extend(
|
||||
format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(),
|
||||
);
|
||||
ret.push('\n');
|
||||
|
||||
ret.push_str(&BASE64_MIME.encode(&self.body.as_bytes()).trim());
|
||||
ret.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
|
|
|
@ -33,6 +33,8 @@ pub use crate::view::*;
|
|||
mod compose;
|
||||
pub use self::compose::*;
|
||||
|
||||
pub mod pgp;
|
||||
|
||||
mod accounts;
|
||||
pub use self::accounts::*;
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ pub struct Composer {
|
|||
form: FormWidget,
|
||||
|
||||
mode: ViewMode,
|
||||
sign_mail: ToggleFlag,
|
||||
dirty: bool,
|
||||
initialized: bool,
|
||||
id: ComponentId,
|
||||
|
@ -62,6 +63,7 @@ impl Default for Composer {
|
|||
form: FormWidget::default(),
|
||||
|
||||
mode: ViewMode::Edit,
|
||||
sign_mail: ToggleFlag::Unset,
|
||||
dirty: true,
|
||||
initialized: false,
|
||||
id: ComponentId::new_v4(),
|
||||
|
@ -217,11 +219,20 @@ impl Composer {
|
|||
}
|
||||
}
|
||||
|
||||
fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
|
||||
fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, context: &Context) {
|
||||
let attachments_no = self.draft.attachments().len();
|
||||
if attachments_no == 0 {
|
||||
if self.sign_mail.is_true() {
|
||||
write_string_to_grid(
|
||||
"no attachments",
|
||||
&format!(
|
||||
"☑ sign with {}",
|
||||
context
|
||||
.settings
|
||||
.pgp
|
||||
.key
|
||||
.as_ref()
|
||||
.map(String::as_str)
|
||||
.unwrap_or("default key")
|
||||
),
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
|
@ -231,7 +242,7 @@ impl Composer {
|
|||
);
|
||||
} else {
|
||||
write_string_to_grid(
|
||||
&format!("{} attachments ", attachments_no),
|
||||
"☐ don't sign",
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
|
@ -239,6 +250,27 @@ impl Composer {
|
|||
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
|
||||
false,
|
||||
);
|
||||
}
|
||||
if attachments_no == 0 {
|
||||
write_string_to_grid(
|
||||
"no attachments",
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
write_string_to_grid(
|
||||
&format!("{} attachments ", attachments_no),
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
||||
false,
|
||||
);
|
||||
for (i, a) in self.draft.attachments().iter().enumerate() {
|
||||
if let Some(name) = a.content_type().name() {
|
||||
write_string_to_grid(
|
||||
|
@ -253,7 +285,7 @@ impl Composer {
|
|||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
(pos_inc(upper_left!(area), (0, 2 + i)), bottom_right!(area)),
|
||||
(pos_inc(upper_left!(area), (0, 3 + i)), bottom_right!(area)),
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
|
@ -263,7 +295,7 @@ impl Composer {
|
|||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
(pos_inc(upper_left!(area), (0, 2 + i)), bottom_right!(area)),
|
||||
(pos_inc(upper_left!(area), (0, 3 + i)), bottom_right!(area)),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
@ -291,6 +323,9 @@ impl Component for Composer {
|
|||
};
|
||||
|
||||
if !self.initialized {
|
||||
if self.sign_mail.is_unset() {
|
||||
self.sign_mail = ToggleFlag::InternalVal(context.settings.pgp.auto_sign);
|
||||
}
|
||||
if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty()
|
||||
{
|
||||
self.draft.headers_mut().insert(
|
||||
|
@ -632,7 +667,12 @@ impl Component for Composer {
|
|||
}
|
||||
UIEvent::Input(Key::Char('s')) => {
|
||||
self.update_draft();
|
||||
if send_draft(context, self.account_cursor, self.draft.clone()) {
|
||||
if send_draft(
|
||||
self.sign_mail,
|
||||
context,
|
||||
self.account_cursor,
|
||||
self.draft.clone(),
|
||||
) {
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
||||
|
@ -743,6 +783,12 @@ impl Component for Composer {
|
|||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
Action::Compose(ComposeAction::ToggleSign) => {
|
||||
let is_true = self.sign_mail.is_true();
|
||||
self.sign_mail = ToggleFlag::from(!is_true);
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -815,7 +861,12 @@ impl Component for Composer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn send_draft(context: &mut Context, account_cursor: usize, draft: Draft) -> bool {
|
||||
pub fn send_draft(
|
||||
sign_mail: ToggleFlag,
|
||||
context: &mut Context,
|
||||
account_cursor: usize,
|
||||
mut draft: Draft,
|
||||
) -> bool {
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
let mut failure = true;
|
||||
|
@ -830,6 +881,55 @@ pub fn send_draft(context: &mut Context, account_cursor: usize, draft: Draft) ->
|
|||
.expect("Failed to start mailer command");
|
||||
{
|
||||
let stdin = msmtp.stdin.as_mut().expect("failed to open stdin");
|
||||
if sign_mail.is_true() {
|
||||
let mut body: AttachmentBuilder = Attachment::new(
|
||||
Default::default(),
|
||||
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();
|
||||
}
|
||||
let output = crate::components::mail::pgp::sign(
|
||||
body.into(),
|
||||
context.settings.pgp.gpg_binary.as_ref().map(String::as_str),
|
||||
context.settings.pgp.key.as_ref().map(String::as_str),
|
||||
);
|
||||
if let Err(e) = &output {
|
||||
debug!("{:?} could not sign draft msg", e);
|
||||
log(
|
||||
format!(
|
||||
"Could not sign draft in account `{}`: {}.",
|
||||
context.accounts[account_cursor].name(),
|
||||
e.to_string()
|
||||
),
|
||||
ERROR,
|
||||
);
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
Some(format!(
|
||||
"Could not sign draft in account `{}`.",
|
||||
context.accounts[account_cursor].name()
|
||||
)),
|
||||
e.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
return false;
|
||||
}
|
||||
draft.attachments.push(output.unwrap());
|
||||
}
|
||||
let draft = draft.finalise().unwrap();
|
||||
stdin
|
||||
.write_all(draft.as_bytes())
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* meli - ui crate.
|
||||
*
|
||||
* Copyright 2019 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
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);
|
||||
if let Ok(gpg) = Command::new(
|
||||
context
|
||||
.settings
|
||||
.pgp
|
||||
.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()
|
||||
{
|
||||
return gpg.wait_with_output().unwrap().stderr;
|
||||
} else {
|
||||
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"),
|
||||
)),
|
||||
"see meli.conf(5) for configuration setting pgp.gpg_binary".to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
Some(e.to_string()),
|
||||
String::new(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
}
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Returns multipart/signed
|
||||
pub fn sign(
|
||||
a: AttachmentBuilder,
|
||||
gpg_binary: Option<&str>,
|
||||
pgp_key: Option<&str>,
|
||||
) -> Result<AttachmentBuilder> {
|
||||
let mut command = Command::new(gpg_binary.unwrap_or("gpg2"));
|
||||
command.args(&[
|
||||
"--digest-algo",
|
||||
"sha512",
|
||||
"--output",
|
||||
"-",
|
||||
"--detach-sig",
|
||||
"--armor",
|
||||
]);
|
||||
if let Some(key) = pgp_key {
|
||||
command.args(&["--local-user", key]);
|
||||
}
|
||||
let a: Attachment = a.into();
|
||||
let mut gpg = 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(),
|
||||
))
|
||||
.unwrap();
|
||||
let gpg = gpg.wait_with_output().unwrap();
|
||||
Attachment::new(ContentType::PGPSignature, Default::default(), gpg.stdout)
|
||||
};
|
||||
|
||||
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())
|
||||
}
|
|
@ -196,58 +196,7 @@ impl MailView {
|
|||
} else if a.is_signed() {
|
||||
v.clear();
|
||||
if context.settings.pgp.auto_verify_signatures {
|
||||
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);
|
||||
if let Ok(gpg) = Command::new(
|
||||
context
|
||||
.settings
|
||||
.pgp
|
||||
.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()
|
||||
{
|
||||
v.extend(gpg.wait_with_output().unwrap().stderr);
|
||||
} else {
|
||||
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"),
|
||||
)),
|
||||
"see meli.conf(5) for configuration setting pgp.gpg_binary"
|
||||
.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
Some(e.to_string()),
|
||||
String::new(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
}
|
||||
}
|
||||
v.extend(crate::mail::pgp::verify_signature(a, context).into_iter());
|
||||
}
|
||||
}
|
||||
})),
|
||||
|
@ -1041,6 +990,7 @@ impl Component for MailView {
|
|||
),
|
||||
);
|
||||
if super::compose::send_draft(
|
||||
ToggleFlag::False,
|
||||
/* FIXME: refactor to avoid unsafe.
|
||||
*
|
||||
* actions contains byte slices from the envelope's
|
||||
|
|
|
@ -58,7 +58,7 @@ macro_rules! split_command {
|
|||
}};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Copy, Debug, Clone, PartialEq)]
|
||||
pub enum ToggleFlag {
|
||||
Unset,
|
||||
InternalVal(bool),
|
||||
|
@ -66,6 +66,16 @@ pub enum ToggleFlag {
|
|||
True,
|
||||
}
|
||||
|
||||
impl From<bool> for ToggleFlag {
|
||||
fn from(val: bool) -> Self {
|
||||
if val {
|
||||
ToggleFlag::True
|
||||
} else {
|
||||
ToggleFlag::False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ToggleFlag {
|
||||
fn default() -> Self {
|
||||
ToggleFlag::Unset
|
||||
|
|
|
@ -214,6 +214,17 @@ define_commands!([
|
|||
);
|
||||
)
|
||||
},
|
||||
{ tags: ["toggle sign "],
|
||||
desc: "switch between sign/unsign for this draft",
|
||||
parser:(
|
||||
named!( toggle_sign<Action>,
|
||||
do_parse!(
|
||||
ws!(tag!("toggle sign"))
|
||||
>> (Compose(ToggleSign))
|
||||
)
|
||||
);
|
||||
)
|
||||
},
|
||||
{ tags: ["create-folder "],
|
||||
desc: "create-folder ACCOUNT FOLDER_PATH",
|
||||
parser:(
|
||||
|
@ -350,7 +361,7 @@ named!(
|
|||
|
||||
named!(
|
||||
compose_action<Action>,
|
||||
alt_complete!(add_attachment | remove_attachment)
|
||||
alt_complete!(add_attachment | remove_attachment | toggle_sign)
|
||||
);
|
||||
|
||||
named!(pub parse_command<Action>,
|
||||
|
|
|
@ -71,6 +71,7 @@ pub enum PagerAction {
|
|||
pub enum ComposeAction {
|
||||
AddAttachment(String),
|
||||
RemoveAttachment(usize),
|
||||
ToggleSign,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
Loading…
Reference in New Issue