fix various parsing and sending issues

This commit is contained in:
dignifiedquire
2019-12-01 21:32:15 +01:00
parent 6bb2adaf45
commit d5287256e0
6 changed files with 270 additions and 227 deletions

View File

@@ -8,6 +8,7 @@ use std::{fmt, str};
use crate::constants::*; use crate::constants::*;
use crate::contact::*; use crate::contact::*;
use crate::context::Context;
use crate::key::*; use crate::key::*;
/// Possible values for encryption preference /// Possible values for encryption preference
@@ -65,16 +66,27 @@ impl Aheader {
} }
} }
pub fn from_imffields( pub fn from_headers(
context: &Context,
wanted_from: &str, wanted_from: &str,
headers: &[mailparse::MailHeader<'_>], headers: &[mailparse::MailHeader<'_>],
) -> Option<Self> { ) -> Option<Self> {
use mailparse::MailHeaderMap; use mailparse::MailHeaderMap;
if let Ok(Some(value)) = headers.get_first_value("Autocrypt") { if let Ok(Some(value)) = headers.get_first_value("Autocrypt") {
if let Ok(test) = Self::from_str(&value) { match Self::from_str(&value) {
if addr_cmp(&test.addr, wanted_from) { Ok(header) => {
return Some(test); info!(context, "comparing {} - {}", header.addr, wanted_from);
if addr_cmp(&header.addr, wanted_from) {
info!(context, "found header {:?}", header);
return Some(header);
}
}
Err(err) => {
warn!(
context,
"found invalid autocrypt header {}: {:?}", value, err
);
} }
} }
} }

View File

@@ -88,47 +88,29 @@ impl EncryptHelper {
&mut self, &mut self,
context: &Context, context: &Context,
min_verified: PeerstateVerifiedStatus, min_verified: PeerstateVerifiedStatus,
do_gossip: bool, mail_to_encrypt: lettre_email::PartBuilder,
mut mail_to_encrypt: lettre_email::PartBuilder,
peerstates: &[(Option<Peerstate>, &str)], peerstates: &[(Option<Peerstate>, &str)],
) -> Result<String> { ) -> Result<String> {
let mut keyring = Keyring::default(); let mut keyring = Keyring::default();
let mut gossip_headers: Vec<String> = Vec::with_capacity(peerstates.len());
for (peerstate, addr) in peerstates for (peerstate, addr) in peerstates
.iter() .iter()
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr))) .filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
{ {
if let Some(key) = peerstate.peek_key(min_verified) { info!(context, "adding for {}: {:?}", addr, peerstate);
keyring.add_owned(key.clone()); let key = peerstate.peek_key(min_verified).ok_or_else(|| {
if do_gossip { format_err!("proper enc-key for {} missing, cannot encrypt", addr)
if let Some(header) = peerstate.render_gossip_header(min_verified) { })?;
gossip_headers.push(header.to_string()); keyring.add_ref(key);
}
}
} else {
bail!("proper enc-key for {} missing, cannot encrypt", addr);
}
} }
// libEtPan's pgp_encrypt_mime() takes the parent as the new root. keyring.add_ref(&self.public_key);
// We just expect the root as being given to this function. let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql)
let sign_key = { .ok_or_else(|| format_err!("missing own private key"))?;
keyring.add_ref(&self.public_key);
let key = Key::from_self_private(context, self.addr.clone(), &context.sql);
ensure!(key.is_some(), "no own private key found");
key
};
// Add gossip headers
for header in &gossip_headers {
mail_to_encrypt = mail_to_encrypt.header(("Autocrypt-Gossip", header));
}
let raw_message = mail_to_encrypt.build().as_string().into_bytes(); let raw_message = mail_to_encrypt.build().as_string().into_bytes();
let ctext = pgp::pk_encrypt(&raw_message, &keyring, sign_key.as_ref())?; let ctext = pgp::pk_encrypt(&raw_message, &keyring, Some(&sign_key))?;
Ok(ctext) Ok(ctext)
} }
@@ -137,18 +119,25 @@ impl EncryptHelper {
pub fn try_decrypt( pub fn try_decrypt(
context: &Context, context: &Context,
mail: &mailparse::ParsedMail<'_>, mail: &mailparse::ParsedMail<'_>,
) -> Result<(Option<Vec<u8>>, HashSet<String>, i64)> { message_time: i64,
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
use mailparse::MailHeaderMap; use mailparse::MailHeaderMap;
info!(context, "trying to decrypt: {:?}", mail.get_body());
for part in &mail.subparts {
info!(context, "trying to decrypt part: {:?}", part.get_body());
}
let from = mail.headers.get_first_value("From")?.unwrap_or_default(); let from = mail
let message_time = mail
.headers .headers
.get_first_value("Date")? .get_first_value("From")?
.and_then(|v| mailparse::dateparse(&v).ok()) .and_then(|from_addr| mailparse::addrparse(&from_addr).ok())
.and_then(|from| from.extract_single_info())
.map(|from| from.addr)
.unwrap_or_default(); .unwrap_or_default();
let mut peerstate = None; let mut peerstate = None;
let autocryptheader = Aheader::from_imffields(&from, &mail.headers); let autocryptheader = Aheader::from_headers(context, &from, &mail.headers);
info!(context, "got autocryptheader {:?}", &autocryptheader);
if message_time > 0 { if message_time > 0 {
peerstate = Peerstate::from_addr(context, &context.sql, &from); peerstate = Peerstate::from_addr(context, &context.sql, &from);
@@ -167,6 +156,7 @@ pub fn try_decrypt(
peerstate = Some(p); peerstate = Some(p);
} }
} }
/* possibly perform decryption */ /* possibly perform decryption */
let mut private_keyring = Keyring::default(); let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default(); let mut public_keyring_for_validate = Keyring::default();
@@ -200,7 +190,7 @@ pub fn try_decrypt(
)?; )?;
} }
} }
Ok((out_mail, signatures, message_time)) Ok((out_mail, signatures))
} }
/// Load public key from database or generate a new one. /// Load public key from database or generate a new one.
@@ -265,8 +255,9 @@ fn decrypt_if_autocrypt_message<'a>(
// Errors are returned for failures related to decryption of AC-messages. // Errors are returned for failures related to decryption of AC-messages.
let encrypted_data_part = match wrapmime::get_autocrypt_mime(mail) { let encrypted_data_part = match wrapmime::get_autocrypt_mime(mail) {
Err(_) => { Err(err) => {
// not a proper autocrypt message, abort and ignore // not a proper autocrypt message, abort and ignore
warn!(context, "Invalid autocrypt message: {:?}", err);
return Ok(None); return Ok(None);
} }
Ok(res) => res, Ok(res) => res,
@@ -283,12 +274,13 @@ fn decrypt_if_autocrypt_message<'a>(
/// Returns Ok(None) if nothing encrypted was found. /// Returns Ok(None) if nothing encrypted was found.
fn decrypt_part( fn decrypt_part(
_context: &Context, context: &Context,
mail: &mailparse::ParsedMail<'_>, mail: &mailparse::ParsedMail<'_>,
private_keyring: &Keyring, private_keyring: &Keyring,
public_keyring_for_validate: &Keyring, public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>, ret_valid_signatures: &mut HashSet<String>,
) -> Result<Option<Vec<u8>>> { ) -> Result<Option<Vec<u8>>> {
info!(context, "decrypting part");
let data = mail.get_body_raw()?; let data = mail.get_body_raw()?;
if has_decrypted_pgp_armor(&data) { if has_decrypted_pgp_armor(&data) {

View File

@@ -1137,7 +1137,7 @@ pub fn mdn_from_ext(
return None; return None;
} }
if let Ok((msg_id, chat_id, chat_type, msg_state)) = context.sql.query_row( let res = context.sql.query_row(
concat!( concat!(
"SELECT", "SELECT",
" m.id AS msg_id,", " m.id AS msg_id,",
@@ -1157,7 +1157,12 @@ pub fn mdn_from_ext(
row.get::<_, MessageState>("state")?, row.get::<_, MessageState>("state")?,
)) ))
}, },
) { );
if let Err(ref err) = res {
info!(context, "Failed to select MDN {:?}", err);
}
if let Ok((msg_id, chat_id, chat_type, msg_state)) = res {
let mut read_by_all = false; let mut read_by_all = false;
// if already marked as MDNS_RCVD msgstate_can_fail() returns false. // if already marked as MDNS_RCVD msgstate_can_fail() returns false.

View File

@@ -1,5 +1,5 @@
use chrono::TimeZone; use chrono::TimeZone;
use lettre_email::{Address, Header, MimeMessage, MimeMultipartType, PartBuilder}; use lettre_email::{Address, Header, MimeMultipartType, PartBuilder};
use crate::chat::{self, Chat}; use crate::chat::{self, Chat};
use crate::config::Config; use crate::config::Config;
@@ -229,11 +229,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.collect()) .collect())
} }
fn is_e2ee_guranteed(&self) -> Result<bool, Error> { fn is_e2ee_guranteed(&self) -> bool {
match self.loaded { match self.loaded {
Loaded::Message => { Loaded::Message => {
if self.chat.as_ref().unwrap().typ == Chattype::VerifiedGroup { if self.chat.as_ref().unwrap().typ == Chattype::VerifiedGroup {
return Ok(true); return true;
} }
let force_plaintext = self let force_plaintext = self
@@ -243,78 +243,76 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.unwrap_or_default(); .unwrap_or_default();
if force_plaintext == 0 { if force_plaintext == 0 {
return Ok(self return self
.msg .msg
.param .param
.get_int(Param::GuaranteeE2ee) .get_int(Param::GuaranteeE2ee)
.unwrap_or_default() .unwrap_or_default()
!= 0); != 0;
} }
Ok(false) false
} }
Loaded::MDN => Ok(false), Loaded::MDN => false,
} }
} }
fn min_verified(&self) -> Result<PeerstateVerifiedStatus, Error> { fn min_verified(&self) -> PeerstateVerifiedStatus {
match self.loaded { match self.loaded {
Loaded::Message => { Loaded::Message => {
let chat = self.chat.as_ref().unwrap(); let chat = self.chat.as_ref().unwrap();
if chat.typ == Chattype::VerifiedGroup { if chat.typ == Chattype::VerifiedGroup {
Ok(PeerstateVerifiedStatus::BidirectVerified) PeerstateVerifiedStatus::BidirectVerified
} else { } else {
Ok(PeerstateVerifiedStatus::Unverified) PeerstateVerifiedStatus::Unverified
} }
} }
Loaded::MDN => Ok(PeerstateVerifiedStatus::Unverified), Loaded::MDN => PeerstateVerifiedStatus::Unverified,
} }
} }
fn should_force_plaintext(&self) -> Result<i32, Error> { fn should_force_plaintext(&self) -> i32 {
match self.loaded { match self.loaded {
Loaded::Message => { Loaded::Message => {
let chat = self.chat.as_ref().unwrap(); let chat = self.chat.as_ref().unwrap();
if chat.typ == Chattype::VerifiedGroup { if chat.typ == Chattype::VerifiedGroup {
Ok(0) 0
} else { } else {
Ok(self self.msg
.msg
.param .param
.get_int(Param::ForcePlaintext) .get_int(Param::ForcePlaintext)
.unwrap_or_default()) .unwrap_or_default()
} }
} }
Loaded::MDN => Ok(DC_FP_NO_AUTOCRYPT_HEADER), Loaded::MDN => DC_FP_NO_AUTOCRYPT_HEADER,
} }
} }
fn should_do_gossip(&self) -> Result<bool, Error> { fn should_do_gossip(&self) -> bool {
match self.loaded { match self.loaded {
Loaded::Message => { Loaded::Message => {
let chat = self.chat.as_ref().unwrap(); let chat = self.chat.as_ref().unwrap();
// beside key- and member-changes, force re-gossip every 48 hours // beside key- and member-changes, force re-gossip every 48 hours
if chat.gossiped_timestamp == 0 if chat.gossiped_timestamp == 0
|| (chat.gossiped_timestamp + (2 * 24 * 60 * 60)) < time() || (chat.gossiped_timestamp + (2 * 24 * 60 * 60)) > time()
{ {
return Ok(true); return true;
} }
let cmd = self.msg.param.get_cmd(); match self.msg.param.get_cmd() {
match cmd {
SystemMessage::MemberAddedToGroup => { SystemMessage::MemberAddedToGroup => {
return Ok(true); return true;
} }
_ => {} _ => {}
} }
Ok(false) false
} }
Loaded::MDN => Ok(false), Loaded::MDN => false,
} }
} }
fn grpimage(&self) -> Result<Option<String>, Error> { fn grpimage(&self) -> Option<String> {
match self.loaded { match self.loaded {
Loaded::Message => { Loaded::Message => {
let chat = self.chat.as_ref().unwrap(); let chat = self.chat.as_ref().unwrap();
@@ -322,21 +320,21 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
match cmd { match cmd {
SystemMessage::MemberAddedToGroup => { SystemMessage::MemberAddedToGroup => {
return Ok(chat.param.get(Param::ProfileImage).map(Into::into)); return chat.param.get(Param::ProfileImage).map(Into::into);
} }
SystemMessage::GroupImageChanged => { SystemMessage::GroupImageChanged => {
return Ok(self.msg.param.get(Param::Arg).map(Into::into)) return self.msg.param.get(Param::Arg).map(Into::into)
} }
_ => {} _ => {}
} }
Ok(None) None
} }
Loaded::MDN => Ok(None), Loaded::MDN => None,
} }
} }
fn subject_str(&self) -> Result<String, Error> { fn subject_str(&self) -> String {
match self.loaded { match self.loaded {
Loaded::Message => { Loaded::Message => {
match self.chat { match self.chat {
@@ -354,32 +352,27 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage { if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
// do not add the "Chat:" prefix for setup messages // do not add the "Chat:" prefix for setup messages
Ok(self self.context
.context
.stock_str(StockMessage::AcSetupMsgSubject) .stock_str(StockMessage::AcSetupMsgSubject)
.into_owned()) .into_owned()
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup
{ {
Ok(format!("Chat: {}: {}{}", chat.name, fwd, raw_subject,)) format!("Chat: {}: {}{}", chat.name, fwd, raw_subject)
} else { } else {
Ok(format!("Chat: {}{}", fwd, raw_subject)) format!("Chat: {}{}", fwd, raw_subject)
} }
} }
None => Ok(String::default()), None => String::new(),
} }
} }
Loaded::MDN => { Loaded::MDN => {
let e = self.context.stock_str(StockMessage::ReadRcpt); let e = self.context.stock_str(StockMessage::ReadRcpt);
Ok(format!("Chat: {}", e).to_string()) format!("Chat: {}", e)
} }
} }
} }
pub fn render(mut self) -> Result<RenderedEmail, Error> { pub fn render(mut self) -> Result<RenderedEmail, Error> {
let e2ee_guranteed = self.is_e2ee_guranteed()?;
let mut encrypt_helper = EncryptHelper::new(self.context)?;
// Headers that are encrypted // Headers that are encrypted
// - Chat-*, except Chat-Version // - Chat-*, except Chat-Version
// - Secure-Join* // - Secure-Join*
@@ -450,13 +443,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
)); ));
} }
// 1=add Autocrypt-header (needed eg. for handshaking), 2=no Autocrypte-header (used for MDN) let min_verified = self.min_verified();
let do_gossip = self.should_do_gossip();
let min_verified = self.min_verified()?; let grpimage = self.grpimage();
let do_gossip = self.should_do_gossip()?; let force_plaintext = self.should_force_plaintext();
let grpimage = self.grpimage()?; let subject_str = self.subject_str();
let force_plaintext = self.should_force_plaintext()?; let e2ee_guranteed = self.is_e2ee_guranteed();
let subject_str = self.subject_str()?; let mut encrypt_helper = EncryptHelper::new(self.context)?;
let subject = dc_encode_header_words(subject_str); let subject = dc_encode_header_words(subject_str);
@@ -480,13 +473,34 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
encrypt_helper.should_encrypt(self.context, e2ee_guranteed, &peerstates)?; encrypt_helper.should_encrypt(self.context, e2ee_guranteed, &peerstates)?;
let is_encrypted = should_encrypt && force_plaintext == 0; let is_encrypted = should_encrypt && force_plaintext == 0;
let mut outer_message = if is_encrypted { // Add gossip headers
info!(self.context, "gossip: {:?}", do_gossip);
if do_gossip {
info!(self.context, "Gossip headers: {:?}", &peerstates);
for peerstate in peerstates.iter().filter_map(|(state, _)| state.as_ref()) {
if peerstate.peek_key(min_verified).is_some() {
if let Some(header) = peerstate.render_gossip_header(min_verified) {
protected_headers.push(Header::new("Autocrypt-Gossip".into(), header));
}
}
}
}
let rfc724_mid = match self.loaded {
Loaded::Message => self.msg.rfc724_mid.clone(),
Loaded::MDN => dc_create_outgoing_rfc724_mid(None, &self.from_addr),
};
protected_headers.push(Header::new("Message-ID".into(), rfc724_mid.clone()));
unprotected_headers.push(Header::new_with_value("To".into(), to).unwrap());
unprotected_headers.push(Header::new_with_value("From".into(), vec![from]).unwrap());
let outer_message = if is_encrypted {
for header in protected_headers.into_iter() { for header in protected_headers.into_iter() {
message = message.header(header); message = message.header(header);
} }
// Manual Content-Type only works with https://github.com/niax/rust-email/pull/51
// At the moment the boundary will not be inserted.
let mut outer_message = PartBuilder::new().header(( let mut outer_message = PartBuilder::new().header((
"Content-Type".to_string(), "Content-Type".to_string(),
"multipart/encrypted; protocol=\"application/pgp-encrypted\"".to_string(), "multipart/encrypted; protocol=\"application/pgp-encrypted\"".to_string(),
@@ -496,13 +510,8 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
outer_message = outer_message.header(header); outer_message = outer_message.header(header);
} }
let encrypted = encrypt_helper.encrypt( let encrypted =
self.context, encrypt_helper.encrypt(self.context, min_verified, message, &peerstates)?;
min_verified,
do_gossip,
message,
&peerstates,
)?;
outer_message = outer_message outer_message = outer_message
.child( .child(
@@ -540,10 +549,6 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
message message
}; };
outer_message = outer_message
.header(Header::new_with_value("To".into(), to).unwrap())
.header(Header::new_with_value("From".into(), vec![from]).unwrap());
let is_gossiped = is_encrypted && do_gossip && !peerstates.is_empty(); let is_gossiped = is_encrypted && do_gossip && !peerstates.is_empty();
let MimeFactory { let MimeFactory {
@@ -555,11 +560,6 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.. ..
} = self; } = self;
let rfc724_mid = match loaded {
Loaded::Message => msg.rfc724_mid.clone(),
Loaded::MDN => dc_create_outgoing_rfc724_mid(None, &from_addr),
};
Ok(RenderedEmail { Ok(RenderedEmail {
message: outer_message.build().as_string().into_bytes(), message: outer_message.build().as_string().into_bytes(),
// envelope: Envelope::new, // envelope: Envelope::new,
@@ -766,10 +766,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
); );
// Message is sent as text/plain, with charset = utf-8 // Message is sent as text/plain, with charset = utf-8
let mut message = lettre_email::PartBuilder::new() let mut parts = vec![PartBuilder::new()
.content_type(&mime::TEXT_PLAIN_UTF_8) .content_type(&mime::TEXT_PLAIN_UTF_8)
.body(message_text); .body(message_text)];
let mut is_multipart = false;
// add attachment part // add attachment part
if chat::msgtype_has_file(self.msg.type_0) { if chat::msgtype_has_file(self.msg.type_0) {
@@ -780,14 +779,12 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
); );
} else { } else {
let (file_part, _) = build_body_file(context, &self.msg, "")?; let (file_part, _) = build_body_file(context, &self.msg, "")?;
message = message.child(file_part); parts.push(file_part);
is_multipart = true;
} }
} }
if let Some(meta_part) = meta_part { if let Some(meta_part) = meta_part {
message = message.child(meta_part); parts.push(meta_part);
is_multipart = true;
} }
if self.msg.param.exists(Param::SetLatitude) { if self.msg.param.exists(Param::SetLatitude) {
@@ -797,8 +794,8 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
param.get_float(Param::SetLatitude).unwrap_or_default(), param.get_float(Param::SetLatitude).unwrap_or_default(),
param.get_float(Param::SetLongitude).unwrap_or_default(), param.get_float(Param::SetLongitude).unwrap_or_default(),
); );
message = message.child( parts.push(
lettre_email::PartBuilder::new() PartBuilder::new()
.content_type( .content_type(
&"application/vnd.google-earth.kml+xml" &"application/vnd.google-earth.kml+xml"
.parse::<mime::Mime>() .parse::<mime::Mime>()
@@ -808,17 +805,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
"Content-Disposition", "Content-Disposition",
"attachment; filename=\"message.kml\"", "attachment; filename=\"message.kml\"",
)) ))
.body(kml_file) .body(kml_file),
.build(),
); );
is_multipart = true;
} }
if location::is_sending_locations_to_chat(context, self.msg.chat_id) { if location::is_sending_locations_to_chat(context, self.msg.chat_id) {
match location::get_kml(context, self.msg.chat_id) { match location::get_kml(context, self.msg.chat_id) {
Ok((kml_content, last_added_location_id)) => { Ok((kml_content, last_added_location_id)) => {
message = message.child( parts.push(
lettre_email::PartBuilder::new() PartBuilder::new()
.content_type( .content_type(
&"application/vnd.google-earth.kml+xml" &"application/vnd.google-earth.kml+xml"
.parse::<mime::Mime>() .parse::<mime::Mime>()
@@ -828,10 +823,8 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
"Content-Disposition", "Content-Disposition",
"attachment; filename=\"message.kml\"", "attachment; filename=\"message.kml\"",
)) ))
.body(kml_content) .body(kml_content),
.build(),
); );
is_multipart = true;
if !self.msg.param.exists(Param::SetLatitude) { if !self.msg.param.exists(Param::SetLatitude) {
// otherwise, the independent location is already filed // otherwise, the independent location is already filed
self.last_added_location_id = last_added_location_id; self.last_added_location_id = last_added_location_id;
@@ -843,8 +836,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
} }
} }
if is_multipart { // Single part, render as regular message.
message = message.message_type(MimeMultipartType::Mixed); if parts.len() == 1 {
return Ok(parts.pop().unwrap());
}
// Multiple parts, render as multipart.
let mut message = PartBuilder::new().message_type(MimeMultipartType::Mixed);
for part in parts.into_iter() {
message = message.child(part.build());
} }
Ok(message) Ok(message)
@@ -920,7 +920,7 @@ fn build_body_file(
context: &Context, context: &Context,
msg: &Message, msg: &Message,
base_name: &str, base_name: &str,
) -> Result<(MimeMessage, String), Error> { ) -> Result<(PartBuilder, String), Error> {
let blob = msg let blob = msg
.param .param
.get_blob(Param::File, context, true)? .get_blob(Param::File, context, true)?
@@ -984,8 +984,7 @@ fn build_body_file(
.content_type(&mimetype) .content_type(&mimetype)
.header(("Content-Disposition", cd_value)) .header(("Content-Disposition", cd_value))
.header(("Content-Transfer-Encoding", "base64")) .header(("Content-Transfer-Encoding", "base64"))
.body(encoded_body) .body(encoded_body);
.build();
Ok((mail, filename_to_send)) Ok((mail, filename_to_send))
} }

View File

@@ -38,8 +38,8 @@ pub struct MimeParser<'a> {
pub location_kml: Option<location::Kml>, pub location_kml: Option<location::Kml>,
pub message_kml: Option<location::Kml>, pub message_kml: Option<location::Kml>,
reports: Vec<Report>, reports: Vec<Report>,
parsed_header_protected: bool,
mdns_enabled: bool, mdns_enabled: bool,
parsed_protected_headers: bool,
} }
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
@@ -62,20 +62,30 @@ impl Default for SystemMessage {
} }
} }
const DC_MIMETYPE_MP_ALTERNATIVE: i32 = 10; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
const DC_MIMETYPE_MP_RELATED: i32 = 20; pub enum DcMimeType {
const DC_MIMETYPE_MP_MIXED: i32 = 30; Unknown,
const DC_MIMETYPE_MP_NOT_DECRYPTABLE: i32 = 40; MpAlternative,
const DC_MIMETYPE_MP_REPORT: i32 = 45; MpRelated,
const DC_MIMETYPE_MP_SIGNED: i32 = 46; MpMixed,
const DC_MIMETYPE_MP_OTHER: i32 = 50; MpNotDecryptable,
const DC_MIMETYPE_TEXT_PLAIN: i32 = 60; MpReport,
const DC_MIMETYPE_TEXT_HTML: i32 = 70; MpSigned,
const DC_MIMETYPE_IMAGE: i32 = 80; MpOther,
const DC_MIMETYPE_AUDIO: i32 = 90; TextPlain,
const DC_MIMETYPE_VIDEO: i32 = 100; TextHtml,
const DC_MIMETYPE_FILE: i32 = 110; Image,
const DC_MIMETYPE_AC_SETUP_FILE: i32 = 111; 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> {
@@ -85,7 +95,6 @@ impl<'a> MimeParser<'a> {
let mut parser = MimeParser { let mut parser = MimeParser {
parts: Vec::new(), parts: Vec::new(),
header: Default::default(), header: Default::default(),
parsed_header_protected: false,
subject: None, subject: None,
is_send_by_messenger: false, is_send_by_messenger: false,
decrypting_failed: false, decrypting_failed: false,
@@ -99,11 +108,22 @@ impl<'a> MimeParser<'a> {
location_kml: None, location_kml: None,
message_kml: None, message_kml: None,
mdns_enabled, mdns_enabled,
parsed_protected_headers: false,
}; };
let message_time = mail
.headers
.get_first_value("Date")?
.and_then(|v| mailparse::dateparse(&v).ok())
.unwrap_or_default();
parser.hash_header(&mail.headers);
// Memory location for a possible decrypted message.
let mail_raw; let mail_raw;
let mail = match e2ee::try_decrypt(parser.context, &mail) {
Ok((raw, signatures, message_time)) => { let mail = match e2ee::try_decrypt(parser.context, &mail, message_time) {
Ok((raw, signatures)) => {
// Valid autocrypt message, encrypted // Valid autocrypt message, encrypted
parser.encrypted = raw.is_some(); parser.encrypted = raw.is_some();
parser.signatures = signatures; parser.signatures = signatures;
@@ -111,16 +131,7 @@ impl<'a> MimeParser<'a> {
if let Some(raw) = raw { if let Some(raw) = raw {
mail_raw = raw; mail_raw = raw;
let decrypted_mail = mailparse::parse_mail(&mail_raw)?; let decrypted_mail = mailparse::parse_mail(&mail_raw)?;
// Decrypted the mail
// we have a decrypted mail, that is valid, check for gossip headers
let gossip_headers =
decrypted_mail.headers.get_all_values("Autocrypt-Gossip")?;
if !gossip_headers.is_empty() {
parser.gossipped_addr =
update_gossip_peerstates(context, message_time, &mail, gossip_headers)?;
}
decrypted_mail decrypted_mail
} else { } else {
// Message was not encrypted // Message was not encrypted
@@ -140,7 +151,11 @@ impl<'a> MimeParser<'a> {
} }
}; };
parser.hash_header(&mail.headers); // Handle gossip headers
let gossip_headers = mail.headers.get_all_values("Autocrypt-Gossip")?;
parser.gossipped_addr =
update_gossip_peerstates(context, message_time, &mail, gossip_headers)?;
parser.parse_mime_recursive(&mail)?; parser.parse_mime_recursive(&mail)?;
parser.parse_headers()?; parser.parse_headers()?;
@@ -160,7 +175,7 @@ impl<'a> MimeParser<'a> {
let has_setup_file = self let has_setup_file = self
.parts .parts
.iter() .iter()
.any(|p| p.mimetype == DC_MIMETYPE_AC_SETUP_FILE); .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;
@@ -178,7 +193,7 @@ 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 != 111 { if self.parts[i].mimetype != DcMimeType::AcSetupFile {
self.parts.remove(i); self.parts.remove(i);
} else { } else {
i += 1; i += 1;
@@ -201,6 +216,9 @@ impl<'a> MimeParser<'a> {
} }
} }
} }
info!(self.context, "checking message parts: {:?}", &self.parts);
if self.is_send_by_messenger && self.parts.len() == 2 { if self.is_send_by_messenger && self.parts.len() == 2 {
let need_drop = { let need_drop = {
let textpart = &self.parts[0]; let textpart = &self.parts[0];
@@ -302,6 +320,7 @@ impl<'a> MimeParser<'a> {
if let Some(dn_field) = self.lookup_field("Chat-Disposition-Notification-To") { if let Some(dn_field) = self.lookup_field("Chat-Disposition-Notification-To") {
if self.get_last_nonmeta().is_some() { if self.get_last_nonmeta().is_some() {
let addrs = mailparse::addrparse(&dn_field).unwrap(); let addrs = mailparse::addrparse(&dn_field).unwrap();
info!(self.context, "last non meta addrs: {:?}", &addrs);
if let Some(dn_to_addr) = addrs.first() { if let Some(dn_to_addr) = addrs.first() {
if let Some(from_field) = self.lookup_field("From") { if let Some(from_field) = self.lookup_field("From") {
@@ -352,6 +371,7 @@ impl<'a> MimeParser<'a> {
} }
fn parse_mime_recursive(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> { fn parse_mime_recursive(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> {
info!(self.context, "parse mime_recursive {:?}", mail.ctype);
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" {
info!( info!(
@@ -361,21 +381,12 @@ impl<'a> MimeParser<'a> {
return Ok(false); return Ok(false);
} }
if !self.parsed_header_protected { if !self.parsed_protected_headers {
// use the most outer protected header - this is typically
// created in sync with the normal, unprotected header
self.parsed_header_protected = true;
self.hash_header(&mail.headers); self.hash_header(&mail.headers);
} else { self.parsed_protected_headers;
info!(
self.context,
"Protected headers found in MIME header: Will be ignored as we already found an outer one."
);
} }
} }
// multiple = multipart/ or message/
enum MimeS { enum MimeS {
Multiple, Multiple,
Single, Single,
@@ -422,9 +433,9 @@ impl<'a> MimeParser<'a> {
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") */
(DC_MIMETYPE_MP_ALTERNATIVE, _) => { (DcMimeType::MpAlternative, _) => {
for cur_data in &mail.subparts { for cur_data in &mail.subparts {
if mailmime_get_mime_type(cur_data).0 == DC_MIMETYPE_MP_MIXED { if mailmime_get_mime_type(cur_data).0 == DcMimeType::MpMixed {
any_part_added = self.parse_mime_recursive(cur_data)?; any_part_added = self.parse_mime_recursive(cur_data)?;
break; break;
} }
@@ -432,7 +443,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 == DC_MIMETYPE_TEXT_PLAIN { if mailmime_get_mime_type(cur_data).0 == DcMimeType::TextPlain {
any_part_added = self.parse_mime_recursive(cur_data)?; any_part_added = self.parse_mime_recursive(cur_data)?;
break; break;
} }
@@ -448,7 +459,7 @@ impl<'a> MimeParser<'a> {
} }
} }
} }
(DC_MIMETYPE_MP_RELATED, _) => { (DcMimeType::MpRelated, _) => {
/* 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 ...
@@ -457,7 +468,7 @@ impl<'a> MimeParser<'a> {
any_part_added = self.parse_mime_recursive(first)?; any_part_added = self.parse_mime_recursive(first)?;
} }
} }
(DC_MIMETYPE_MP_NOT_DECRYPTABLE, _) => { (DcMimeType::MpNotDecryptable, _) => {
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 msg_body = self.context.stock_str(StockMessage::CantDecryptMsgBody);
@@ -470,7 +481,7 @@ impl<'a> MimeParser<'a> {
any_part_added = true; any_part_added = true;
self.decrypting_failed = true; self.decrypting_failed = true;
} }
(DC_MIMETYPE_MP_SIGNED, _) => { (DcMimeType::MpSigned, _) => {
/* 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 [...]
@@ -483,12 +494,14 @@ impl<'a> MimeParser<'a> {
any_part_added = self.parse_mime_recursive(first)?; any_part_added = self.parse_mime_recursive(first)?;
} }
} }
(DC_MIMETYPE_MP_REPORT, _) => { (DcMimeType::MpReport, _) => {
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 {
info!(self.context, "report: {:?}", &mail.ctype); info!(self.context, "report: {:?}", &mail.ctype);
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" {
info!(self.context, "processing report");
if let Some(report) = self.process_report(mail)? { if let Some(report) = self.process_report(mail)? {
self.reports.push(report); self.reports.push(report);
} }
@@ -503,12 +516,12 @@ impl<'a> MimeParser<'a> {
} }
} }
_ => { _ => {
/* eg. DC_MIMETYPE_MP_MIXED - add all parts (in fact, /* eg. DcMimeType::MpMixed - 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;
@@ -518,10 +531,10 @@ impl<'a> MimeParser<'a> {
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 mailmime_get_mime_type(cur_data) {
(DC_MIMETYPE_TEXT_PLAIN, _) => { (DcMimeType::TextPlain, _) => {
plain_cnt += 1; plain_cnt += 1;
} }
(DC_MIMETYPE_TEXT_HTML, _) => { (DcMimeType::TextHtml, _) => {
html_part = i as isize; html_part = i as isize;
html_cnt += 1; html_cnt += 1;
} }
@@ -531,7 +544,7 @@ impl<'a> MimeParser<'a> {
if plain_cnt == 1 && html_cnt == 1 { if plain_cnt == 1 && html_cnt == 1 {
warn!( warn!(
self.context, self.context,
"HACK: multipart/mixed message found with PLAIN and HTML, we\'ll skip the HTML part as this seems to be unwanted." "HACK: multipart/mixed message found with Plain and HTML, we\'ll skip the HTML part as this seems to be unwanted."
); );
skip_part = html_part; skip_part = html_part;
} }
@@ -554,16 +567,16 @@ impl<'a> MimeParser<'a> {
let (mime_type, msg_type) = mailmime_get_mime_type(mail); let (mime_type, msg_type) = mailmime_get_mime_type(mail);
let raw_mime = mail.ctype.mimetype.to_lowercase(); let raw_mime = mail.ctype.mimetype.to_lowercase();
if !raw_mime.starts_with("text") { info!(
// MAILMIME_DATA_FILE indicates, the data is in a file; AFAIK this is not used on parsing self.context,
return Ok(false); "got mime type: {:?} ({})", mime_type, raw_mime
} );
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 {
DC_MIMETYPE_TEXT_PLAIN | DC_MIMETYPE_TEXT_HTML => { DcMimeType::TextPlain | DcMimeType::TextHtml => {
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) => {
@@ -580,7 +593,7 @@ 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 == DC_MIMETYPE_TEXT_HTML; let is_html = mime_type == DcMimeType::TextHtml;
simplifier.simplify(&decoded_data, is_html, is_msgrmsg) simplifier.simplify(&decoded_data, is_html, is_msgrmsg)
}; };
@@ -597,11 +610,11 @@ impl<'a> MimeParser<'a> {
self.is_forwarded = true; self.is_forwarded = true;
} }
} }
DC_MIMETYPE_IMAGE DcMimeType::Image
| DC_MIMETYPE_AUDIO | DcMimeType::Audio
| DC_MIMETYPE_VIDEO | DcMimeType::Video
| DC_MIMETYPE_FILE | DcMimeType::File
| DC_MIMETYPE_AC_SETUP_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*=...`
@@ -650,7 +663,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: libc::c_int, mime_type: DcMimeType,
raw_mime: &String, raw_mime: &String,
decoded_data: &[u8], decoded_data: &[u8],
filename: &str, filename: &str,
@@ -698,7 +711,7 @@ impl<'a> MimeParser<'a> {
part.param.set(Param::File, blob.as_name()); part.param.set(Param::File, blob.as_name());
part.param.set(Param::MimeType, raw_mime); part.param.set(Param::MimeType, raw_mime);
if mime_type == DC_MIMETYPE_IMAGE { 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);
@@ -809,6 +822,7 @@ impl<'a> MimeParser<'a> {
.flatten() .flatten()
.and_then(|v| parse_message_id(&v)) .and_then(|v| parse_message_id(&v))
{ {
info!(self.context, "got report {:?}", original_message_id);
return Ok(Some(Report { return Ok(Some(Report {
original_message_id, original_message_id,
})); }));
@@ -827,6 +841,7 @@ impl<'a> MimeParser<'a> {
server_folder: impl AsRef<str>, server_folder: impl AsRef<str>,
server_uid: u32, server_uid: u32,
) { ) {
info!(self.context, "processing reports {:?}", &self.reports);
for report in &self.reports { for report in &self.reports {
let mut mdn_consumed = false; let mut mdn_consumed = false;
@@ -836,6 +851,10 @@ impl<'a> MimeParser<'a> {
&report.original_message_id, &report.original_message_id,
sent_timestamp, sent_timestamp,
) { ) {
info!(
self.context,
"found message for report {}", report.original_message_id
);
rr_event_to_send.push((chat_id, msg_id)); rr_event_to_send.push((chat_id, msg_id));
mdn_consumed = true; mdn_consumed = true;
} }
@@ -863,16 +882,29 @@ fn update_gossip_peerstates(
let mut recipients: Option<HashSet<String>> = None; let mut recipients: Option<HashSet<String>> = None;
let mut gossipped_addr: HashSet<String> = Default::default(); let mut gossipped_addr: HashSet<String> = Default::default();
info!(
context,
"Updating gossip peerstates: {:#?}", &gossip_headers
);
for value in &gossip_headers { for value in &gossip_headers {
let gossip_header = value.parse::<Aheader>(); let gossip_header = value.parse::<Aheader>();
info!(context, "got gossip header: {:?}", gossip_header);
if let Ok(ref header) = gossip_header { if let Ok(ref header) = gossip_header {
if recipients.is_none() { if recipients.is_none() {
recipients = Some(get_recipients(mail.headers.iter().map(|v| { recipients = Some(get_recipients(mail.headers.iter().filter_map(|v| {
// TODO: error handling let key = v.get_key();
(v.get_key().unwrap(), v.get_value().unwrap()) let value = v.get_value();
if key.is_err() || value.is_err() {
return None;
}
Some((v.get_key().unwrap(), v.get_value().unwrap()))
}))); })));
} }
info!(context, "got recipients {:?}", recipients);
info!(context, "looking for addr {:?}", &header.addr);
if recipients.as_ref().unwrap().contains(&header.addr) { if recipients.as_ref().unwrap().contains(&header.addr) {
let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr); let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr);
if let Some(ref mut peerstate) = peerstate { if let Some(ref mut peerstate) = peerstate {
@@ -929,15 +961,15 @@ fn is_known(key: &str) -> bool {
pub struct Part { pub struct Part {
pub typ: Viewtype, pub typ: Viewtype,
pub is_meta: bool, pub is_meta: bool,
pub mimetype: i32, pub mimetype: DcMimeType,
pub msg: Option<String>, pub msg: Option<String>,
pub msg_raw: Option<String>, pub msg_raw: Option<String>,
pub bytes: i32, pub bytes: i32,
pub param: Params, pub param: Params,
} }
fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (libc::c_int, Viewtype) { fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (DcMimeType, Viewtype) {
let unknown_type = (0, Viewtype::Unknown); let unknown_type = (DcMimeType::Unknown, Viewtype::Unknown);
let mimetype = mail.ctype.mimetype.to_lowercase(); let mimetype = mail.ctype.mimetype.to_lowercase();
let mut parts = mimetype.split('/'); let mut parts = mimetype.split('/');
@@ -948,41 +980,41 @@ fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (libc::c_int, Vie
"text" => { "text" => {
if !mailmime_is_attachment_disposition(mail) { if !mailmime_is_attachment_disposition(mail) {
if subtype == "plain" { if subtype == "plain" {
return (DC_MIMETYPE_TEXT_PLAIN, Viewtype::Text); return (DcMimeType::TextPlain, Viewtype::Text);
} }
if subtype == "html" { if subtype == "html" {
return (DC_MIMETYPE_TEXT_HTML, Viewtype::Text); return (DcMimeType::TextHtml, Viewtype::Text);
} }
} }
(DC_MIMETYPE_FILE, Viewtype::File) (DcMimeType::File, Viewtype::File)
} }
"image" => { "image" => {
let msg_type = match subtype { let msg_type = match subtype {
"gif" => Viewtype::Gif, "gif" => Viewtype::Gif,
"svg+xml" => { "svg+xml" => {
return (DC_MIMETYPE_FILE, Viewtype::File); return (DcMimeType::File, Viewtype::File);
} }
_ => Viewtype::Image, _ => Viewtype::Image,
}; };
(DC_MIMETYPE_IMAGE, msg_type) (DcMimeType::Image, msg_type)
} }
"audio" => (DC_MIMETYPE_AUDIO, Viewtype::Audio), "audio" => (DcMimeType::Audio, Viewtype::Audio),
"video" => (DC_MIMETYPE_VIDEO, Viewtype::Video), "video" => (DcMimeType::Video, Viewtype::Video),
"multipart" => { "multipart" => {
let mime_type = match subtype { let mime_type = match subtype {
"alternative" => DC_MIMETYPE_MP_ALTERNATIVE, "alternative" => DcMimeType::MpAlternative,
"related" => DC_MIMETYPE_MP_RELATED, "related" => DcMimeType::MpRelated,
"encrypted" => { "encrypted" => {
// maybe try_decrypt failed to decrypt // maybe try_decrypt failed to decrypt
// or it wasn't in proper Autocrypt format // or it wasn't in proper Autocrypt format
DC_MIMETYPE_MP_NOT_DECRYPTABLE DcMimeType::MpNotDecryptable
} }
"signed" => DC_MIMETYPE_MP_SIGNED, "signed" => DcMimeType::MpSigned,
"mixed" => DC_MIMETYPE_MP_MIXED, "mixed" => DcMimeType::MpMixed,
"report" => DC_MIMETYPE_MP_REPORT, "report" => DcMimeType::MpReport,
_ => DC_MIMETYPE_MP_OTHER, _ => DcMimeType::MpOther,
}; };
(mime_type, Viewtype::Unknown) (mime_type, Viewtype::Unknown)
@@ -993,16 +1025,16 @@ fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (libc::c_int, Vie
// be handled separatedly. // be handled separatedly.
// I've not seen any messages using this, so we do not attach these parts (maybe they're used to attach replies, // I've not seen any messages using this, so we do not attach these parts (maybe they're used to attach replies,
// which are unwanted at all). // which are unwanted at all).
// For now, we skip these parts at all; if desired, we could return DC_MIMETYPE_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 unknown_type
} }
"application" => { "application" => {
if subtype == "autocrypt-setup" { if subtype == "autocrypt-setup" {
return (DC_MIMETYPE_AC_SETUP_FILE, Viewtype::File); return (DcMimeType::AcSetupFile, Viewtype::File);
} }
(DC_MIMETYPE_FILE, Viewtype::File) (DcMimeType::File, Viewtype::File)
} }
_ => unknown_type, _ => unknown_type,
} }

View File

@@ -18,7 +18,8 @@ pub fn parse_message_id(message_id: &[u8]) -> Result<String, Error> {
pub fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail<'b>, Error> { pub fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail<'b>, Error> {
ensure!( ensure!(
mail.ctype.mimetype == "multipart/encrypted", mail.ctype.mimetype == "multipart/encrypted",
"Not a multipart/encrypted message" "Not a multipart/encrypted message: {}",
mail.ctype.mimetype
); );
ensure!( ensure!(
mail.subparts.len() == 2, mail.subparts.len() == 2,
@@ -27,12 +28,14 @@ pub fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a Parsed
ensure!( ensure!(
mail.subparts[0].ctype.mimetype == "application/pgp-encrypted", mail.subparts[0].ctype.mimetype == "application/pgp-encrypted",
"Invalid Autocrypt Level 1 version part" "Invalid Autocrypt Level 1 version part: {:?}",
mail.subparts[0].ctype,
); );
ensure!( ensure!(
mail.subparts[1].ctype.mimetype == "application/octetstream", mail.subparts[1].ctype.mimetype == "application/octet-stream",
"Invalid Autocrypt Level 1 encrypted part" "Invalid Autocrypt Level 1 encrypted part: {:?}",
mail.subparts[1].ctype
); );
Ok(&mail.subparts[1]) Ok(&mail.subparts[1])