melib/email/attachments: Add DecodeOptions struct for decoding
parent
3688369278
commit
9cbbf71e0f
|
@ -554,7 +554,7 @@ impl Attachment {
|
||||||
fn get_text_recursive(&self, text: &mut Vec<u8>) {
|
fn get_text_recursive(&self, text: &mut Vec<u8>) {
|
||||||
match self.content_type {
|
match self.content_type {
|
||||||
ContentType::Text { .. } | ContentType::PGPSignature | ContentType::CMSSignature => {
|
ContentType::Text { .. } | ContentType::PGPSignature | ContentType::CMSSignature => {
|
||||||
text.extend(decode(self, None));
|
text.extend(self.decode(Default::default()));
|
||||||
}
|
}
|
||||||
ContentType::Multipart {
|
ContentType::Multipart {
|
||||||
ref kind,
|
ref kind,
|
||||||
|
@ -798,119 +798,129 @@ impl Attachment {
|
||||||
})
|
})
|
||||||
.map(|n| n.replace(|c| std::path::is_separator(c) || c.is_ascii_control(), "_"))
|
.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 {
|
pub fn interpret_format_flowed(_t: &str) -> String {
|
||||||
unimplemented!()
|
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> {
|
#[derive(Default)]
|
||||||
match a.content_type {
|
pub struct DecodeOptions<'att> {
|
||||||
ContentType::Other { .. } => Vec::new(),
|
pub filter: Option<Filter<'att>>,
|
||||||
ContentType::Text { .. } => decode_helper(a, filter),
|
pub force_charset: Option<Charset>,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ use super::*;
|
||||||
use crate::email::attachment_types::{
|
use crate::email::attachment_types::{
|
||||||
Charset, ContentTransferEncoding, ContentType, MultipartType,
|
Charset, ContentTransferEncoding, ContentType, MultipartType,
|
||||||
};
|
};
|
||||||
use crate::email::attachments::{decode, decode_rec, AttachmentBuilder};
|
use crate::email::attachments::AttachmentBuilder;
|
||||||
use crate::shellexpand::ShellExpandTrait;
|
use crate::shellexpand::ShellExpandTrait;
|
||||||
use data_encoding::BASE64_MIME;
|
use data_encoding::BASE64_MIME;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
@ -92,7 +92,7 @@ impl FromStr for Draft {
|
||||||
}
|
}
|
||||||
let body = Envelope::new(0).body_bytes(s.as_bytes());
|
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)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ impl Draft {
|
||||||
);
|
);
|
||||||
let body = envelope.body_bytes(bytes);
|
let body = envelope.body_bytes(bytes);
|
||||||
ret.body = {
|
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 reply_body = String::from_utf8_lossy(&reply_body_bytes);
|
||||||
let lines: Vec<&str> = reply_body.lines().collect();
|
let lines: Vec<&str> = reply_body.lines().collect();
|
||||||
let mut ret = format!(
|
let mut ret = format!(
|
||||||
|
|
|
@ -733,7 +733,7 @@ impl MailView {
|
||||||
inner: Box::new(a.clone()),
|
inner: Box::new(a.clone()),
|
||||||
});
|
});
|
||||||
} else if a.content_type().is_text_html() {
|
} else if a.content_type().is_text_html() {
|
||||||
let bytes = decode(a, None);
|
let bytes = a.decode(Default::default());
|
||||||
let filter_invocation =
|
let filter_invocation =
|
||||||
mailbox_settings!(context[coordinates.0][&coordinates.1].pager.html_filter)
|
mailbox_settings!(context[coordinates.0][&coordinates.1].pager.html_filter)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -788,7 +788,7 @@ impl MailView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if a.is_text() {
|
} else if a.is_text() {
|
||||||
let bytes = decode(a, None);
|
let bytes = a.decode(Default::default());
|
||||||
acc.push(AttachmentDisplay::InlineText {
|
acc.push(AttachmentDisplay::InlineText {
|
||||||
inner: Box::new(a.clone()),
|
inner: Box::new(a.clone()),
|
||||||
comment: None,
|
comment: None,
|
||||||
|
@ -810,7 +810,7 @@ impl MailView {
|
||||||
if let Some(text_attachment_pos) =
|
if let Some(text_attachment_pos) =
|
||||||
parts.iter().position(|a| a.content_type == "text/plain")
|
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()
|
if bytes.trim().is_empty()
|
||||||
&& mailbox_settings!(
|
&& mailbox_settings!(
|
||||||
context[coordinates.0][&coordinates.1]
|
context[coordinates.0][&coordinates.1]
|
||||||
|
@ -2211,7 +2211,7 @@ impl Component for MailView {
|
||||||
let filename = attachment.filename();
|
let filename = attachment.filename();
|
||||||
if let Ok(command) = query_default_app(&attachment_type) {
|
if let Ok(command) = query_default_app(&attachment_type) {
|
||||||
let p = create_temp_file(
|
let p = create_temp_file(
|
||||||
&decode(attachment, None),
|
&attachment.decode(Default::default()),
|
||||||
filename.as_deref(),
|
filename.as_deref(),
|
||||||
None,
|
None,
|
||||||
true,
|
true,
|
||||||
|
@ -2466,7 +2466,7 @@ impl Component for MailView {
|
||||||
path.push(u.as_hyphenated().to_string());
|
path.push(u.as_hyphenated().to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match save_attachment(&path, &decode(u, None)) {
|
match save_attachment(&path, &u.decode(Default::default())) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
context.replies.push_back(UIEvent::Notification(
|
context.replies.push_back(UIEvent::Notification(
|
||||||
Some(format!("Failed to create file at {}", path.display())),
|
Some(format!("Failed to create file at {}", path.display())),
|
||||||
|
|
|
@ -83,9 +83,8 @@ impl EnvelopeView {
|
||||||
/// Returns the string to be displayed in the Viewer
|
/// Returns the string to be displayed in the Viewer
|
||||||
fn attachment_to_text(&self, body: &Attachment, context: &mut Context) -> String {
|
fn attachment_to_text(&self, body: &Attachment, context: &mut Context) -> String {
|
||||||
let finder = LinkFinder::new();
|
let finder = LinkFinder::new();
|
||||||
let body_text = String::from_utf8_lossy(&decode_rec(
|
let body_text = String::from_utf8_lossy(&body.decode_rec(DecodeOptions {
|
||||||
body,
|
filter: Some(Box::new(|a: &Attachment, v: &mut Vec<u8>| {
|
||||||
Some(Box::new(|a: &Attachment, v: &mut Vec<u8>| {
|
|
||||||
if a.content_type().is_text_html() {
|
if a.content_type().is_text_html() {
|
||||||
let settings = &context.settings;
|
let settings = &context.settings;
|
||||||
if let Some(filter_invocation) = settings.pager.html_filter.as_ref() {
|
if let Some(filter_invocation) = settings.pager.html_filter.as_ref() {
|
||||||
|
@ -123,7 +122,8 @@ impl EnvelopeView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
))
|
..Default::default()
|
||||||
|
}))
|
||||||
.into_owned();
|
.into_owned();
|
||||||
match self.mode {
|
match self.mode {
|
||||||
ViewMode::Normal | ViewMode::Subview => {
|
ViewMode::Normal | ViewMode::Subview => {
|
||||||
|
@ -370,7 +370,8 @@ impl Component for EnvelopeView {
|
||||||
self.mode = ViewMode::Subview;
|
self.mode = ViewMode::Subview;
|
||||||
let colors = crate::conf::value(context, "mail.view.body");
|
let colors = crate::conf::value(context, "mail.view.body");
|
||||||
self.subview = Some(Box::new(Pager::from_string(
|
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),
|
Some(context),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
@ -397,7 +398,7 @@ impl Component for EnvelopeView {
|
||||||
let filename = u.filename();
|
let filename = u.filename();
|
||||||
if let Ok(command) = query_default_app(&attachment_type) {
|
if let Ok(command) = query_default_app(&attachment_type) {
|
||||||
let p = create_temp_file(
|
let p = create_temp_file(
|
||||||
&decode(u, None),
|
&u.decode(Default::default()),
|
||||||
filename.as_deref(),
|
filename.as_deref(),
|
||||||
None,
|
None,
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub struct HtmlView {
|
||||||
impl HtmlView {
|
impl HtmlView {
|
||||||
pub fn new(body: &Attachment, context: &mut Context) -> Self {
|
pub fn new(body: &Attachment, context: &mut Context) -> Self {
|
||||||
let id = ComponentId::new_v4();
|
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 settings = &context.settings;
|
||||||
let mut display_text = if let Some(filter_invocation) = settings.pager.html_filter.as_ref()
|
let mut display_text = if let Some(filter_invocation) = settings.pager.html_filter.as_ref()
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
*/
|
*/
|
||||||
use crate::state::Context;
|
use crate::state::Context;
|
||||||
use crate::types::{create_temp_file, ForkType, UIEvent};
|
use crate::types::{create_temp_file, ForkType, UIEvent};
|
||||||
use melib::attachments::decode;
|
|
||||||
use melib::text_processing::GlobMatch;
|
use melib::text_processing::GlobMatch;
|
||||||
use melib::{email::Attachment, MeliError, Result};
|
use melib::{email::Attachment, MeliError, Result};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -159,7 +158,8 @@ impl MailcapEntry {
|
||||||
.map(|arg| match *arg {
|
.map(|arg| match *arg {
|
||||||
"%s" => {
|
"%s" => {
|
||||||
needs_stdin = false;
|
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();
|
let p = _f.path().display().to_string();
|
||||||
f = Some(_f);
|
f = Some(_f);
|
||||||
p
|
p
|
||||||
|
@ -191,7 +191,11 @@ impl MailcapEntry {
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.spawn()?;
|
.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
|
child.wait_with_output()?.stdout
|
||||||
} else {
|
} else {
|
||||||
let child = Command::new("sh")
|
let child = Command::new("sh")
|
||||||
|
@ -221,7 +225,11 @@ impl MailcapEntry {
|
||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
.spawn()?;
|
.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);
|
debug!(child.wait_with_output()?.stdout);
|
||||||
} else {
|
} else {
|
||||||
let child = Command::new("sh")
|
let child = Command::new("sh")
|
||||||
|
|
|
@ -34,7 +34,7 @@ use termion::color::{AnsiValue, Rgb as TermionRgb};
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```no_run
|
||||||
/// use meli::Color;
|
/// use meli::Color;
|
||||||
///
|
///
|
||||||
/// // The default color.
|
/// // The default color.
|
||||||
|
|
Loading…
Reference in New Issue