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::contact::*;
use crate::context::Context;
use crate::key::*;
/// Possible values for encryption preference
@@ -65,16 +66,27 @@ impl Aheader {
}
}
pub fn from_imffields(
pub fn from_headers(
context: &Context,
wanted_from: &str,
headers: &[mailparse::MailHeader<'_>],
) -> Option<Self> {
use mailparse::MailHeaderMap;
if let Ok(Some(value)) = headers.get_first_value("Autocrypt") {
if let Ok(test) = Self::from_str(&value) {
if addr_cmp(&test.addr, wanted_from) {
return Some(test);
match Self::from_str(&value) {
Ok(header) => {
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,
context: &Context,
min_verified: PeerstateVerifiedStatus,
do_gossip: bool,
mut mail_to_encrypt: lettre_email::PartBuilder,
mail_to_encrypt: lettre_email::PartBuilder,
peerstates: &[(Option<Peerstate>, &str)],
) -> Result<String> {
let mut keyring = Keyring::default();
let mut gossip_headers: Vec<String> = Vec::with_capacity(peerstates.len());
for (peerstate, addr) in peerstates
.iter()
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
{
if let Some(key) = peerstate.peek_key(min_verified) {
keyring.add_owned(key.clone());
if do_gossip {
if let Some(header) = peerstate.render_gossip_header(min_verified) {
gossip_headers.push(header.to_string());
}
}
} else {
bail!("proper enc-key for {} missing, cannot encrypt", addr);
}
info!(context, "adding for {}: {:?}", addr, peerstate);
let key = peerstate.peek_key(min_verified).ok_or_else(|| {
format_err!("proper enc-key for {} missing, cannot encrypt", addr)
})?;
keyring.add_ref(key);
}
// libEtPan's pgp_encrypt_mime() takes the parent as the new root.
// We just expect the root as being given to this function.
let sign_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));
}
keyring.add_ref(&self.public_key);
let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql)
.ok_or_else(|| format_err!("missing own private key"))?;
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)
}
@@ -137,18 +119,25 @@ impl EncryptHelper {
pub fn try_decrypt(
context: &Context,
mail: &mailparse::ParsedMail<'_>,
) -> Result<(Option<Vec<u8>>, HashSet<String>, i64)> {
message_time: i64,
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
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 message_time = mail
let from = mail
.headers
.get_first_value("Date")?
.and_then(|v| mailparse::dateparse(&v).ok())
.get_first_value("From")?
.and_then(|from_addr| mailparse::addrparse(&from_addr).ok())
.and_then(|from| from.extract_single_info())
.map(|from| from.addr)
.unwrap_or_default();
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 {
peerstate = Peerstate::from_addr(context, &context.sql, &from);
@@ -167,6 +156,7 @@ pub fn try_decrypt(
peerstate = Some(p);
}
}
/* possibly perform decryption */
let mut private_keyring = 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.
@@ -265,8 +255,9 @@ fn decrypt_if_autocrypt_message<'a>(
// Errors are returned for failures related to decryption of AC-messages.
let encrypted_data_part = match wrapmime::get_autocrypt_mime(mail) {
Err(_) => {
Err(err) => {
// not a proper autocrypt message, abort and ignore
warn!(context, "Invalid autocrypt message: {:?}", err);
return Ok(None);
}
Ok(res) => res,
@@ -283,12 +274,13 @@ fn decrypt_if_autocrypt_message<'a>(
/// Returns Ok(None) if nothing encrypted was found.
fn decrypt_part(
_context: &Context,
context: &Context,
mail: &mailparse::ParsedMail<'_>,
private_keyring: &Keyring,
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
) -> Result<Option<Vec<u8>>> {
info!(context, "decrypting part");
let data = mail.get_body_raw()?;
if has_decrypted_pgp_armor(&data) {

View File

@@ -1137,7 +1137,7 @@ pub fn mdn_from_ext(
return None;
}
if let Ok((msg_id, chat_id, chat_type, msg_state)) = context.sql.query_row(
let res = context.sql.query_row(
concat!(
"SELECT",
" m.id AS msg_id,",
@@ -1157,7 +1157,12 @@ pub fn mdn_from_ext(
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;
// if already marked as MDNS_RCVD msgstate_can_fail() returns false.

View File

@@ -1,5 +1,5 @@
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::config::Config;
@@ -229,11 +229,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.collect())
}
fn is_e2ee_guranteed(&self) -> Result<bool, Error> {
fn is_e2ee_guranteed(&self) -> bool {
match self.loaded {
Loaded::Message => {
if self.chat.as_ref().unwrap().typ == Chattype::VerifiedGroup {
return Ok(true);
return true;
}
let force_plaintext = self
@@ -243,78 +243,76 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.unwrap_or_default();
if force_plaintext == 0 {
return Ok(self
return self
.msg
.param
.get_int(Param::GuaranteeE2ee)
.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 {
Loaded::Message => {
let chat = self.chat.as_ref().unwrap();
if chat.typ == Chattype::VerifiedGroup {
Ok(PeerstateVerifiedStatus::BidirectVerified)
PeerstateVerifiedStatus::BidirectVerified
} 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 {
Loaded::Message => {
let chat = self.chat.as_ref().unwrap();
if chat.typ == Chattype::VerifiedGroup {
Ok(0)
0
} else {
Ok(self
.msg
self.msg
.param
.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 {
Loaded::Message => {
let chat = self.chat.as_ref().unwrap();
// beside key- and member-changes, force re-gossip every 48 hours
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 cmd {
match self.msg.param.get_cmd() {
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 {
Loaded::Message => {
let chat = self.chat.as_ref().unwrap();
@@ -322,21 +320,21 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
match cmd {
SystemMessage::MemberAddedToGroup => {
return Ok(chat.param.get(Param::ProfileImage).map(Into::into));
return chat.param.get(Param::ProfileImage).map(Into::into);
}
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 {
Loaded::Message => {
match self.chat {
@@ -354,32 +352,27 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
// do not add the "Chat:" prefix for setup messages
Ok(self
.context
self.context
.stock_str(StockMessage::AcSetupMsgSubject)
.into_owned())
.into_owned()
} 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 {
Ok(format!("Chat: {}{}", fwd, raw_subject))
format!("Chat: {}{}", fwd, raw_subject)
}
}
None => Ok(String::default()),
None => String::new(),
}
}
Loaded::MDN => {
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> {
let e2ee_guranteed = self.is_e2ee_guranteed()?;
let mut encrypt_helper = EncryptHelper::new(self.context)?;
// Headers that are encrypted
// - Chat-*, except Chat-Version
// - 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 grpimage = self.grpimage()?;
let force_plaintext = self.should_force_plaintext()?;
let subject_str = self.subject_str()?;
let min_verified = self.min_verified();
let do_gossip = self.should_do_gossip();
let grpimage = self.grpimage();
let force_plaintext = self.should_force_plaintext();
let subject_str = self.subject_str();
let e2ee_guranteed = self.is_e2ee_guranteed();
let mut encrypt_helper = EncryptHelper::new(self.context)?;
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)?;
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() {
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((
"Content-Type".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);
}
let encrypted = encrypt_helper.encrypt(
self.context,
min_verified,
do_gossip,
message,
&peerstates,
)?;
let encrypted =
encrypt_helper.encrypt(self.context, min_verified, message, &peerstates)?;
outer_message = outer_message
.child(
@@ -540,10 +549,6 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
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 MimeFactory {
@@ -555,11 +560,6 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
..
} = self;
let rfc724_mid = match loaded {
Loaded::Message => msg.rfc724_mid.clone(),
Loaded::MDN => dc_create_outgoing_rfc724_mid(None, &from_addr),
};
Ok(RenderedEmail {
message: outer_message.build().as_string().into_bytes(),
// envelope: Envelope::new,
@@ -766,10 +766,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
);
// 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)
.body(message_text);
let mut is_multipart = false;
.body(message_text)];
// add attachment part
if chat::msgtype_has_file(self.msg.type_0) {
@@ -780,14 +779,12 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
);
} else {
let (file_part, _) = build_body_file(context, &self.msg, "")?;
message = message.child(file_part);
is_multipart = true;
parts.push(file_part);
}
}
if let Some(meta_part) = meta_part {
message = message.child(meta_part);
is_multipart = true;
parts.push(meta_part);
}
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::SetLongitude).unwrap_or_default(),
);
message = message.child(
lettre_email::PartBuilder::new()
parts.push(
PartBuilder::new()
.content_type(
&"application/vnd.google-earth.kml+xml"
.parse::<mime::Mime>()
@@ -808,17 +805,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
"Content-Disposition",
"attachment; filename=\"message.kml\"",
))
.body(kml_file)
.build(),
.body(kml_file),
);
is_multipart = true;
}
if location::is_sending_locations_to_chat(context, self.msg.chat_id) {
match location::get_kml(context, self.msg.chat_id) {
Ok((kml_content, last_added_location_id)) => {
message = message.child(
lettre_email::PartBuilder::new()
parts.push(
PartBuilder::new()
.content_type(
&"application/vnd.google-earth.kml+xml"
.parse::<mime::Mime>()
@@ -828,10 +823,8 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
"Content-Disposition",
"attachment; filename=\"message.kml\"",
))
.body(kml_content)
.build(),
.body(kml_content),
);
is_multipart = true;
if !self.msg.param.exists(Param::SetLatitude) {
// otherwise, the independent location is already filed
self.last_added_location_id = last_added_location_id;
@@ -843,8 +836,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
}
}
if is_multipart {
message = message.message_type(MimeMultipartType::Mixed);
// Single part, render as regular message.
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)
@@ -920,7 +920,7 @@ fn build_body_file(
context: &Context,
msg: &Message,
base_name: &str,
) -> Result<(MimeMessage, String), Error> {
) -> Result<(PartBuilder, String), Error> {
let blob = msg
.param
.get_blob(Param::File, context, true)?
@@ -984,8 +984,7 @@ fn build_body_file(
.content_type(&mimetype)
.header(("Content-Disposition", cd_value))
.header(("Content-Transfer-Encoding", "base64"))
.body(encoded_body)
.build();
.body(encoded_body);
Ok((mail, filename_to_send))
}

View File

@@ -38,8 +38,8 @@ pub struct MimeParser<'a> {
pub location_kml: Option<location::Kml>,
pub message_kml: Option<location::Kml>,
reports: Vec<Report>,
parsed_header_protected: bool,
mdns_enabled: bool,
parsed_protected_headers: bool,
}
#[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;
const DC_MIMETYPE_MP_RELATED: i32 = 20;
const DC_MIMETYPE_MP_MIXED: i32 = 30;
const DC_MIMETYPE_MP_NOT_DECRYPTABLE: i32 = 40;
const DC_MIMETYPE_MP_REPORT: i32 = 45;
const DC_MIMETYPE_MP_SIGNED: i32 = 46;
const DC_MIMETYPE_MP_OTHER: i32 = 50;
const DC_MIMETYPE_TEXT_PLAIN: i32 = 60;
const DC_MIMETYPE_TEXT_HTML: i32 = 70;
const DC_MIMETYPE_IMAGE: i32 = 80;
const DC_MIMETYPE_AUDIO: i32 = 90;
const DC_MIMETYPE_VIDEO: i32 = 100;
const DC_MIMETYPE_FILE: i32 = 110;
const DC_MIMETYPE_AC_SETUP_FILE: i32 = 111;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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> {
pub fn from_bytes(context: &'a Context, body: &[u8]) -> Result<Self> {
@@ -85,7 +95,6 @@ impl<'a> MimeParser<'a> {
let mut parser = MimeParser {
parts: Vec::new(),
header: Default::default(),
parsed_header_protected: false,
subject: None,
is_send_by_messenger: false,
decrypting_failed: false,
@@ -99,11 +108,22 @@ impl<'a> MimeParser<'a> {
location_kml: None,
message_kml: None,
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 = 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
parser.encrypted = raw.is_some();
parser.signatures = signatures;
@@ -111,16 +131,7 @@ impl<'a> MimeParser<'a> {
if let Some(raw) = raw {
mail_raw = raw;
let decrypted_mail = mailparse::parse_mail(&mail_raw)?;
// 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 the mail
decrypted_mail
} else {
// 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_headers()?;
@@ -160,7 +175,7 @@ impl<'a> MimeParser<'a> {
let has_setup_file = self
.parts
.iter()
.any(|p| p.mimetype == DC_MIMETYPE_AC_SETUP_FILE);
.any(|p| p.mimetype == DcMimeType::AcSetupFile);
if has_setup_file {
self.is_system_message = SystemMessage::AutocryptSetupMessage;
@@ -178,7 +193,7 @@ impl<'a> MimeParser<'a> {
let mut i = 0;
while i != self.parts.len() {
if self.parts[i].mimetype != 111 {
if self.parts[i].mimetype != DcMimeType::AcSetupFile {
self.parts.remove(i);
} else {
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 {
let need_drop = {
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 self.get_last_nonmeta().is_some() {
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(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> {
info!(self.context, "parse mime_recursive {:?}", mail.ctype);
if mail.ctype.params.get("protected-headers").is_some() {
if mail.ctype.mimetype == "text/rfc822-headers" {
info!(
@@ -361,21 +381,12 @@ impl<'a> MimeParser<'a> {
return Ok(false);
}
if !self.parsed_header_protected {
// use the most outer protected header - this is typically
// created in sync with the normal, unprotected header
self.parsed_header_protected = true;
if !self.parsed_protected_headers {
self.hash_header(&mail.headers);
} else {
info!(
self.context,
"Protected headers found in MIME header: Will be ignored as we already found an outer one."
);
self.parsed_protected_headers;
}
}
// multiple = multipart/ or message/
enum MimeS {
Multiple,
Single,
@@ -422,9 +433,9 @@ impl<'a> MimeParser<'a> {
as text/plain and text/html. If we find a multipart/mixed
inside mutlipart/alternative, we use this (happens eg in
apple mail: "plaintext" as an alternative to "html+PDF attachment") */
(DC_MIMETYPE_MP_ALTERNATIVE, _) => {
(DcMimeType::MpAlternative, _) => {
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)?;
break;
}
@@ -432,7 +443,7 @@ impl<'a> MimeParser<'a> {
if !any_part_added {
/* search for text/plain and add this */
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)?;
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
not interesting for us (eg. embedded images) we assume he "root part"
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)?;
}
}
(DC_MIMETYPE_MP_NOT_DECRYPTABLE, _) => {
(DcMimeType::MpNotDecryptable, _) => {
let mut part = Part::default();
part.typ = Viewtype::Text;
let msg_body = self.context.stock_str(StockMessage::CantDecryptMsgBody);
@@ -470,7 +481,7 @@ impl<'a> MimeParser<'a> {
any_part_added = true;
self.decrypting_failed = true;
}
(DC_MIMETYPE_MP_SIGNED, _) => {
(DcMimeType::MpSigned, _) => {
/* RFC 1847: "The multipart/signed content type
contains exactly two body parts. The first body
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)?;
}
}
(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 */
if mail.subparts.len() >= 2 {
info!(self.context, "report: {:?}", &mail.ctype);
if let Some(report_type) = mail.ctype.params.get("report-type") {
if report_type == "disposition-notification" {
info!(self.context, "processing report");
if let Some(report) = self.process_report(mail)? {
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)
HACK: the following lines are a hack for clients who use
multipart/mixed instead of multipart/alternative for
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
plain+html in multipart/mixed - if so, we should disable the hack. */
let mut skip_part = -1;
@@ -518,10 +531,10 @@ impl<'a> MimeParser<'a> {
for (i, cur_data) in mail.subparts.iter().enumerate() {
match mailmime_get_mime_type(cur_data) {
(DC_MIMETYPE_TEXT_PLAIN, _) => {
(DcMimeType::TextPlain, _) => {
plain_cnt += 1;
}
(DC_MIMETYPE_TEXT_HTML, _) => {
(DcMimeType::TextHtml, _) => {
html_part = i as isize;
html_cnt += 1;
}
@@ -531,7 +544,7 @@ impl<'a> MimeParser<'a> {
if plain_cnt == 1 && html_cnt == 1 {
warn!(
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;
}
@@ -554,16 +567,16 @@ impl<'a> MimeParser<'a> {
let (mime_type, msg_type) = mailmime_get_mime_type(mail);
let raw_mime = mail.ctype.mimetype.to_lowercase();
if !raw_mime.starts_with("text") {
// MAILMIME_DATA_FILE indicates, the data is in a file; AFAIK this is not used on parsing
return Ok(false);
}
info!(
self.context,
"got mime type: {:?} ({})", mime_type, raw_mime
);
let old_part_count = self.parts.len();
// regard `Content-Transfer-Encoding:`
match mime_type {
DC_MIMETYPE_TEXT_PLAIN | DC_MIMETYPE_TEXT_HTML => {
DcMimeType::TextPlain | DcMimeType::TextHtml => {
let decoded_data = match mail.get_body() {
Ok(decoded_data) => decoded_data,
Err(err) => {
@@ -580,7 +593,7 @@ impl<'a> MimeParser<'a> {
let simplified_txt = if decoded_data.is_empty() {
"".into()
} 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)
};
@@ -597,11 +610,11 @@ impl<'a> MimeParser<'a> {
self.is_forwarded = true;
}
}
DC_MIMETYPE_IMAGE
| DC_MIMETYPE_AUDIO
| DC_MIMETYPE_VIDEO
| DC_MIMETYPE_FILE
| DC_MIMETYPE_AC_SETUP_FILE => {
DcMimeType::Image
| DcMimeType::Audio
| DcMimeType::Video
| DcMimeType::File
| DcMimeType::AcSetupFile => {
// try to get file name from
// `Content-Disposition: ... filename*=...`
// or `Content-Disposition: ... filename*0*=... filename*1*=... filename*2*=...`
@@ -650,7 +663,7 @@ impl<'a> MimeParser<'a> {
fn do_add_single_file_part(
&mut self,
msg_type: Viewtype,
mime_type: libc::c_int,
mime_type: DcMimeType,
raw_mime: &String,
decoded_data: &[u8],
filename: &str,
@@ -698,7 +711,7 @@ impl<'a> MimeParser<'a> {
part.param.set(Param::File, blob.as_name());
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) {
part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32);
@@ -809,6 +822,7 @@ impl<'a> MimeParser<'a> {
.flatten()
.and_then(|v| parse_message_id(&v))
{
info!(self.context, "got report {:?}", original_message_id);
return Ok(Some(Report {
original_message_id,
}));
@@ -827,6 +841,7 @@ impl<'a> MimeParser<'a> {
server_folder: impl AsRef<str>,
server_uid: u32,
) {
info!(self.context, "processing reports {:?}", &self.reports);
for report in &self.reports {
let mut mdn_consumed = false;
@@ -836,6 +851,10 @@ impl<'a> MimeParser<'a> {
&report.original_message_id,
sent_timestamp,
) {
info!(
self.context,
"found message for report {}", report.original_message_id
);
rr_event_to_send.push((chat_id, msg_id));
mdn_consumed = true;
}
@@ -863,16 +882,29 @@ fn update_gossip_peerstates(
let mut recipients: Option<HashSet<String>> = None;
let mut gossipped_addr: HashSet<String> = Default::default();
info!(
context,
"Updating gossip peerstates: {:#?}", &gossip_headers
);
for value in &gossip_headers {
let gossip_header = value.parse::<Aheader>();
info!(context, "got gossip header: {:?}", gossip_header);
if let Ok(ref header) = gossip_header {
if recipients.is_none() {
recipients = Some(get_recipients(mail.headers.iter().map(|v| {
// TODO: error handling
(v.get_key().unwrap(), v.get_value().unwrap())
recipients = Some(get_recipients(mail.headers.iter().filter_map(|v| {
let key = v.get_key();
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) {
let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr);
if let Some(ref mut peerstate) = peerstate {
@@ -929,15 +961,15 @@ fn is_known(key: &str) -> bool {
pub struct Part {
pub typ: Viewtype,
pub is_meta: bool,
pub mimetype: i32,
pub mimetype: DcMimeType,
pub msg: Option<String>,
pub msg_raw: Option<String>,
pub bytes: i32,
pub param: Params,
}
fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (libc::c_int, Viewtype) {
let unknown_type = (0, Viewtype::Unknown);
fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (DcMimeType, Viewtype) {
let unknown_type = (DcMimeType::Unknown, Viewtype::Unknown);
let mimetype = mail.ctype.mimetype.to_lowercase();
let mut parts = mimetype.split('/');
@@ -948,41 +980,41 @@ fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (libc::c_int, Vie
"text" => {
if !mailmime_is_attachment_disposition(mail) {
if subtype == "plain" {
return (DC_MIMETYPE_TEXT_PLAIN, Viewtype::Text);
return (DcMimeType::TextPlain, Viewtype::Text);
}
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" => {
let msg_type = match subtype {
"gif" => Viewtype::Gif,
"svg+xml" => {
return (DC_MIMETYPE_FILE, Viewtype::File);
return (DcMimeType::File, Viewtype::File);
}
_ => Viewtype::Image,
};
(DC_MIMETYPE_IMAGE, msg_type)
(DcMimeType::Image, msg_type)
}
"audio" => (DC_MIMETYPE_AUDIO, Viewtype::Audio),
"video" => (DC_MIMETYPE_VIDEO, Viewtype::Video),
"audio" => (DcMimeType::Audio, Viewtype::Audio),
"video" => (DcMimeType::Video, Viewtype::Video),
"multipart" => {
let mime_type = match subtype {
"alternative" => DC_MIMETYPE_MP_ALTERNATIVE,
"related" => DC_MIMETYPE_MP_RELATED,
"alternative" => DcMimeType::MpAlternative,
"related" => DcMimeType::MpRelated,
"encrypted" => {
// maybe try_decrypt failed to decrypt
// or it wasn't in proper Autocrypt format
DC_MIMETYPE_MP_NOT_DECRYPTABLE
DcMimeType::MpNotDecryptable
}
"signed" => DC_MIMETYPE_MP_SIGNED,
"mixed" => DC_MIMETYPE_MP_MIXED,
"report" => DC_MIMETYPE_MP_REPORT,
_ => DC_MIMETYPE_MP_OTHER,
"signed" => DcMimeType::MpSigned,
"mixed" => DcMimeType::MpMixed,
"report" => DcMimeType::MpReport,
_ => DcMimeType::MpOther,
};
(mime_type, Viewtype::Unknown)
@@ -993,16 +1025,16 @@ fn mailmime_get_mime_type(mail: &mailparse::ParsedMail<'_>) -> (libc::c_int, Vie
// 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,
// 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.
unknown_type
}
"application" => {
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,
}

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