From 9cbbf71e0f8f9115e9e043982f20045cfc550eb7 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 11 Sep 2022 01:11:33 +0300 Subject: [PATCH] melib/email/attachments: Add DecodeOptions struct for decoding --- melib/src/email/attachments.rs | 228 ++++++++++++++------------- melib/src/email/compose.rs | 6 +- src/components/mail/view.rs | 10 +- src/components/mail/view/envelope.rs | 13 +- src/components/mail/view/html.rs | 2 +- src/mailcap.rs | 16 +- src/terminal/color.rs | 2 +- 7 files changed, 148 insertions(+), 129 deletions(-) diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs index 9e9875ca..b1b50f41 100644 --- a/melib/src/email/attachments.rs +++ b/melib/src/email/attachments.rs @@ -554,7 +554,7 @@ impl Attachment { fn get_text_recursive(&self, text: &mut Vec) { match self.content_type { ContentType::Text { .. } | ContentType::PGPSignature | ContentType::CMSSignature => { - text.extend(decode(self, None)); + text.extend(self.decode(Default::default())); } ContentType::Multipart { ref kind, @@ -798,119 +798,129 @@ impl Attachment { }) .map(|n| n.replace(|c| std::path::is_separator(c) || c.is_ascii_control(), "_")) } + + fn decode_rec_helper<'a, 'b>(&'a self, options: &mut DecodeOptions<'b>) -> Vec { + match self.content_type { + ContentType::Other { .. } => Vec::new(), + ContentType::Text { .. } => self.decode_helper(options), + ContentType::OctetStream { ref name } => name + .clone() + .unwrap_or_else(|| self.mime_type()) + .into_bytes(), + ContentType::CMSSignature | ContentType::PGPSignature => Vec::new(), + ContentType::MessageRfc822 => { + if self.content_disposition.kind.is_inline() { + let b = AttachmentBuilder::new(self.body()).build(); + let ret = b.decode_rec_helper(options); + ret + } else { + b"message/rfc822 attachment".to_vec() + } + } + ContentType::Multipart { + ref kind, + ref parts, + .. + } => match kind { + MultipartType::Alternative => { + for a in parts { + if let ContentType::Text { + kind: Text::Plain, .. + } = a.content_type + { + return a.decode_helper(options); + } + } + self.decode_helper(options) + } + MultipartType::Signed => { + let mut vec = Vec::new(); + for a in parts { + vec.extend(a.decode_rec_helper(options)); + } + vec.extend(self.decode_helper(options)); + vec + } + MultipartType::Encrypted => { + let mut vec = Vec::new(); + for a in parts { + if a.content_type == "application/octet-stream" { + vec.extend(a.decode_rec_helper(options)); + } + } + vec.extend(self.decode_helper(options)); + vec + } + _ => { + let mut vec = Vec::new(); + for a in parts { + if a.content_disposition.kind.is_inline() { + vec.extend(a.decode_rec_helper(options)); + } + } + vec + } + }, + } + } + + pub fn decode_rec<'a, 'b>(&'a self, mut options: DecodeOptions<'b>) -> Vec { + self.decode_rec_helper(&mut options) + } + + fn decode_helper<'a, 'b>(&'a self, options: &mut DecodeOptions<'b>) -> Vec { + let charset = options + .force_charset + .unwrap_or_else(|| match self.content_type { + ContentType::Text { charset, .. } => charset, + _ => Default::default(), + }); + + let bytes = match self.content_transfer_encoding { + ContentTransferEncoding::Base64 => match BASE64_MIME.decode(self.body()) { + Ok(v) => v, + _ => self.body().to_vec(), + }, + ContentTransferEncoding::QuotedPrintable => { + parser::encodings::quoted_printable_bytes(self.body()) + .unwrap() + .1 + } + ContentTransferEncoding::_7Bit + | ContentTransferEncoding::_8Bit + | ContentTransferEncoding::Other { .. } => self.body().to_vec(), + }; + + let mut ret = if self.content_type.is_text() { + if let Ok(v) = parser::encodings::decode_charset(&bytes, charset) { + v.into_bytes() + } else { + self.body().to_vec() + } + } else { + bytes.to_vec() + }; + + if let Some(filter) = options.filter.as_mut() { + filter(self, &mut ret); + } + + ret + } + + pub fn decode<'a, 'b>(&'a self, mut options: DecodeOptions<'b>) -> Vec { + self.decode_helper(&mut options) + } } pub fn interpret_format_flowed(_t: &str) -> String { unimplemented!() } -type Filter<'a> = Box) + 'a>; +pub type Filter<'a> = Box) + 'a>; -fn decode_rec_helper<'a, 'b>(a: &'a Attachment, filter: &mut Option>) -> Vec { - match a.content_type { - ContentType::Other { .. } => Vec::new(), - ContentType::Text { .. } => decode_helper(a, filter), - ContentType::OctetStream { ref name } => { - name.clone().unwrap_or_else(|| a.mime_type()).into_bytes() - } - ContentType::CMSSignature | ContentType::PGPSignature => Vec::new(), - ContentType::MessageRfc822 => { - if a.content_disposition.kind.is_inline() { - let b = AttachmentBuilder::new(a.body()).build(); - let ret = decode_rec_helper(&b, filter); - ret - } else { - b"message/rfc822 attachment".to_vec() - } - } - ContentType::Multipart { - ref kind, - ref parts, - .. - } => match kind { - MultipartType::Alternative => { - for a in parts { - if let ContentType::Text { - kind: Text::Plain, .. - } = a.content_type - { - return decode_helper(a, filter); - } - } - decode_helper(a, filter) - } - MultipartType::Signed => { - let mut vec = Vec::new(); - for a in parts { - vec.extend(decode_rec_helper(a, filter)); - } - vec.extend(decode_helper(a, filter)); - vec - } - MultipartType::Encrypted => { - let mut vec = Vec::new(); - for a in parts { - if a.content_type == "application/octet-stream" { - vec.extend(decode_rec_helper(a, filter)); - } - } - vec.extend(decode_helper(a, filter)); - vec - } - _ => { - let mut vec = Vec::new(); - for a in parts { - if a.content_disposition.kind.is_inline() { - vec.extend(decode_rec_helper(a, filter)); - } - } - vec - } - }, - } -} - -pub fn decode_rec<'a, 'b>(a: &'a Attachment, mut filter: Option>) -> Vec { - decode_rec_helper(a, &mut filter) -} - -fn decode_helper<'a, 'b>(a: &'a Attachment, filter: &mut Option>) -> Vec { - let charset = match a.content_type { - ContentType::Text { charset: c, .. } => c, - _ => Default::default(), - }; - - let bytes = match a.content_transfer_encoding { - ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.body()) { - Ok(v) => v, - _ => a.body().to_vec(), - }, - ContentTransferEncoding::QuotedPrintable => { - parser::encodings::quoted_printable_bytes(a.body()) - .unwrap() - .1 - } - ContentTransferEncoding::_7Bit - | ContentTransferEncoding::_8Bit - | ContentTransferEncoding::Other { .. } => a.body().to_vec(), - }; - - let mut ret = if a.content_type.is_text() { - if let Ok(v) = parser::encodings::decode_charset(&bytes, charset) { - v.into_bytes() - } else { - a.body().to_vec() - } - } else { - bytes.to_vec() - }; - if let Some(filter) = filter { - filter(a, &mut ret); - } - - ret -} - -pub fn decode<'a, 'b>(a: &'a Attachment, mut filter: Option>) -> Vec { - decode_helper(a, &mut filter) +#[derive(Default)] +pub struct DecodeOptions<'att> { + pub filter: Option>, + pub force_charset: Option, } diff --git a/melib/src/email/compose.rs b/melib/src/email/compose.rs index 1e23d1c2..0c449780 100644 --- a/melib/src/email/compose.rs +++ b/melib/src/email/compose.rs @@ -24,7 +24,7 @@ use super::*; use crate::email::attachment_types::{ Charset, ContentTransferEncoding, ContentType, MultipartType, }; -use crate::email::attachments::{decode, decode_rec, AttachmentBuilder}; +use crate::email::attachments::AttachmentBuilder; use crate::shellexpand::ShellExpandTrait; use data_encoding::BASE64_MIME; use std::ffi::OsStr; @@ -92,7 +92,7 @@ impl FromStr for Draft { } let body = Envelope::new(0).body_bytes(s.as_bytes()); - ret.body = String::from_utf8(decode(&body, None))?; + ret.body = String::from_utf8(body.decode(Default::default()))?; Ok(ret) } @@ -207,7 +207,7 @@ impl Draft { ); let body = envelope.body_bytes(bytes); ret.body = { - let reply_body_bytes = decode_rec(&body, None); + let reply_body_bytes = body.decode_rec(Default::default()); let reply_body = String::from_utf8_lossy(&reply_body_bytes); let lines: Vec<&str> = reply_body.lines().collect(); let mut ret = format!( diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index 47641095..f4677629 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -733,7 +733,7 @@ impl MailView { inner: Box::new(a.clone()), }); } else if a.content_type().is_text_html() { - let bytes = decode(a, None); + let bytes = a.decode(Default::default()); let filter_invocation = mailbox_settings!(context[coordinates.0][&coordinates.1].pager.html_filter) .as_ref() @@ -788,7 +788,7 @@ impl MailView { } } } else if a.is_text() { - let bytes = decode(a, None); + let bytes = a.decode(Default::default()); acc.push(AttachmentDisplay::InlineText { inner: Box::new(a.clone()), comment: None, @@ -810,7 +810,7 @@ impl MailView { if let Some(text_attachment_pos) = parts.iter().position(|a| a.content_type == "text/plain") { - let bytes = decode(&parts[text_attachment_pos], None); + let bytes = &parts[text_attachment_pos].decode(Default::default()); if bytes.trim().is_empty() && mailbox_settings!( context[coordinates.0][&coordinates.1] @@ -2211,7 +2211,7 @@ impl Component for MailView { let filename = attachment.filename(); if let Ok(command) = query_default_app(&attachment_type) { let p = create_temp_file( - &decode(attachment, None), + &attachment.decode(Default::default()), filename.as_deref(), None, true, @@ -2466,7 +2466,7 @@ impl Component for MailView { path.push(u.as_hyphenated().to_string()); } } - match save_attachment(&path, &decode(u, None)) { + match save_attachment(&path, &u.decode(Default::default())) { Err(err) => { context.replies.push_back(UIEvent::Notification( Some(format!("Failed to create file at {}", path.display())), diff --git a/src/components/mail/view/envelope.rs b/src/components/mail/view/envelope.rs index 2861c337..2f310558 100644 --- a/src/components/mail/view/envelope.rs +++ b/src/components/mail/view/envelope.rs @@ -83,9 +83,8 @@ impl EnvelopeView { /// Returns the string to be displayed in the Viewer fn attachment_to_text(&self, body: &Attachment, context: &mut Context) -> String { let finder = LinkFinder::new(); - let body_text = String::from_utf8_lossy(&decode_rec( - body, - Some(Box::new(|a: &Attachment, v: &mut Vec| { + let body_text = String::from_utf8_lossy(&body.decode_rec(DecodeOptions { + filter: Some(Box::new(|a: &Attachment, v: &mut Vec| { if a.content_type().is_text_html() { let settings = &context.settings; if let Some(filter_invocation) = settings.pager.html_filter.as_ref() { @@ -123,7 +122,8 @@ impl EnvelopeView { } } })), - )) + ..Default::default() + })) .into_owned(); match self.mode { ViewMode::Normal | ViewMode::Subview => { @@ -370,7 +370,8 @@ impl Component for EnvelopeView { self.mode = ViewMode::Subview; let colors = crate::conf::value(context, "mail.view.body"); self.subview = Some(Box::new(Pager::from_string( - String::from_utf8_lossy(&decode_rec(u, None)).to_string(), + String::from_utf8_lossy(&u.decode_rec(Default::default())) + .to_string(), Some(context), None, None, @@ -397,7 +398,7 @@ impl Component for EnvelopeView { let filename = u.filename(); if let Ok(command) = query_default_app(&attachment_type) { let p = create_temp_file( - &decode(u, None), + &u.decode(Default::default()), filename.as_deref(), None, true, diff --git a/src/components/mail/view/html.rs b/src/components/mail/view/html.rs index 873d0cae..fe72a040 100644 --- a/src/components/mail/view/html.rs +++ b/src/components/mail/view/html.rs @@ -33,7 +33,7 @@ pub struct HtmlView { impl HtmlView { pub fn new(body: &Attachment, context: &mut Context) -> Self { let id = ComponentId::new_v4(); - let bytes: Vec = decode_rec(body, None); + let bytes: Vec = body.decode_rec(Default::default()); let settings = &context.settings; let mut display_text = if let Some(filter_invocation) = settings.pager.html_filter.as_ref() diff --git a/src/mailcap.rs b/src/mailcap.rs index 7ff0e824..0cc566a3 100644 --- a/src/mailcap.rs +++ b/src/mailcap.rs @@ -23,7 +23,6 @@ */ use crate::state::Context; use crate::types::{create_temp_file, ForkType, UIEvent}; -use melib::attachments::decode; use melib::text_processing::GlobMatch; use melib::{email::Attachment, MeliError, Result}; use std::collections::HashMap; @@ -159,7 +158,8 @@ impl MailcapEntry { .map(|arg| match *arg { "%s" => { needs_stdin = false; - let _f = create_temp_file(&decode(a, None), None, None, true); + let _f = + create_temp_file(&a.decode(Default::default()), None, None, true); let p = _f.path().display().to_string(); f = Some(_f); p @@ -191,7 +191,11 @@ impl MailcapEntry { .stdout(Stdio::piped()) .spawn()?; - child.stdin.as_mut().unwrap().write_all(&decode(a, None))?; + child + .stdin + .as_mut() + .unwrap() + .write_all(&a.decode(Default::default()))?; child.wait_with_output()?.stdout } else { let child = Command::new("sh") @@ -221,7 +225,11 @@ impl MailcapEntry { .stdout(Stdio::inherit()) .spawn()?; - child.stdin.as_mut().unwrap().write_all(&decode(a, None))?; + child + .stdin + .as_mut() + .unwrap() + .write_all(&a.decode(Default::default()))?; debug!(child.wait_with_output()?.stdout); } else { let child = Command::new("sh") diff --git a/src/terminal/color.rs b/src/terminal/color.rs index 6c915e4f..37cdf67d 100644 --- a/src/terminal/color.rs +++ b/src/terminal/color.rs @@ -34,7 +34,7 @@ use termion::color::{AnsiValue, Rgb as TermionRgb}; /// /// # Examples /// -/// ``` +/// ```no_run /// use meli::Color; /// /// // The default color.