mirror of
https://github.com/chatmail/core.git
synced 2026-05-06 06:46:35 +03:00
fix various parsing and sending issues
This commit is contained in:
@@ -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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/e2ee.rs
66
src/e2ee.rs
@@ -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) {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
Reference in New Issue
Block a user