melib/email/attachments: Add DecodeOptions struct for decoding

pull/150/head
Manos Pitsidianakis 2022-09-11 01:11:33 +03:00
parent 3688369278
commit 9cbbf71e0f
7 changed files with 148 additions and 129 deletions

View File

@ -554,7 +554,7 @@ impl Attachment {
fn get_text_recursive(&self, text: &mut Vec<u8>) {
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<u8> {
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<u8> {
self.decode_rec_helper(&mut options)
}
fn decode_helper<'a, 'b>(&'a self, options: &mut DecodeOptions<'b>) -> Vec<u8> {
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<u8> {
self.decode_helper(&mut options)
}
}
pub fn interpret_format_flowed(_t: &str) -> String {
unimplemented!()
}
type Filter<'a> = Box<dyn FnMut(&Attachment, &mut Vec<u8>) + 'a>;
pub type Filter<'a> = Box<dyn FnMut(&Attachment, &mut Vec<u8>) + 'a>;
fn decode_rec_helper<'a, 'b>(a: &'a Attachment, filter: &mut Option<Filter<'b>>) -> Vec<u8> {
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<Filter<'b>>) -> Vec<u8> {
decode_rec_helper(a, &mut filter)
}
fn decode_helper<'a, 'b>(a: &'a Attachment, filter: &mut Option<Filter<'b>>) -> Vec<u8> {
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<Filter<'b>>) -> Vec<u8> {
decode_helper(a, &mut filter)
#[derive(Default)]
pub struct DecodeOptions<'att> {
pub filter: Option<Filter<'att>>,
pub force_charset: Option<Charset>,
}

View File

@ -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!(

View File

@ -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())),

View File

@ -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<u8>| {
let body_text = String::from_utf8_lossy(&body.decode_rec(DecodeOptions {
filter: Some(Box::new(|a: &Attachment, v: &mut Vec<u8>| {
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,

View File

@ -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<u8> = decode_rec(body, None);
let bytes: Vec<u8> = body.decode_rec(Default::default());
let settings = &context.settings;
let mut display_text = if let Some(filter_invocation) = settings.pager.html_filter.as_ref()

View File

@ -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")

View File

@ -34,7 +34,7 @@ use termion::color::{AnsiValue, Rgb as TermionRgb};
///
/// # Examples
///
/// ```
/// ```no_run
/// use meli::Color;
///
/// // The default color.