cleanup mimeparser and use mime

This commit is contained in:
dignifiedquire
2019-12-02 08:44:51 +01:00
parent e94a896478
commit e6f737be9d
2 changed files with 104 additions and 167 deletions

View File

@@ -613,17 +613,16 @@ fn add_parts(
continue; continue;
} }
if let Some(ref msg) = part.msg { if mime_parser.location_kml.is_some()
if mime_parser.location_kml.is_some() && icnt == 1
&& icnt == 1 && (part.msg == "-location-" || part.msg.is_empty())
&& (msg == "-location-" || msg.is_empty()) {
{ *hidden = 1;
*hidden = 1; if state == MessageState::InFresh {
if state == MessageState::InFresh { state = MessageState::InNoticed;
state = MessageState::InNoticed;
}
} }
} }
if part.typ == Viewtype::Text { if part.typ == Viewtype::Text {
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default(); let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
let subject = mime_parser let subject = mime_parser
@@ -651,11 +650,11 @@ fn add_parts(
part.typ, part.typ,
state, state,
msgrmsg, msgrmsg,
part.msg.as_ref().map_or("", String::as_str), &part.msg,
// txt_raw might contain invalid utf8 // txt_raw might contain invalid utf8
txt_raw.unwrap_or_default(), txt_raw.unwrap_or_default(),
part.param.to_string(), part.param.to_string(),
part.bytes, part.bytes as isize,
*hidden, *hidden,
if save_mime_headers { if save_mime_headers {
Some(String::from_utf8_lossy(imf_raw)) Some(String::from_utf8_lossy(imf_raw))
@@ -1512,9 +1511,9 @@ fn set_better_msg(mime_parser: &mut MimeParser, better_msg: impl AsRef<str>) {
if msg.len() > 0 && !mime_parser.parts.is_empty() { if msg.len() > 0 && !mime_parser.parts.is_empty() {
let part = &mut mime_parser.parts[0]; let part = &mut mime_parser.parts[0];
if part.typ == Viewtype::Text { if part.typ == Viewtype::Text {
part.msg = Some(msg.to_string()); part.msg = msg.to_string();
} }
}; }
} }
fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimeParser) -> libc::c_int { fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimeParser) -> libc::c_int {

View File

@@ -1,6 +1,7 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use deltachat_derive::{FromSql, ToSql}; use deltachat_derive::{FromSql, ToSql};
use lettre_email::mime::{self, Mime};
use mailparse::MailHeaderMap; use mailparse::MailHeaderMap;
use crate::aheader::Aheader; use crate::aheader::Aheader;
@@ -62,30 +63,7 @@ impl Default for SystemMessage {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
pub enum DcMimeType {
Unknown,
MpAlternative,
MpRelated,
MpMixed,
MpNotDecryptable,
MpReport,
MpSigned,
MpOther,
TextPlain,
TextHtml,
Image,
Audio,
Video,
File,
AcSetupFile,
}
impl Default for DcMimeType {
fn default() -> Self {
DcMimeType::Unknown
}
}
impl<'a> MimeParser<'a> { impl<'a> MimeParser<'a> {
pub fn from_bytes(context: &'a Context, body: &[u8]) -> Result<Self> { pub fn from_bytes(context: &'a Context, body: &[u8]) -> Result<Self> {
@@ -176,10 +154,9 @@ impl<'a> MimeParser<'a> {
} }
if let Some(_) = self.lookup_field("Autocrypt-Setup-Message") { if let Some(_) = self.lookup_field("Autocrypt-Setup-Message") {
let has_setup_file = self let has_setup_file = self.parts.iter().any(|p| {
.parts p.mimetype.is_some() && p.mimetype.as_ref().unwrap().as_ref() == MIME_AC_SETUP_FILE
.iter() });
.any(|p| p.mimetype == DcMimeType::AcSetupFile);
if has_setup_file { if has_setup_file {
self.is_system_message = SystemMessage::AutocryptSetupMessage; self.is_system_message = SystemMessage::AutocryptSetupMessage;
@@ -197,7 +174,10 @@ impl<'a> MimeParser<'a> {
let mut i = 0; let mut i = 0;
while i != self.parts.len() { while i != self.parts.len() {
if self.parts[i].mimetype != DcMimeType::AcSetupFile { let mimetype = &self.parts[i].mimetype;
if mimetype.is_none()
|| mimetype.as_ref().unwrap().as_ref() != MIME_AC_SETUP_FILE
{
self.parts.remove(i); self.parts.remove(i);
} else { } else {
i += 1; i += 1;
@@ -242,10 +222,10 @@ impl<'a> MimeParser<'a> {
let mut filepart = self.parts.swap_remove(1); let mut filepart = self.parts.swap_remove(1);
// insert new one // insert new one
filepart.msg = self.parts[0].msg.as_ref().map(|s| s.to_string()); filepart.msg = self.parts[0].msg.clone();
// forget the one we use now // forget the one we use now
self.parts[0].msg = None; self.parts[0].msg = "".to_string();
// swap new with old // swap new with old
std::mem::replace(&mut self.parts[0], filepart); std::mem::replace(&mut self.parts[0], filepart);
@@ -274,12 +254,7 @@ impl<'a> MimeParser<'a> {
if !subj.is_empty() { if !subj.is_empty() {
for part in self.parts.iter_mut() { for part in self.parts.iter_mut() {
if part.typ == Viewtype::Text { if part.typ == Viewtype::Text {
let new_txt = format!( part.msg = format!("{} {}", subj, part.msg);
"{} {}",
subj,
part.msg.as_ref().expect("missing msg part")
);
part.msg = Some(new_txt);
break; break;
} }
} }
@@ -345,18 +320,18 @@ impl<'a> MimeParser<'a> {
} }
} }
/* Cleanup - and try to create at least an empty part if there are no parts yet */ // Cleanup - and try to create at least an empty part if there are no parts yet
if self.get_last_nonmeta().is_none() && self.reports.is_empty() { if self.get_last_nonmeta().is_none() && self.reports.is_empty() {
let mut part_5 = Part::default(); let mut part = Part::default();
part_5.typ = Viewtype::Text; part.typ = Viewtype::Text;
part_5.msg = Some("".into());
if let Some(ref subject) = self.subject { if let Some(ref subject) = self.subject {
if !self.is_send_by_messenger { if !self.is_send_by_messenger {
part_5.msg = Some(subject.to_string()) part.msg = subject.to_string();
} }
} }
self.parts.push(part_5);
self.parts.push(part);
} }
Ok(()) Ok(())
@@ -437,14 +412,15 @@ impl<'a> MimeParser<'a> {
fn handle_multiple(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> { fn handle_multiple(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> {
let mut any_part_added = false; let mut any_part_added = false;
match mailmime_get_mime_type(mail) { let mimetype = get_mime_type(mail)?.0;
match (mimetype.type_(), mimetype.subtype().as_str()) {
/* Most times, mutlipart/alternative contains true alternatives /* Most times, mutlipart/alternative contains true alternatives
as text/plain and text/html. If we find a multipart/mixed as text/plain and text/html. If we find a multipart/mixed
inside mutlipart/alternative, we use this (happens eg in inside mutlipart/alternative, we use this (happens eg in
apple mail: "plaintext" as an alternative to "html+PDF attachment") */ apple mail: "plaintext" as an alternative to "html+PDF attachment") */
(DcMimeType::MpAlternative, _) => { (mime::MULTIPART, "alternative") => {
for cur_data in &mail.subparts { for cur_data in &mail.subparts {
if mailmime_get_mime_type(cur_data).0 == DcMimeType::MpMixed { if get_mime_type(cur_data)?.0 == "multipart/mixed" {
any_part_added = self.parse_mime_recursive(cur_data)?; any_part_added = self.parse_mime_recursive(cur_data)?;
break; break;
} }
@@ -452,7 +428,7 @@ impl<'a> MimeParser<'a> {
if !any_part_added { if !any_part_added {
/* search for text/plain and add this */ /* search for text/plain and add this */
for cur_data in &mail.subparts { for cur_data in &mail.subparts {
if mailmime_get_mime_type(cur_data).0 == DcMimeType::TextPlain { if get_mime_type(cur_data)?.0.type_() == mime::TEXT {
any_part_added = self.parse_mime_recursive(cur_data)?; any_part_added = self.parse_mime_recursive(cur_data)?;
break; break;
} }
@@ -468,7 +444,7 @@ impl<'a> MimeParser<'a> {
} }
} }
} }
(DcMimeType::MpRelated, _) => { (mime::MULTIPART, "related") => {
/* add the "root part" - the other parts may be referenced which is /* add the "root part" - the other parts may be referenced which is
not interesting for us (eg. embedded images) we assume he "root part" not interesting for us (eg. embedded images) we assume he "root part"
being the first one, which may not be always true ... being the first one, which may not be always true ...
@@ -477,20 +453,21 @@ impl<'a> MimeParser<'a> {
any_part_added = self.parse_mime_recursive(first)?; any_part_added = self.parse_mime_recursive(first)?;
} }
} }
(DcMimeType::MpNotDecryptable, _) => { (mime::MULTIPART, "encrypted") => {
let msg_body = self.context.stock_str(StockMessage::CantDecryptMsgBody);
let txt = format!("[{}]", msg_body);
let mut part = Part::default(); let mut part = Part::default();
part.typ = Viewtype::Text; part.typ = Viewtype::Text;
let msg_body = self.context.stock_str(StockMessage::CantDecryptMsgBody);
let txt = format!("[{}]", msg_body);
part.msg_raw = Some(txt.clone()); part.msg_raw = Some(txt.clone());
part.msg = Some(txt); part.msg = txt;
self.parts.push(part); self.parts.push(part);
any_part_added = true; any_part_added = true;
self.decrypting_failed = true; self.decrypting_failed = true;
} }
(DcMimeType::MpSigned, _) => { (mime::MULTIPART, "signed") => {
/* RFC 1847: "The multipart/signed content type /* RFC 1847: "The multipart/signed content type
contains exactly two body parts. The first body contains exactly two body parts. The first body
part is the body part over which the digital signature was created [...] part is the body part over which the digital signature was created [...]
@@ -503,7 +480,7 @@ impl<'a> MimeParser<'a> {
any_part_added = self.parse_mime_recursive(first)?; any_part_added = self.parse_mime_recursive(first)?;
} }
} }
(DcMimeType::MpReport, _) => { (mime::MULTIPART, "report") => {
info!(self.context, "got report {}", mail.subparts.len()); info!(self.context, "got report {}", mail.subparts.len());
/* RFC 6522: the first part is for humans, the second for machines */ /* RFC 6522: the first part is for humans, the second for machines */
if mail.subparts.len() >= 2 { if mail.subparts.len() >= 2 {
@@ -525,25 +502,25 @@ impl<'a> MimeParser<'a> {
} }
} }
_ => { _ => {
/* eg. DcMimeType::MpMixed - add all parts (in fact, // Add all parts (in fact,
AddSinglePartIfKnown() later check if the parts are really supported) // AddSinglePartIfKnown() later check if the parts are really supported)
HACK: the following lines are a hack for clients who use // HACK: the following lines are a hack for clients who use
multipart/mixed instead of multipart/alternative for // multipart/mixed instead of multipart/alternative for
combined text/html messages (eg. Stock Android "Mail" does so). // combined text/html messages (eg. Stock Android "Mail" does so).
So, if we detect such a message below, we skip the Html // So, if we detect such a message below, we skip the Html
part. However, not sure, if there are useful situations to use // part. However, not sure, if there are useful situations to use
plain+html in multipart/mixed - if so, we should disable the hack. */ // plain+html in multipart/mixed - if so, we should disable the hack.
let mut skip_part = -1; let mut skip_part = -1;
let mut html_part = -1; let mut html_part = -1;
let mut plain_cnt = 0; let mut plain_cnt = 0;
let mut html_cnt = 0; let mut html_cnt = 0;
for (i, cur_data) in mail.subparts.iter().enumerate() { for (i, cur_data) in mail.subparts.iter().enumerate() {
match mailmime_get_mime_type(cur_data) { match get_mime_type(cur_data)?.0.type_() {
(DcMimeType::TextPlain, _) => { mime::TEXT => {
plain_cnt += 1; plain_cnt += 1;
} }
(DcMimeType::TextHtml, _) => { mime::HTML => {
html_part = i as isize; html_part = i as isize;
html_cnt += 1; html_cnt += 1;
} }
@@ -559,10 +536,8 @@ impl<'a> MimeParser<'a> {
} }
for (i, cur_data) in mail.subparts.iter().enumerate() { for (i, cur_data) in mail.subparts.iter().enumerate() {
if i as isize != skip_part { if i as isize != skip_part && self.parse_mime_recursive(cur_data)? {
if self.parse_mime_recursive(cur_data)? { any_part_added = true;
any_part_added = true;
}
} }
} }
} }
@@ -573,7 +548,7 @@ impl<'a> MimeParser<'a> {
fn add_single_part_if_known(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> { fn add_single_part_if_known(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> {
// return true if a part was added // return true if a part was added
let (mime_type, msg_type) = mailmime_get_mime_type(mail); let (mime_type, msg_type) = get_mime_type(mail)?;
let raw_mime = mail.ctype.mimetype.to_lowercase(); let raw_mime = mail.ctype.mimetype.to_lowercase();
info!( info!(
@@ -584,8 +559,8 @@ impl<'a> MimeParser<'a> {
let old_part_count = self.parts.len(); let old_part_count = self.parts.len();
// regard `Content-Transfer-Encoding:` // regard `Content-Transfer-Encoding:`
match mime_type { match mime_type.type_() {
DcMimeType::TextPlain | DcMimeType::TextHtml => { mime::TEXT | mime::HTML => {
let decoded_data = match mail.get_body() { let decoded_data = match mail.get_body() {
Ok(decoded_data) => decoded_data, Ok(decoded_data) => decoded_data,
Err(err) => { Err(err) => {
@@ -602,15 +577,15 @@ impl<'a> MimeParser<'a> {
let simplified_txt = if decoded_data.is_empty() { let simplified_txt = if decoded_data.is_empty() {
"".into() "".into()
} else { } else {
let is_html = mime_type == DcMimeType::TextHtml; let is_html = mime_type == mime::TEXT_HTML;
simplifier.simplify(&decoded_data, is_html, is_msgrmsg) simplifier.simplify(&decoded_data, is_html, is_msgrmsg)
}; };
if !simplified_txt.is_empty() { if !simplified_txt.is_empty() {
let mut part = Part::default(); let mut part = Part::default();
part.typ = Viewtype::Text; part.typ = Viewtype::Text;
part.mimetype = mime_type; part.mimetype = Some(mime_type);
part.msg = Some(simplified_txt); part.msg = simplified_txt;
part.msg_raw = Some(decoded_data); part.msg_raw = Some(decoded_data);
self.do_add_single_part(part); self.do_add_single_part(part);
} }
@@ -619,11 +594,7 @@ impl<'a> MimeParser<'a> {
self.is_forwarded = true; self.is_forwarded = true;
} }
} }
DcMimeType::Image mime::IMAGE | mime::AUDIO | mime::VIDEO | mime::APPLICATION => {
| DcMimeType::Audio
| DcMimeType::Video
| DcMimeType::File
| DcMimeType::AcSetupFile => {
// try to get file name from // try to get file name from
// `Content-Disposition: ... filename*=...` // `Content-Disposition: ... filename*=...`
// or `Content-Disposition: ... filename*0*=... filename*1*=... filename*2*=...` // or `Content-Disposition: ... filename*0*=... filename*1*=... filename*2*=...`
@@ -672,7 +643,7 @@ impl<'a> MimeParser<'a> {
fn do_add_single_file_part( fn do_add_single_file_part(
&mut self, &mut self,
msg_type: Viewtype, msg_type: Viewtype,
mime_type: DcMimeType, mime_type: Mime,
raw_mime: &String, raw_mime: &String,
decoded_data: &[u8], decoded_data: &[u8],
filename: &str, filename: &str,
@@ -714,18 +685,19 @@ impl<'a> MimeParser<'a> {
/* create and register Mime part referencing the new Blob object */ /* create and register Mime part referencing the new Blob object */
let mut part = Part::default(); let mut part = Part::default();
part.typ = msg_type; if mime_type.type_() == mime::IMAGE {
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, blob.as_name());
part.param.set(Param::MimeType, raw_mime);
if mime_type == DcMimeType::Image {
if let Ok((width, height)) = dc_get_filemeta(decoded_data) { if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
part.param.set_int(Param::Width, width as i32); part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32); part.param.set_int(Param::Height, height as i32);
} }
} }
part.typ = msg_type;
part.mimetype = Some(mime_type);
part.bytes = decoded_data.len();
part.param.set(Param::File, blob.as_name());
part.param.set(Param::MimeType, raw_mime);
self.do_add_single_part(part); self.do_add_single_part(part);
} }
@@ -783,7 +755,7 @@ impl<'a> MimeParser<'a> {
let part = &mut self.parts[0]; let part = &mut self.parts[0];
part.typ = Viewtype::Text; part.typ = Viewtype::Text;
part.msg = Some(format!("[{}]", error_msg.as_ref())); part.msg = format!("[{}]", error_msg.as_ref());
self.parts.truncate(1); self.parts.truncate(1);
assert_eq!(self.parts.len(), 1); assert_eq!(self.parts.len(), 1);
@@ -968,69 +940,40 @@ fn is_known(key: &str) -> bool {
} }
} }
#[derive(Default, Debug, Clone)] #[derive(Debug, Default, Clone)]
pub struct Part { pub struct Part {
pub typ: Viewtype, pub typ: Viewtype,
pub is_meta: bool, pub is_meta: bool,
pub mimetype: DcMimeType, pub mimetype: Option<Mime>,
pub msg: Option<String>, pub msg: String,
pub msg_raw: Option<String>, pub msg_raw: Option<String>,
pub bytes: i32, pub bytes: usize,
pub param: Params, pub param: Params,
} }
fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (DcMimeType, Viewtype) { fn get_mime_type(mail: &mailparse::ParsedMail<'_>) -> Result<(Mime, Viewtype)> {
let unknown_type = (DcMimeType::Unknown, Viewtype::Unknown); let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
let mimetype = mail.ctype.mimetype.to_lowercase(); let viewtype = match mimetype.type_() {
let mut parts = mimetype.split('/'); mime::TEXT => {
let typ = parts.next().expect("invalid mimetype");
let subtype = parts.next().unwrap_or_default();
match typ {
"text" => {
if !mailmime_is_attachment_disposition(mail) { if !mailmime_is_attachment_disposition(mail) {
if subtype == "plain" { match mimetype.subtype() {
return (DcMimeType::TextPlain, Viewtype::Text); mime::PLAIN | mime::HTML => Viewtype::Text,
} _ => Viewtype::File,
if subtype == "html" {
return (DcMimeType::TextHtml, Viewtype::Text);
} }
} else {
Viewtype::File
} }
(DcMimeType::File, Viewtype::File)
} }
"image" => { mime::IMAGE => match mimetype.subtype() {
let msg_type = match subtype { mime::GIF => Viewtype::Gif,
"gif" => Viewtype::Gif, mime::SVG => Viewtype::File,
"svg+xml" => { _ => Viewtype::Image,
return (DcMimeType::File, Viewtype::File); },
} mime::AUDIO => Viewtype::Audio,
_ => Viewtype::Image, mime::VIDEO => Viewtype::Video,
}; mime::MULTIPART => Viewtype::Unknown,
mime::MESSAGE => {
(DcMimeType::Image, msg_type)
}
"audio" => (DcMimeType::Audio, Viewtype::Audio),
"video" => (DcMimeType::Video, Viewtype::Video),
"multipart" => {
let mime_type = match subtype {
"alternative" => DcMimeType::MpAlternative,
"related" => DcMimeType::MpRelated,
"encrypted" => {
// maybe try_decrypt failed to decrypt
// or it wasn't in proper Autocrypt format
DcMimeType::MpNotDecryptable
}
"signed" => DcMimeType::MpSigned,
"mixed" => DcMimeType::MpMixed,
"report" => DcMimeType::MpReport,
_ => DcMimeType::MpOther,
};
(mime_type, Viewtype::Unknown)
}
"message" => {
// Enacapsulated messages, see https://www.w3.org/Protocols/rfc1341/7_3_Message.html // Enacapsulated messages, see https://www.w3.org/Protocols/rfc1341/7_3_Message.html
// Also used as part "message/disposition-notification" of "multipart/report", which, however, will // Also used as part "message/disposition-notification" of "multipart/report", which, however, will
// be handled separatedly. // be handled separatedly.
@@ -1038,17 +981,13 @@ fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (DcMimeType, View
// which are unwanted at all). // which are unwanted at all).
// For now, we skip these parts at all; if desired, we could return DcMimeType::File/DC_MSG_File // For now, we skip these parts at all; if desired, we could return DcMimeType::File/DC_MSG_File
// for selected and known subparts. // for selected and known subparts.
unknown_type Viewtype::Unknown
} }
"application" => { mime::APPLICATION => Viewtype::File,
if subtype == "autocrypt-setup" { _ => Viewtype::Unknown,
return (DcMimeType::AcSetupFile, Viewtype::File); };
}
(DcMimeType::File, Viewtype::File) Ok((mimetype, viewtype))
}
_ => unknown_type,
}
} }
fn mailmime_is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool { fn mailmime_is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool {
@@ -1127,10 +1066,9 @@ mod tests {
#[test] #[test]
fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") { fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") {
let context = dummy_context(); let context = dummy_context();
// parsing should error out for all these random strings
assert!( // just don't crash
MimeParser::from_bytes(&context.ctx, data.as_bytes()).is_err() let _ = MimeParser::from_bytes(&context.ctx, data.as_bytes());
);
} }
} }