mimeparser: pass the Context around explicitly

This commit is contained in:
Alexander Krotov
2020-01-29 22:40:15 +03:00
parent d83652b0fc
commit 8ef7b6fc54
3 changed files with 82 additions and 61 deletions

View File

@@ -244,7 +244,7 @@ pub fn dc_receive_imf(
cleanup(context, &create_event_to_send, &created_db_entries); cleanup(context, &create_event_to_send, &created_db_entries);
mime_parser.handle_reports(from_id, sent_timestamp, &server_folder, server_uid); mime_parser.handle_reports(context, from_id, sent_timestamp, &server_folder, server_uid);
Ok(()) Ok(())
} }
@@ -1054,7 +1054,7 @@ fn create_or_lookup_group(
} }
/// try extract a grpid from a message-id list header value /// try extract a grpid from a message-id list header value
fn extract_grpid<'a>(mime_parser: &'a MimeMessage, headerdef: HeaderDef) -> Option<&'a str> { fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<&str> {
let header = mime_parser.get(headerdef)?; let header = mime_parser.get(headerdef)?;
let parts = header let parts = header
.split(',') .split(',')

View File

@@ -36,8 +36,7 @@ use crate::{bail, ensure};
/// It is created by parsing the raw data of an actual MIME message /// It is created by parsing the raw data of an actual MIME message
/// using the [MimeMessage::from_bytes] constructor. /// using the [MimeMessage::from_bytes] constructor.
#[derive(Debug)] #[derive(Debug)]
pub struct MimeMessage<'a> { pub struct MimeMessage {
pub context: &'a Context,
pub parts: Vec<Part>, pub parts: Vec<Part>,
header: HashMap<String, String>, header: HashMap<String, String>,
pub decrypting_failed: bool, pub decrypting_failed: bool,
@@ -91,8 +90,8 @@ impl Default for SystemMessage {
const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup"; const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
impl<'a> MimeMessage<'a> { impl MimeMessage {
pub fn from_bytes(context: &'a Context, body: &[u8]) -> Result<Self> { pub fn from_bytes(context: &Context, body: &[u8]) -> Result<Self> {
let mail = mailparse::parse_mail(body)?; let mail = mailparse::parse_mail(body)?;
let message_time = mail let message_time = mail
@@ -160,7 +159,6 @@ impl<'a> MimeMessage<'a> {
signatures, signatures,
gossipped_addr, gossipped_addr,
is_forwarded: false, is_forwarded: false,
context,
reports: Vec::new(), reports: Vec::new(),
is_system_message: SystemMessage::Unknown, is_system_message: SystemMessage::Unknown,
location_kml: None, location_kml: None,
@@ -168,14 +166,14 @@ impl<'a> MimeMessage<'a> {
user_avatar: AvatarAction::None, user_avatar: AvatarAction::None,
group_avatar: AvatarAction::None, group_avatar: AvatarAction::None,
}; };
parser.parse_mime_recursive(&mail)?; parser.parse_mime_recursive(context, &mail)?;
parser.parse_headers()?; parser.parse_headers(context)?;
Ok(parser) Ok(parser)
} }
/// Parses system messages. /// Parses system messages.
fn parse_system_message_headers(&mut self) -> Result<()> { fn parse_system_message_headers(&mut self, context: &Context) -> Result<()> {
if self.get(HeaderDef::AutocryptSetupMessage).is_some() { if self.get(HeaderDef::AutocryptSetupMessage).is_some() {
self.parts = self self.parts = self
.parts .parts
@@ -190,7 +188,7 @@ impl<'a> MimeMessage<'a> {
if self.parts.len() == 1 { if self.parts.len() == 1 {
self.is_system_message = SystemMessage::AutocryptSetupMessage; self.is_system_message = SystemMessage::AutocryptSetupMessage;
} else { } else {
warn!(self.context, "could not determine ASM mime-part"); warn!(context, "could not determine ASM mime-part");
} }
} else if let Some(value) = self.get(HeaderDef::ChatContent) { } else if let Some(value) = self.get(HeaderDef::ChatContent) {
if value == "location-streaming-enabled" { if value == "location-streaming-enabled" {
@@ -284,8 +282,8 @@ impl<'a> MimeMessage<'a> {
} }
} }
fn parse_headers(&mut self) -> Result<()> { fn parse_headers(&mut self, context: &Context) -> Result<()> {
self.parse_system_message_headers()?; self.parse_system_message_headers(context)?;
self.parse_avatar_headers(); self.parse_avatar_headers();
self.squash_attachment_parts(); self.squash_attachment_parts();
@@ -330,9 +328,9 @@ impl<'a> MimeMessage<'a> {
// See if an MDN is requested from the other side // See if an MDN is requested from the other side
if !self.decrypting_failed && !self.parts.is_empty() { if !self.decrypting_failed && !self.parts.is_empty() {
if let Some(ref dn_to_addr) = if let Some(ref dn_to_addr) =
self.parse_first_addr(HeaderDef::ChatDispositionNotificationTo) self.parse_first_addr(context, HeaderDef::ChatDispositionNotificationTo)
{ {
if let Some(ref from_addr) = self.parse_first_addr(HeaderDef::From_) { if let Some(ref from_addr) = self.parse_first_addr(context, HeaderDef::From_) {
if compare_addrs(from_addr, dn_to_addr) { if compare_addrs(from_addr, dn_to_addr) {
if let Some(part) = self.parts.last_mut() { if let Some(part) = self.parts.last_mut() {
part.param.set_int(Param::WantsMdn, 1); part.param.set_int(Param::WantsMdn, 1);
@@ -407,31 +405,35 @@ impl<'a> MimeMessage<'a> {
self.header.get(&headerdef.get_headername()) self.header.get(&headerdef.get_headername())
} }
fn parse_first_addr(&self, headerdef: HeaderDef) -> Option<MailAddr> { fn parse_first_addr(&self, context: &Context, headerdef: HeaderDef) -> Option<MailAddr> {
if let Some(value) = self.get(headerdef.clone()) { if let Some(value) = self.get(headerdef.clone()) {
match mailparse::addrparse(&value) { match mailparse::addrparse(&value) {
Ok(ref addrs) => { Ok(ref addrs) => {
return addrs.first().cloned(); return addrs.first().cloned();
} }
Err(err) => { Err(err) => {
warn!(self.context, "header {} parse error: {:?}", headerdef, err); warn!(context, "header {} parse error: {:?}", headerdef, err);
} }
} }
} }
None None
} }
fn parse_mime_recursive(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> { fn parse_mime_recursive(
&mut self,
context: &Context,
mail: &mailparse::ParsedMail<'_>,
) -> Result<bool> {
if mail.ctype.params.get("protected-headers").is_some() { if mail.ctype.params.get("protected-headers").is_some() {
if mail.ctype.mimetype == "text/rfc822-headers" { if mail.ctype.mimetype == "text/rfc822-headers" {
warn!( warn!(
self.context, context,
"Protected headers found in text/rfc822-headers attachment: Will be ignored.", "Protected headers found in text/rfc822-headers attachment: Will be ignored.",
); );
return Ok(false); return Ok(false);
} }
warn!(self.context, "Ignoring nested protected headers"); warn!(context, "Ignoring nested protected headers");
} }
enum MimeS { enum MimeS {
@@ -459,7 +461,7 @@ impl<'a> MimeMessage<'a> {
}; };
match m { match m {
MimeS::Multiple => self.handle_multiple(mail), MimeS::Multiple => self.handle_multiple(context, mail),
MimeS::Message => { MimeS::Message => {
let raw = mail.get_body_raw()?; let raw = mail.get_body_raw()?;
if raw.is_empty() { if raw.is_empty() {
@@ -467,13 +469,17 @@ impl<'a> MimeMessage<'a> {
} }
let mail = mailparse::parse_mail(&raw).unwrap(); let mail = mailparse::parse_mail(&raw).unwrap();
self.parse_mime_recursive(&mail) self.parse_mime_recursive(context, &mail)
} }
MimeS::Single => self.add_single_part_if_known(mail), MimeS::Single => self.add_single_part_if_known(context, mail),
} }
} }
fn handle_multiple(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> { fn handle_multiple(
&mut self,
context: &Context,
mail: &mailparse::ParsedMail<'_>,
) -> Result<bool> {
let mut any_part_added = false; let mut any_part_added = false;
let mimetype = get_mime_type(mail)?.0; let mimetype = get_mime_type(mail)?.0;
match (mimetype.type_(), mimetype.subtype().as_str()) { match (mimetype.type_(), mimetype.subtype().as_str()) {
@@ -484,7 +490,7 @@ impl<'a> MimeMessage<'a> {
(mime::MULTIPART, "alternative") => { (mime::MULTIPART, "alternative") => {
for cur_data in &mail.subparts { for cur_data in &mail.subparts {
if get_mime_type(cur_data)?.0 == "multipart/mixed" { 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(context, cur_data)?;
break; break;
} }
} }
@@ -492,7 +498,7 @@ impl<'a> MimeMessage<'a> {
/* 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 get_mime_type(cur_data)?.0.type_() == mime::TEXT { 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(context, cur_data)?;
break; break;
} }
} }
@@ -500,7 +506,7 @@ impl<'a> MimeMessage<'a> {
if !any_part_added { if !any_part_added {
/* `text/plain` not found - use the first part */ /* `text/plain` not found - use the first part */
for cur_part in &mail.subparts { for cur_part in &mail.subparts {
if self.parse_mime_recursive(cur_part)? { if self.parse_mime_recursive(context, cur_part)? {
any_part_added = true; any_part_added = true;
break; break;
} }
@@ -513,14 +519,14 @@ impl<'a> MimeMessage<'a> {
being the first one, which may not be always true ... being the first one, which may not be always true ...
however, most times it seems okay. */ however, most times it seems okay. */
if let Some(first) = mail.subparts.iter().next() { if let Some(first) = mail.subparts.iter().next() {
any_part_added = self.parse_mime_recursive(first)?; any_part_added = self.parse_mime_recursive(context, first)?;
} }
} }
(mime::MULTIPART, "encrypted") => { (mime::MULTIPART, "encrypted") => {
// we currently do not try to decrypt non-autocrypt messages // we currently do not try to decrypt non-autocrypt messages
// at all. If we see an encrypted part, we set // at all. If we see an encrypted part, we set
// decrypting_failed. // decrypting_failed.
let msg_body = self.context.stock_str(StockMessage::CantDecryptMsgBody); let msg_body = context.stock_str(StockMessage::CantDecryptMsgBody);
let txt = format!("[{}]", msg_body); let txt = format!("[{}]", msg_body);
let mut part = Part::default(); let mut part = Part::default();
@@ -543,7 +549,7 @@ impl<'a> MimeMessage<'a> {
https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html
for background information why we use encrypted+signed) */ for background information why we use encrypted+signed) */
if let Some(first) = mail.subparts.iter().next() { if let Some(first) = mail.subparts.iter().next() {
any_part_added = self.parse_mime_recursive(first)?; any_part_added = self.parse_mime_recursive(context, first)?;
} }
} }
(mime::MULTIPART, "report") => { (mime::MULTIPART, "report") => {
@@ -551,14 +557,14 @@ impl<'a> MimeMessage<'a> {
if mail.subparts.len() >= 2 { if mail.subparts.len() >= 2 {
if let Some(report_type) = mail.ctype.params.get("report-type") { if let Some(report_type) = mail.ctype.params.get("report-type") {
if report_type == "disposition-notification" { if report_type == "disposition-notification" {
if let Some(report) = self.process_report(mail)? { if let Some(report) = self.process_report(context, mail)? {
self.reports.push(report); self.reports.push(report);
} }
} else { } else {
/* eg. `report-type=delivery-status`; /* eg. `report-type=delivery-status`;
maybe we should show them as a little error icon */ maybe we should show them as a little error icon */
if let Some(first) = mail.subparts.iter().next() { if let Some(first) = mail.subparts.iter().next() {
any_part_added = self.parse_mime_recursive(first)?; any_part_added = self.parse_mime_recursive(context, first)?;
} }
} }
} }
@@ -568,7 +574,7 @@ impl<'a> MimeMessage<'a> {
// Add all parts (in fact, AddSinglePartIfKnown() later check if // Add all parts (in fact, AddSinglePartIfKnown() later check if
// the parts are really supported) // the parts are really supported)
for cur_data in mail.subparts.iter() { for cur_data in mail.subparts.iter() {
if self.parse_mime_recursive(cur_data)? { if self.parse_mime_recursive(context, cur_data)? {
any_part_added = true; any_part_added = true;
} }
} }
@@ -578,7 +584,11 @@ impl<'a> MimeMessage<'a> {
Ok(any_part_added) Ok(any_part_added)
} }
fn add_single_part_if_known(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> { fn add_single_part_if_known(
&mut self,
context: &Context,
mail: &mailparse::ParsedMail<'_>,
) -> Result<bool> {
// return true if a part was added // return true if a part was added
let (mime_type, msg_type) = 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();
@@ -589,6 +599,7 @@ impl<'a> MimeMessage<'a> {
if let Ok(filename) = filename { if let Ok(filename) = filename {
self.do_add_single_file_part( self.do_add_single_file_part(
context,
msg_type, msg_type,
mime_type, mime_type,
&raw_mime, &raw_mime,
@@ -604,7 +615,7 @@ impl<'a> MimeMessage<'a> {
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) => {
warn!(self.context, "Invalid body parsed {:?}", err); warn!(context, "Invalid body parsed {:?}", err);
// Note that it's not always an error - might be no data // Note that it's not always an error - might be no data
return Ok(false); return Ok(false);
} }
@@ -645,6 +656,7 @@ impl<'a> MimeMessage<'a> {
fn do_add_single_file_part( fn do_add_single_file_part(
&mut self, &mut self,
context: &Context,
msg_type: Viewtype, msg_type: Viewtype,
mime_type: Mime, mime_type: Mime,
raw_mime: &str, raw_mime: &str,
@@ -659,9 +671,9 @@ impl<'a> MimeMessage<'a> {
// XXX what if somebody sends eg an "location-highlights.kml" // XXX what if somebody sends eg an "location-highlights.kml"
// attachment unrelated to location streaming? // attachment unrelated to location streaming?
if filename.starts_with("location") || filename.starts_with("message") { if filename.starts_with("location") || filename.starts_with("message") {
let parsed = location::Kml::parse(self.context, decoded_data) let parsed = location::Kml::parse(context, decoded_data)
.map_err(|err| { .map_err(|err| {
warn!(self.context, "failed to parse kml part: {}", err); warn!(context, "failed to parse kml part: {}", err);
}) })
.ok(); .ok();
if filename.starts_with("location") { if filename.starts_with("location") {
@@ -675,17 +687,17 @@ impl<'a> MimeMessage<'a> {
/* we have a regular file attachment, /* we have a regular file attachment,
write decoded data to new blob object */ write decoded data to new blob object */
let blob = match BlobObject::create(self.context, filename, decoded_data) { let blob = match BlobObject::create(context, filename, decoded_data) {
Ok(blob) => blob, Ok(blob) => blob,
Err(err) => { Err(err) => {
error!( error!(
self.context, context,
"Could not add blob for mime part {}, error {}", filename, err "Could not add blob for mime part {}, error {}", filename, err
); );
return; return;
} }
}; };
info!(self.context, "added blobfile: {:?}", blob.as_name()); info!(context, "added blobfile: {:?}", blob.as_name());
/* 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();
@@ -759,7 +771,11 @@ impl<'a> MimeMessage<'a> {
} }
} }
fn process_report(&self, report: &mailparse::ParsedMail<'_>) -> Result<Option<Report>> { fn process_report(
&self,
context: &Context,
report: &mailparse::ParsedMail<'_>,
) -> Result<Option<Report>> {
// parse as mailheaders // parse as mailheaders
let report_body = report.subparts[1].get_body_raw()?; let report_body = report.subparts[1].get_body_raw()?;
let (report_fields, _) = mailparse::parse_headers(&report_body)?; let (report_fields, _) = mailparse::parse_headers(&report_body)?;
@@ -788,7 +804,7 @@ impl<'a> MimeMessage<'a> {
} }
} }
warn!( warn!(
self.context, context,
"ignoring unknown disposition-notification, Message-Id: {:?}", "ignoring unknown disposition-notification, Message-Id: {:?}",
report_fields.get_first_value("Message-ID").ok() report_fields.get_first_value("Message-ID").ok()
); );
@@ -799,6 +815,7 @@ impl<'a> MimeMessage<'a> {
/// Handle reports (only MDNs for now) /// Handle reports (only MDNs for now)
pub fn handle_reports( pub fn handle_reports(
&self, &self,
context: &Context,
from_id: u32, from_id: u32,
sent_timestamp: i64, sent_timestamp: i64,
server_folder: impl AsRef<str>, server_folder: impl AsRef<str>,
@@ -813,13 +830,10 @@ impl<'a> MimeMessage<'a> {
for original_message_id in for original_message_id in
std::iter::once(&report.original_message_id).chain(&report.additional_message_ids) std::iter::once(&report.original_message_id).chain(&report.additional_message_ids)
{ {
if let Some((chat_id, msg_id)) = message::mdn_from_ext( if let Some((chat_id, msg_id)) =
self.context, message::mdn_from_ext(context, from_id, original_message_id, sent_timestamp)
from_id, {
original_message_id, context.call_cb(Event::MsgRead { chat_id, msg_id });
sent_timestamp,
) {
self.context.call_cb(Event::MsgRead { chat_id, msg_id });
mdn_recognized = true; mdn_recognized = true;
} }
} }
@@ -829,10 +843,10 @@ impl<'a> MimeMessage<'a> {
let mut param = Params::new(); let mut param = Params::new();
param.set(Param::ServerFolder, server_folder.as_ref()); param.set(Param::ServerFolder, server_folder.as_ref());
param.set_int(Param::ServerUid, server_uid as i32); param.set_int(Param::ServerUid, server_uid as i32);
if self.has_chat_version() && self.context.get_config_bool(Config::MvboxMove) { if self.has_chat_version() && context.get_config_bool(Config::MvboxMove) {
param.set_int(Param::AlsoMove, 1); param.set_int(Param::AlsoMove, 1);
} }
job_add(self.context, Action::MarkseenMdnOnImap, 0, param, 0); job_add(context, Action::MarkseenMdnOnImap, 0, param, 0);
} }
} }
} }
@@ -1166,10 +1180,13 @@ mod tests {
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
let of = mimeparser.parse_first_addr(HeaderDef::From_).unwrap(); let of = mimeparser
.parse_first_addr(&context.ctx, HeaderDef::From_)
.unwrap();
assert_eq!(of, mailparse::addrparse("hello@one.org").unwrap()[0]); assert_eq!(of, mailparse::addrparse("hello@one.org").unwrap()[0]);
let of = mimeparser.parse_first_addr(HeaderDef::ChatDispositionNotificationTo); let of =
mimeparser.parse_first_addr(&context.ctx, HeaderDef::ChatDispositionNotificationTo);
assert!(of.is_none()); assert!(of.is_none());
} }

View File

@@ -485,7 +485,7 @@ pub(crate) fn handle_securejoin_handshake(
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string(); let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
let auth = get_qr_attr!(context, auth).to_string(); let auth = get_qr_attr!(context, auth).to_string();
if !encrypted_and_signed(mime_message, &scanned_fingerprint_of_alice) { if !encrypted_and_signed(context, mime_message, &scanned_fingerprint_of_alice) {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
@@ -548,7 +548,7 @@ pub(crate) fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
}; };
if !encrypted_and_signed(mime_message, &fingerprint) { if !encrypted_and_signed(context, mime_message, &fingerprint) {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
@@ -675,7 +675,7 @@ pub(crate) fn handle_securejoin_handshake(
true true
}; };
if vg_expect_encrypted if vg_expect_encrypted
&& !encrypted_and_signed(mime_message, &scanned_fingerprint_of_alice) && !encrypted_and_signed(context, mime_message, &scanned_fingerprint_of_alice)
{ {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
@@ -830,22 +830,26 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
* Tools: Misc. * Tools: Misc.
******************************************************************************/ ******************************************************************************/
fn encrypted_and_signed(mimeparser: &MimeMessage, expected_fingerprint: impl AsRef<str>) -> bool { fn encrypted_and_signed(
context: &Context,
mimeparser: &MimeMessage,
expected_fingerprint: impl AsRef<str>,
) -> bool {
if !mimeparser.was_encrypted() { if !mimeparser.was_encrypted() {
warn!(mimeparser.context, "Message not encrypted.",); warn!(context, "Message not encrypted.",);
false false
} else if mimeparser.signatures.is_empty() { } else if mimeparser.signatures.is_empty() {
warn!(mimeparser.context, "Message not signed.",); warn!(context, "Message not signed.",);
false false
} else if expected_fingerprint.as_ref().is_empty() { } else if expected_fingerprint.as_ref().is_empty() {
warn!(mimeparser.context, "Fingerprint for comparison missing.",); warn!(context, "Fingerprint for comparison missing.",);
false false
} else if !mimeparser } else if !mimeparser
.signatures .signatures
.contains(expected_fingerprint.as_ref()) .contains(expected_fingerprint.as_ref())
{ {
warn!( warn!(
mimeparser.context, context,
"Message does not match expected fingerprint {}.", "Message does not match expected fingerprint {}.",
expected_fingerprint.as_ref(), expected_fingerprint.as_ref(),
); );