fix: do not require the Message to render MDN

This commit is contained in:
link2xt
2024-06-21 12:23:13 +00:00
parent 92e8b80da8
commit a82eb7def6
10 changed files with 266 additions and 247 deletions

View File

@@ -6649,6 +6649,8 @@ void dc_event_unref(dc_event_t* event);
/// ///
/// Used as message text of outgoing read receipts. /// Used as message text of outgoing read receipts.
/// - %1$s will be replaced by the subject of the displayed message /// - %1$s will be replaced by the subject of the displayed message
///
/// @deprecated Deprecated 2024-06-23, use DC_STR_READRCPT_MAILBODY2 instead.
#define DC_STR_READRCPT_MAILBODY 32 #define DC_STR_READRCPT_MAILBODY 32
/// @deprecated Deprecated, this string is no longer needed. /// @deprecated Deprecated, this string is no longer needed.
@@ -7367,6 +7369,11 @@ void dc_event_unref(dc_event_t* event);
/// Used as info message. /// Used as info message.
#define DC_STR_SECUREJOIN_WAIT_TIMEOUT 191 #define DC_STR_SECUREJOIN_WAIT_TIMEOUT 191
/// "The message is a receipt notification."
///
/// Used as message text of outgoing read receipts.
#define DC_STR_READRCPT_MAILBODY2 192
/// "Contact". Deprecated, currently unused. /// "Contact". Deprecated, currently unused.
#define DC_STR_CONTACT 200 #define DC_STR_CONTACT 200

View File

@@ -266,6 +266,7 @@ module.exports = {
DC_STR_REACTED_BY: 177, DC_STR_REACTED_BY: 177,
DC_STR_READRCPT: 31, DC_STR_READRCPT: 31,
DC_STR_READRCPT_MAILBODY: 32, DC_STR_READRCPT_MAILBODY: 32,
DC_STR_READRCPT_MAILBODY2: 192,
DC_STR_REMOVE_MEMBER_BY_OTHER: 131, DC_STR_REMOVE_MEMBER_BY_OTHER: 131,
DC_STR_REMOVE_MEMBER_BY_YOU: 130, DC_STR_REMOVE_MEMBER_BY_YOU: 130,
DC_STR_REPLY_NOUN: 90, DC_STR_REPLY_NOUN: 90,

View File

@@ -266,6 +266,7 @@ export enum C {
DC_STR_REACTED_BY = 177, DC_STR_REACTED_BY = 177,
DC_STR_READRCPT = 31, DC_STR_READRCPT = 31,
DC_STR_READRCPT_MAILBODY = 32, DC_STR_READRCPT_MAILBODY = 32,
DC_STR_READRCPT_MAILBODY2 = 192,
DC_STR_REMOVE_MEMBER_BY_OTHER = 131, DC_STR_REMOVE_MEMBER_BY_OTHER = 131,
DC_STR_REMOVE_MEMBER_BY_YOU = 130, DC_STR_REMOVE_MEMBER_BY_YOU = 130,
DC_STR_REPLY_NOUN = 90, DC_STR_REPLY_NOUN = 90,

View File

@@ -2908,7 +2908,7 @@ async fn prepare_send_msg(
/// The caller has to interrupt SMTP loop or otherwise process new rows. /// The caller has to interrupt SMTP loop or otherwise process new rows.
pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> { pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default(); let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
let mimefactory = MimeFactory::from_msg(context, msg).await?; let mimefactory = MimeFactory::from_msg(context, msg.clone()).await?;
let attach_selfavatar = mimefactory.attach_selfavatar; let attach_selfavatar = mimefactory.attach_selfavatar;
let mut recipients = mimefactory.recipients(); let mut recipients = mimefactory.recipients();

View File

@@ -2,7 +2,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use anyhow::{bail, ensure, Context as _, Result}; use anyhow::{bail, Context as _, Result};
use base64::Engine as _; use base64::Engine as _;
use chrono::TimeZone; use chrono::TimeZone;
use email::Mailbox; use email::Mailbox;
@@ -40,13 +40,19 @@ pub const RECOMMENDED_FILE_SIZE: u64 = 24 * 1024 * 1024 / 4 * 3;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Loaded { pub enum Loaded {
Message { chat: Chat }, Message {
Mdn { additional_msg_ids: Vec<String> }, chat: Chat,
msg: Message,
},
Mdn {
rfc724_mid: String,
additional_msg_ids: Vec<String>,
},
} }
/// Helper to construct mime messages. /// Helper to construct mime messages.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MimeFactory<'a> { pub struct MimeFactory {
from_addr: String, from_addr: String,
from_displayname: String, from_displayname: String,
@@ -65,7 +71,6 @@ pub struct MimeFactory<'a> {
timestamp: i64, timestamp: i64,
loaded: Loaded, loaded: Loaded,
msg: &'a Message,
in_reply_to: String, in_reply_to: String,
/// Space-separated list of Message-IDs for `References` header. /// Space-separated list of Message-IDs for `References` header.
@@ -139,10 +144,10 @@ struct MessageHeaders {
pub hidden: Vec<Header>, pub hidden: Vec<Header>,
} }
impl<'a> MimeFactory<'a> { impl MimeFactory {
pub async fn from_msg(context: &Context, msg: &'a Message) -> Result<MimeFactory<'a>> { pub async fn from_msg(context: &Context, msg: Message) -> Result<MimeFactory> {
let chat = Chat::load_from_db(context, msg.chat_id).await?; let chat = Chat::load_from_db(context, msg.chat_id).await?;
let attach_profile_data = Self::should_attach_profile_data(msg); let attach_profile_data = Self::should_attach_profile_data(&msg);
let from_addr = context.get_primary_self_addr().await?; let from_addr = context.get_primary_self_addr().await?;
let config_displayname = context let config_displayname = context
@@ -236,7 +241,7 @@ impl<'a> MimeFactory<'a> {
.unwrap_or_default(), .unwrap_or_default(),
false => "".to_string(), false => "".to_string(),
}; };
let attach_selfavatar = Self::should_attach_selfavatar(context, msg).await; let attach_selfavatar = Self::should_attach_selfavatar(context, &msg).await;
let factory = MimeFactory { let factory = MimeFactory {
from_addr, from_addr,
@@ -245,8 +250,7 @@ impl<'a> MimeFactory<'a> {
selfstatus, selfstatus,
recipients, recipients,
timestamp: msg.timestamp_sort, timestamp: msg.timestamp_sort,
loaded: Loaded::Message { chat }, loaded: Loaded::Message { msg, chat },
msg,
in_reply_to, in_reply_to,
references, references,
req_mdn, req_mdn,
@@ -259,24 +263,25 @@ impl<'a> MimeFactory<'a> {
pub async fn from_mdn( pub async fn from_mdn(
context: &Context, context: &Context,
msg: &'a Message, from_id: ContactId,
rfc724_mid: String,
additional_msg_ids: Vec<String>, additional_msg_ids: Vec<String>,
) -> Result<MimeFactory<'a>> { ) -> Result<MimeFactory> {
ensure!(!msg.chat_id.is_special(), "Invalid chat id"); let contact = Contact::get_by_id(context, from_id).await?;
let contact = Contact::get_by_id(context, msg.from_id).await?;
let from_addr = context.get_primary_self_addr().await?; let from_addr = context.get_primary_self_addr().await?;
let timestamp = create_smeared_timestamp(context); let timestamp = create_smeared_timestamp(context);
let res = MimeFactory::<'a> { let res = MimeFactory {
from_addr, from_addr,
from_displayname: "".to_string(), from_displayname: "".to_string(),
sender_displayname: None, sender_displayname: None,
selfstatus: "".to_string(), selfstatus: "".to_string(),
recipients: vec![("".to_string(), contact.get_addr().to_string())], recipients: vec![("".to_string(), contact.get_addr().to_string())],
timestamp, timestamp,
loaded: Loaded::Mdn { additional_msg_ids }, loaded: Loaded::Mdn {
msg, rfc724_mid,
additional_msg_ids,
},
in_reply_to: String::default(), in_reply_to: String::default(),
references: String::default(), references: String::default(),
req_mdn: false, req_mdn: false,
@@ -308,21 +313,15 @@ impl<'a> MimeFactory<'a> {
fn is_e2ee_guaranteed(&self) -> bool { fn is_e2ee_guaranteed(&self) -> bool {
match &self.loaded { match &self.loaded {
Loaded::Message { chat } => { Loaded::Message { chat, msg } => {
if chat.is_protected() { if chat.is_protected() {
return true; return true;
} }
!self !msg.param
.msg
.param
.get_bool(Param::ForcePlaintext) .get_bool(Param::ForcePlaintext)
.unwrap_or_default() .unwrap_or_default()
&& self && msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default()
.msg
.param
.get_bool(Param::GuaranteeE2ee)
.unwrap_or_default()
} }
Loaded::Mdn { .. } => false, Loaded::Mdn { .. } => false,
} }
@@ -330,9 +329,9 @@ impl<'a> MimeFactory<'a> {
fn verified(&self) -> bool { fn verified(&self) -> bool {
match &self.loaded { match &self.loaded {
Loaded::Message { chat } => { Loaded::Message { chat, msg } => {
if chat.is_protected() { if chat.is_protected() {
if self.msg.get_info_type() == SystemMessage::SecurejoinMessage { if msg.get_info_type() == SystemMessage::SecurejoinMessage {
// Securejoin messages are supposed to verify a key. // Securejoin messages are supposed to verify a key.
// In order to do this, it is necessary that they can be sent // In order to do this, it is necessary that they can be sent
// to a key that is not yet verified. // to a key that is not yet verified.
@@ -351,9 +350,8 @@ impl<'a> MimeFactory<'a> {
fn should_force_plaintext(&self) -> bool { fn should_force_plaintext(&self) -> bool {
match &self.loaded { match &self.loaded {
Loaded::Message { chat } => { Loaded::Message { chat, msg } => {
self.msg msg.param
.param
.get_bool(Param::ForcePlaintext) .get_bool(Param::ForcePlaintext)
.unwrap_or_default() .unwrap_or_default()
|| chat.typ == Chattype::Broadcast || chat.typ == Chattype::Broadcast
@@ -364,19 +362,17 @@ impl<'a> MimeFactory<'a> {
fn should_skip_autocrypt(&self) -> bool { fn should_skip_autocrypt(&self) -> bool {
match &self.loaded { match &self.loaded {
Loaded::Message { .. } => self Loaded::Message { msg, .. } => {
.msg msg.param.get_bool(Param::SkipAutocrypt).unwrap_or_default()
.param }
.get_bool(Param::SkipAutocrypt)
.unwrap_or_default(),
Loaded::Mdn { .. } => true, Loaded::Mdn { .. } => true,
} }
} }
async fn should_do_gossip(&self, context: &Context, multiple_recipients: bool) -> Result<bool> { async fn should_do_gossip(&self, context: &Context, multiple_recipients: bool) -> Result<bool> {
match &self.loaded { match &self.loaded {
Loaded::Message { chat } => { Loaded::Message { chat, msg } => {
let cmd = self.msg.param.get_cmd(); let cmd = msg.param.get_cmd();
if cmd == SystemMessage::MemberAddedToGroup if cmd == SystemMessage::MemberAddedToGroup
|| cmd == SystemMessage::SecurejoinMessage || cmd == SystemMessage::SecurejoinMessage
{ {
@@ -429,21 +425,20 @@ impl<'a> MimeFactory<'a> {
fn grpimage(&self) -> Option<String> { fn grpimage(&self) -> Option<String> {
match &self.loaded { match &self.loaded {
Loaded::Message { chat } => { Loaded::Message { chat, msg } => {
let cmd = self.msg.param.get_cmd(); let cmd = msg.param.get_cmd();
match cmd { match cmd {
SystemMessage::MemberAddedToGroup => { SystemMessage::MemberAddedToGroup => {
return chat.param.get(Param::ProfileImage).map(Into::into); return chat.param.get(Param::ProfileImage).map(Into::into);
} }
SystemMessage::GroupImageChanged => { SystemMessage::GroupImageChanged => {
return self.msg.param.get(Param::Arg).map(Into::into) return msg.param.get(Param::Arg).map(Into::into)
} }
_ => {} _ => {}
} }
if self if msg
.msg
.param .param
.get_bool(Param::AttachGroupImage) .get_bool(Param::AttachGroupImage)
.unwrap_or_default() .unwrap_or_default()
@@ -457,13 +452,13 @@ impl<'a> MimeFactory<'a> {
} }
} }
async fn subject_str(&self, context: &Context) -> anyhow::Result<String> { async fn subject_str(&self, context: &Context) -> Result<String> {
let quoted_msg_subject = self.msg.quoted_message(context).await?.map(|m| m.subject); let subject = match &self.loaded {
Loaded::Message { ref chat, msg } => {
let quoted_msg_subject = msg.quoted_message(context).await?.map(|m| m.subject);
let subject = match self.loaded { if !msg.subject.is_empty() {
Loaded::Message { ref chat } => { return Ok(msg.subject.clone());
if !self.msg.subject.is_empty() {
return Ok(self.msg.subject.clone());
} }
if (chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast) if (chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast)
@@ -486,7 +481,7 @@ impl<'a> MimeFactory<'a> {
return Ok(format!("Re: {}", remove_subject_prefix(last_subject))); return Ok(format!("Re: {}", remove_subject_prefix(last_subject)));
} }
let self_name = match Self::should_attach_profile_data(self.msg) { let self_name = match Self::should_attach_profile_data(msg) {
true => context.get_config(Config::Displayname).await?, true => context.get_config(Config::Displayname).await?,
false => None, false => None,
}; };
@@ -520,7 +515,7 @@ impl<'a> MimeFactory<'a> {
); );
let undisclosed_recipients = match &self.loaded { let undisclosed_recipients = match &self.loaded {
Loaded::Message { chat } => chat.typ == Chattype::Broadcast, Loaded::Message { chat, .. } => chat.typ == Chattype::Broadcast,
Loaded::Mdn { .. } => false, Loaded::Mdn { .. } => false,
}; };
@@ -531,12 +526,16 @@ impl<'a> MimeFactory<'a> {
Vec::new(), Vec::new(),
)); ));
} else { } else {
let email_to_remove = let email_to_remove = match &self.loaded {
if self.msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup { Loaded::Message { msg, .. } => {
self.msg.param.get(Param::Arg) if msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup {
} else { msg.param.get(Param::Arg)
None } else {
}; None
}
}
Loaded::Mdn { .. } => None,
};
for (name, addr) in &self.recipients { for (name, addr) in &self.recipients {
if let Some(email_to_remove) = email_to_remove { if let Some(email_to_remove) = email_to_remove {
@@ -596,8 +595,8 @@ impl<'a> MimeFactory<'a> {
.to_rfc2822(); .to_rfc2822();
headers.unprotected.push(Header::new("Date".into(), date)); headers.unprotected.push(Header::new("Date".into(), date));
let rfc724_mid = match self.loaded { let rfc724_mid = match &self.loaded {
Loaded::Message { .. } => self.msg.rfc724_mid.clone(), Loaded::Message { msg, .. } => msg.rfc724_mid.clone(),
Loaded::Mdn { .. } => create_outgoing_rfc724_mid(), Loaded::Mdn { .. } => create_outgoing_rfc724_mid(),
}; };
let rfc724_mid_headervalue = render_rfc724_mid(&rfc724_mid); let rfc724_mid_headervalue = render_rfc724_mid(&rfc724_mid);
@@ -630,7 +629,7 @@ impl<'a> MimeFactory<'a> {
)); ));
} }
if let Loaded::Message { chat } = &self.loaded { if let Loaded::Message { chat, .. } = &self.loaded {
if chat.typ == Chattype::Broadcast { if chat.typ == Chattype::Broadcast {
let encoded_chat_name = encode_words(&chat.name); let encoded_chat_name = encode_words(&chat.name);
headers.protected.push(Header::new( headers.protected.push(Header::new(
@@ -670,12 +669,17 @@ impl<'a> MimeFactory<'a> {
.push(Header::new("Autocrypt".into(), aheader)); .push(Header::new("Autocrypt".into(), aheader));
} }
let ephemeral_timer = self.msg.chat_id.get_ephemeral_timer(context).await?; // Add ephemeral timer for non-MDN messages.
if let EphemeralTimer::Enabled { duration } = ephemeral_timer { // For MDNs it does not matter because they are not visible
headers.protected.push(Header::new( // and ignored by the receiver.
"Ephemeral-Timer".to_string(), if let Loaded::Message { msg, .. } = &self.loaded {
duration.to_string(), let ephemeral_timer = msg.chat_id.get_ephemeral_timer(context).await?;
)); if let EphemeralTimer::Enabled { duration } = ephemeral_timer {
headers.protected.push(Header::new(
"Ephemeral-Timer".to_string(),
duration.to_string(),
));
}
} }
// MIME header <https://datatracker.ietf.org/doc/html/rfc2045>. // MIME header <https://datatracker.ietf.org/doc/html/rfc2045>.
@@ -718,8 +722,13 @@ impl<'a> MimeFactory<'a> {
.unwrap(), .unwrap(),
); );
} }
if is_encrypted && verified || self.msg.param.get_cmd() == SystemMessage::SecurejoinMessage
{ let is_securejoin_message = if let Loaded::Message { msg, .. } = &self.loaded {
msg.param.get_cmd() == SystemMessage::SecurejoinMessage
} else {
false
};
if is_encrypted && verified || is_securejoin_message {
headers.unprotected.insert( headers.unprotected.insert(
0, 0,
Header::new_with_value( Header::new_with_value(
@@ -732,38 +741,39 @@ impl<'a> MimeFactory<'a> {
headers.unprotected.insert(0, from_header); headers.unprotected.insert(0, from_header);
} }
let (main_part, parts) = match self.loaded { let message = match &self.loaded {
Loaded::Message { .. } => { Loaded::Message { msg, .. } => {
self.render_message(context, &mut headers, &grpimage, is_encrypted) let msg = msg.clone();
.await? let (main_part, parts) = self
.render_message(context, &mut headers, &grpimage, is_encrypted)
.await?;
if parts.is_empty() {
// Single part, render as regular message.
main_part
} else {
// Multiple parts, render as multipart.
let part_holder = if msg.param.get_cmd() == SystemMessage::MultiDeviceSync {
PartBuilder::new().header((
"Content-Type".to_string(),
"multipart/report; report-type=multi-device-sync".to_string(),
))
} else if msg.param.get_cmd() == SystemMessage::WebxdcStatusUpdate {
PartBuilder::new().header((
"Content-Type".to_string(),
"multipart/report; report-type=status-update".to_string(),
))
} else {
PartBuilder::new().message_type(MimeMultipartType::Mixed)
};
parts
.into_iter()
.fold(part_holder.child(main_part.build()), |message, part| {
message.child(part.build())
})
}
} }
Loaded::Mdn { .. } => (self.render_mdn(context).await?, Vec::new()), Loaded::Mdn { .. } => self.render_mdn(context).await?,
};
let message = if parts.is_empty() {
// Single part, render as regular message.
main_part
} else {
// Multiple parts, render as multipart.
let part_holder = if self.msg.param.get_cmd() == SystemMessage::MultiDeviceSync {
PartBuilder::new().header((
"Content-Type".to_string(),
"multipart/report; report-type=multi-device-sync".to_string(),
))
} else if self.msg.param.get_cmd() == SystemMessage::WebxdcStatusUpdate {
PartBuilder::new().header((
"Content-Type".to_string(),
"multipart/report; report-type=status-update".to_string(),
))
} else {
PartBuilder::new().message_type(MimeMultipartType::Mixed)
};
parts
.into_iter()
.fold(part_holder.child(main_part.build()), |message, part| {
message.child(part.build())
})
}; };
let get_content_type_directives_header = || { let get_content_type_directives_header = || {
@@ -825,7 +835,12 @@ impl<'a> MimeFactory<'a> {
// Disable compression for SecureJoin to ensure // Disable compression for SecureJoin to ensure
// there are no compression side channels // there are no compression side channels
// leaking information about the tokens. // leaking information about the tokens.
let compress = self.msg.param.get_cmd() != SystemMessage::SecurejoinMessage; let compress = match &self.loaded {
Loaded::Message { msg, .. } => {
msg.param.get_cmd() != SystemMessage::SecurejoinMessage
}
Loaded::Mdn { .. } => true,
};
let encrypted = encrypt_helper let encrypted = encrypt_helper
.encrypt(context, verified, message, peerstates, compress) .encrypt(context, verified, message, peerstates, compress)
.await?; .await?;
@@ -955,10 +970,14 @@ impl<'a> MimeFactory<'a> {
/// Returns MIME part with a `message.kml` attachment. /// Returns MIME part with a `message.kml` attachment.
fn get_message_kml_part(&self) -> Option<PartBuilder> { fn get_message_kml_part(&self) -> Option<PartBuilder> {
let latitude = self.msg.param.get_float(Param::SetLatitude)?; let Loaded::Message { msg, .. } = &self.loaded else {
let longitude = self.msg.param.get_float(Param::SetLongitude)?; return None;
};
let kml_file = location::get_message_kml(self.msg.timestamp_sort, latitude, longitude); let latitude = msg.param.get_float(Param::SetLatitude)?;
let longitude = msg.param.get_float(Param::SetLongitude)?;
let kml_file = location::get_message_kml(msg.timestamp_sort, latitude, longitude);
let part = PartBuilder::new() let part = PartBuilder::new()
.content_type( .content_type(
&"application/vnd.google-earth.kml+xml" &"application/vnd.google-earth.kml+xml"
@@ -975,8 +994,12 @@ impl<'a> MimeFactory<'a> {
/// Returns MIME part with a `location.kml` attachment. /// Returns MIME part with a `location.kml` attachment.
async fn get_location_kml_part(&mut self, context: &Context) -> Result<Option<PartBuilder>> { async fn get_location_kml_part(&mut self, context: &Context) -> Result<Option<PartBuilder>> {
let Loaded::Message { msg, .. } = &self.loaded else {
return Ok(None);
};
let Some((kml_content, last_added_location_id)) = let Some((kml_content, last_added_location_id)) =
location::get_kml(context, self.msg.chat_id).await? location::get_kml(context, msg.chat_id).await?
else { else {
return Ok(None); return Ok(None);
}; };
@@ -992,7 +1015,7 @@ impl<'a> MimeFactory<'a> {
"attachment; filename=\"location.kml\"", "attachment; filename=\"location.kml\"",
)) ))
.body(kml_content); .body(kml_content);
if !self.msg.param.exists(Param::SetLatitude) { if !msg.param.exists(Param::SetLatitude) {
// otherwise, the independent location is already filed // otherwise, the independent location is already filed
self.last_added_location_id = Some(last_added_location_id); self.last_added_location_id = Some(last_added_location_id);
} }
@@ -1017,11 +1040,12 @@ impl<'a> MimeFactory<'a> {
grpimage: &Option<String>, grpimage: &Option<String>,
is_encrypted: bool, is_encrypted: bool,
) -> Result<(PartBuilder, Vec<PartBuilder>)> { ) -> Result<(PartBuilder, Vec<PartBuilder>)> {
let chat = match &self.loaded { let Loaded::Message { chat, msg } = &self.loaded else {
Loaded::Message { chat } => chat, bail!("Attempt to render MDN as a message");
Loaded::Mdn { .. } => bail!("Attempt to render MDN as a message"),
}; };
let command = self.msg.param.get_cmd(); let chat = chat.clone();
let msg = msg.clone();
let command = msg.param.get_cmd();
let mut placeholdertext = None; let mut placeholdertext = None;
let send_verified_headers = match chat.typ { let send_verified_headers = match chat.typ {
@@ -1052,7 +1076,7 @@ impl<'a> MimeFactory<'a> {
match command { match command {
SystemMessage::MemberRemovedFromGroup => { SystemMessage::MemberRemovedFromGroup => {
let email_to_remove = self.msg.param.get(Param::Arg).unwrap_or_default(); let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
if email_to_remove if email_to_remove
== context == context
@@ -1074,7 +1098,7 @@ impl<'a> MimeFactory<'a> {
} }
} }
SystemMessage::MemberAddedToGroup => { SystemMessage::MemberAddedToGroup => {
let email_to_add = self.msg.param.get(Param::Arg).unwrap_or_default(); let email_to_add = msg.param.get(Param::Arg).unwrap_or_default();
placeholdertext = placeholdertext =
Some(stock_str::msg_add_member_remote(context, email_to_add).await); Some(stock_str::msg_add_member_remote(context, email_to_add).await);
@@ -1084,9 +1108,7 @@ impl<'a> MimeFactory<'a> {
email_to_add.into(), email_to_add.into(),
)); ));
} }
if 0 != self.msg.param.get_int(Param::Arg2).unwrap_or_default() if 0 != msg.param.get_int(Param::Arg2).unwrap_or_default() & DC_FROM_HANDSHAKE {
& DC_FROM_HANDSHAKE
{
info!( info!(
context, context,
"Sending secure-join message {:?}.", "vg-member-added", "Sending secure-join message {:?}.", "vg-member-added",
@@ -1098,7 +1120,7 @@ impl<'a> MimeFactory<'a> {
} }
} }
SystemMessage::GroupNameChanged => { SystemMessage::GroupNameChanged => {
let old_name = self.msg.param.get(Param::Arg).unwrap_or_default(); let old_name = msg.param.get(Param::Arg).unwrap_or_default();
headers.protected.push(Header::new( headers.protected.push(Header::new(
"Chat-Group-Name-Changed".into(), "Chat-Group-Name-Changed".into(),
maybe_encode_words(old_name), maybe_encode_words(old_name),
@@ -1157,7 +1179,6 @@ impl<'a> MimeFactory<'a> {
placeholdertext = Some(stock_str::ac_setup_msg_body(context).await); placeholdertext = Some(stock_str::ac_setup_msg_body(context).await);
} }
SystemMessage::SecurejoinMessage => { SystemMessage::SecurejoinMessage => {
let msg = &self.msg;
let step = msg.param.get(Param::Arg).unwrap_or_default(); let step = msg.param.get(Param::Arg).unwrap_or_default();
if !step.is_empty() { if !step.is_empty() {
info!(context, "Sending secure-join message {step:?}."); info!(context, "Sending secure-join message {step:?}.");
@@ -1229,35 +1250,31 @@ impl<'a> MimeFactory<'a> {
)); ));
} }
if self.msg.viewtype == Viewtype::Sticker { if msg.viewtype == Viewtype::Sticker {
headers headers
.protected .protected
.push(Header::new("Chat-Content".into(), "sticker".into())); .push(Header::new("Chat-Content".into(), "sticker".into()));
} else if self.msg.viewtype == Viewtype::VideochatInvitation { } else if msg.viewtype == Viewtype::VideochatInvitation {
headers.protected.push(Header::new( headers.protected.push(Header::new(
"Chat-Content".into(), "Chat-Content".into(),
"videochat-invitation".into(), "videochat-invitation".into(),
)); ));
headers.protected.push(Header::new( headers.protected.push(Header::new(
"Chat-Webrtc-Room".into(), "Chat-Webrtc-Room".into(),
self.msg msg.param.get(Param::WebrtcRoom).unwrap_or_default().into(),
.param
.get(Param::WebrtcRoom)
.unwrap_or_default()
.into(),
)); ));
} }
if self.msg.viewtype == Viewtype::Voice if msg.viewtype == Viewtype::Voice
|| self.msg.viewtype == Viewtype::Audio || msg.viewtype == Viewtype::Audio
|| self.msg.viewtype == Viewtype::Video || msg.viewtype == Viewtype::Video
{ {
if self.msg.viewtype == Viewtype::Voice { if msg.viewtype == Viewtype::Voice {
headers headers
.protected .protected
.push(Header::new("Chat-Voice-Message".into(), "1".into())); .push(Header::new("Chat-Voice-Message".into(), "1".into()));
} }
let duration_ms = self.msg.param.get_int(Param::Duration).unwrap_or_default(); let duration_ms = msg.param.get_int(Param::Duration).unwrap_or_default();
if duration_ms > 0 { if duration_ms > 0 {
let dur = duration_ms.to_string(); let dur = duration_ms.to_string();
headers headers
@@ -1271,7 +1288,7 @@ impl<'a> MimeFactory<'a> {
// - we can add "forward hints" this way // - we can add "forward hints" this way
// - it looks better // - it looks better
let afwd_email = self.msg.param.exists(Param::Forwarded); let afwd_email = msg.param.exists(Param::Forwarded);
let fwdhint = if afwd_email { let fwdhint = if afwd_email {
Some( Some(
"---------- Forwarded message ----------\r\n\ "---------- Forwarded message ----------\r\n\
@@ -1283,19 +1300,12 @@ impl<'a> MimeFactory<'a> {
None None
}; };
let final_text = placeholdertext.as_deref().unwrap_or(&self.msg.text); let final_text = placeholdertext.as_deref().unwrap_or(&msg.text);
let mut quoted_text = self let mut quoted_text = msg
.msg
.quoted_text() .quoted_text()
.map(|quote| format_flowed_quote(&quote) + "\r\n\r\n"); .map(|quote| format_flowed_quote(&quote) + "\r\n\r\n");
if !is_encrypted if !is_encrypted && msg.param.get_bool(Param::ProtectQuote).unwrap_or_default() {
&& self
.msg
.param
.get_bool(Param::ProtectQuote)
.unwrap_or_default()
{
// Message is not encrypted but quotes encrypted message. // Message is not encrypted but quotes encrypted message.
quoted_text = Some("> ...\r\n\r\n".to_string()); quoted_text = Some("> ...\r\n\r\n".to_string());
} }
@@ -1306,7 +1316,7 @@ impl<'a> MimeFactory<'a> {
} }
let flowed_text = format_flowed(final_text); let flowed_text = format_flowed(final_text);
let is_reaction = self.msg.param.get_int(Param::Reaction).unwrap_or_default() != 0; let is_reaction = msg.param.get_int(Param::Reaction).unwrap_or_default() != 0;
let footer = if is_reaction { "" } else { &self.selfstatus }; let footer = if is_reaction { "" } else { &self.selfstatus };
@@ -1338,13 +1348,13 @@ impl<'a> MimeFactory<'a> {
// add HTML-part, this is needed only if a HTML-message from a non-delta-client is forwarded; // add HTML-part, this is needed only if a HTML-message from a non-delta-client is forwarded;
// for simplificity and to avoid conversion errors, we're generating the HTML-part from the original message. // for simplificity and to avoid conversion errors, we're generating the HTML-part from the original message.
if self.msg.has_html() { if msg.has_html() {
let html = if let Some(orig_msg_id) = self.msg.param.get_int(Param::Forwarded) { let html = if let Some(orig_msg_id) = msg.param.get_int(Param::Forwarded) {
MsgId::new(orig_msg_id.try_into()?) MsgId::new(orig_msg_id.try_into()?)
.get_html(context) .get_html(context)
.await? .await?
} else { } else {
self.msg.param.get(Param::SendHtml).map(|s| s.to_string()) msg.param.get(Param::SendHtml).map(|s| s.to_string())
}; };
if let Some(html) = html { if let Some(html) = html {
main_part = PartBuilder::new() main_part = PartBuilder::new()
@@ -1355,8 +1365,8 @@ impl<'a> MimeFactory<'a> {
} }
// add attachment part // add attachment part
if self.msg.viewtype.has_file() { if msg.viewtype.has_file() {
let (file_part, _) = build_body_file(context, self.msg, "").await?; let (file_part, _) = build_body_file(context, &msg, "").await?;
parts.push(file_part); parts.push(file_part);
} }
@@ -1364,7 +1374,7 @@ impl<'a> MimeFactory<'a> {
parts.push(msg_kml_part); parts.push(msg_kml_part);
} }
if location::is_sending_locations_to_chat(context, Some(self.msg.chat_id)).await? { if location::is_sending_locations_to_chat(context, Some(msg.chat_id)).await? {
if let Some(part) = self.get_location_kml_part(context).await? { if let Some(part) = self.get_location_kml_part(context).await? {
parts.push(part); parts.push(part);
} }
@@ -1373,20 +1383,20 @@ impl<'a> MimeFactory<'a> {
// we do not piggyback sync-files to other self-sent-messages // we do not piggyback sync-files to other self-sent-messages
// to not risk files becoming too larger and being skipped by download-on-demand. // to not risk files becoming too larger and being skipped by download-on-demand.
if command == SystemMessage::MultiDeviceSync && self.is_e2ee_guaranteed() { if command == SystemMessage::MultiDeviceSync && self.is_e2ee_guaranteed() {
let json = self.msg.param.get(Param::Arg).unwrap_or_default(); let json = msg.param.get(Param::Arg).unwrap_or_default();
let ids = self.msg.param.get(Param::Arg2).unwrap_or_default(); let ids = msg.param.get(Param::Arg2).unwrap_or_default();
parts.push(context.build_sync_part(json.to_string())); parts.push(context.build_sync_part(json.to_string()));
self.sync_ids_to_delete = Some(ids.to_string()); self.sync_ids_to_delete = Some(ids.to_string());
} else if command == SystemMessage::WebxdcStatusUpdate { } else if command == SystemMessage::WebxdcStatusUpdate {
let json = self.msg.param.get(Param::Arg).unwrap_or_default(); let json = msg.param.get(Param::Arg).unwrap_or_default();
parts.push(context.build_status_update_part(json)); parts.push(context.build_status_update_part(json));
} else if self.msg.viewtype == Viewtype::Webxdc { } else if msg.viewtype == Viewtype::Webxdc {
let topic = peer_channels::create_random_topic(); let topic = peer_channels::create_random_topic();
headers headers
.protected .protected
.push(create_iroh_header(context, topic, self.msg.id).await?); .push(create_iroh_header(context, topic, msg.id).await?);
if let Some(json) = context if let Some(json) = context
.render_webxdc_status_update_object(self.msg.id, None) .render_webxdc_status_update_object(msg.id, None)
.await? .await?
{ {
parts.push(context.build_status_update_part(&json)); parts.push(context.build_status_update_part(&json));
@@ -1425,11 +1435,12 @@ impl<'a> MimeFactory<'a> {
// are forwarded for any reasons (eg. gmail always forwards to IMAP), we have no chance to decrypt them; // are forwarded for any reasons (eg. gmail always forwards to IMAP), we have no chance to decrypt them;
// this issue is fixed with 0.9.4 // this issue is fixed with 0.9.4
let additional_msg_ids = match &self.loaded { let Loaded::Mdn {
Loaded::Message { .. } => bail!("Attempt to render a message as MDN"), rfc724_mid,
Loaded::Mdn { additional_msg_ids,
additional_msg_ids, .. } = &self.loaded
} => additional_msg_ids, else {
bail!("Attempt to render a message as MDN");
}; };
let mut message = PartBuilder::new().header(( let mut message = PartBuilder::new().header((
@@ -1438,23 +1449,10 @@ impl<'a> MimeFactory<'a> {
)); ));
// first body part: always human-readable, always REQUIRED by RFC 6522 // first body part: always human-readable, always REQUIRED by RFC 6522
let p1 = if 0 let message_text = format!(
!= self "{}\r\n",
.msg format_flowed(&stock_str::read_rcpt_mail_body(context).await)
.param );
.get_int(Param::GuaranteeE2ee)
.unwrap_or_default()
{
stock_str::encrypted_msg(context).await
} else {
self.msg
.get_summary(context, None)
.await?
.truncated_text(32)
.to_string()
};
let p2 = stock_str::read_rcpt_mail_body(context, &p1).await;
let message_text = format!("{}\r\n", format_flowed(&p2));
let text_part = PartBuilder::new().header(( let text_part = PartBuilder::new().header((
"Content-Type".to_string(), "Content-Type".to_string(),
"text/plain; charset=utf-8; format=flowed; delsp=no".to_string(), "text/plain; charset=utf-8; format=flowed; delsp=no".to_string(),
@@ -1468,7 +1466,7 @@ impl<'a> MimeFactory<'a> {
Final-Recipient: rfc822;{}\r\n\ Final-Recipient: rfc822;{}\r\n\
Original-Message-ID: <{}>\r\n\ Original-Message-ID: <{}>\r\n\
Disposition: manual-action/MDN-sent-automatically; displayed\r\n", Disposition: manual-action/MDN-sent-automatically; displayed\r\n",
self.from_addr, self.from_addr, self.msg.rfc724_mid self.from_addr, self.from_addr, rfc724_mid
); );
let extension_fields = if additional_msg_ids.is_empty() { let extension_fields = if additional_msg_ids.is_empty() {
@@ -1930,7 +1928,7 @@ mod tests {
Original-Message-ID: <2893@example.com>\n\ Original-Message-ID: <2893@example.com>\n\
Disposition: manual-action/MDN-sent-automatically; displayed\n\ Disposition: manual-action/MDN-sent-automatically; displayed\n\
\n", &t).await; \n", &t).await;
let mf = MimeFactory::from_msg(&t, &new_msg).await.unwrap(); let mf = MimeFactory::from_msg(&t, new_msg).await.unwrap();
// The subject string should not be "Re: message opened" // The subject string should not be "Re: message opened"
assert_eq!("Re: Hello, Bob", mf.subject_str(&t).await.unwrap()); assert_eq!("Re: Hello, Bob", mf.subject_str(&t).await.unwrap());
} }
@@ -1956,7 +1954,8 @@ mod tests {
let rcvd = bob.recv_msg(&sent).await; let rcvd = bob.recv_msg(&sent).await;
message::markseen_msgs(&bob, vec![rcvd.id]).await?; message::markseen_msgs(&bob, vec![rcvd.id]).await?;
let mimefactory = MimeFactory::from_mdn(&bob, &rcvd, vec![]).await?; let mimefactory =
MimeFactory::from_mdn(&bob, rcvd.from_id, rcvd.rfc724_mid.clone(), vec![]).await?;
let rendered_msg = mimefactory.render(&bob).await?; let rendered_msg = mimefactory.render(&bob).await?;
assert!(!rendered_msg.is_encrypted); assert!(!rendered_msg.is_encrypted);
@@ -1968,7 +1967,8 @@ mod tests {
let rcvd = tcm.send_recv(&alice, &bob, "Heyho").await; let rcvd = tcm.send_recv(&alice, &bob, "Heyho").await;
message::markseen_msgs(&bob, vec![rcvd.id]).await?; message::markseen_msgs(&bob, vec![rcvd.id]).await?;
let mimefactory = MimeFactory::from_mdn(&bob, &rcvd, vec![]).await?; let mimefactory =
MimeFactory::from_mdn(&bob, rcvd.from_id, rcvd.rfc724_mid, vec![]).await?;
let rendered_msg = mimefactory.render(&bob).await?; let rendered_msg = mimefactory.render(&bob).await?;
// When encrypted, the MDN should be encrypted as well // When encrypted, the MDN should be encrypted as well
@@ -2078,7 +2078,7 @@ mod tests {
new_msg.chat_id = chat_id; new_msg.chat_id = chat_id;
chat::prepare_msg(&t, chat_id, &mut new_msg).await.unwrap(); chat::prepare_msg(&t, chat_id, &mut new_msg).await.unwrap();
let mf = MimeFactory::from_msg(&t, &new_msg).await.unwrap(); let mf = MimeFactory::from_msg(&t, new_msg).await.unwrap();
mf.subject_str(&t).await.unwrap() mf.subject_str(&t).await.unwrap()
} }
@@ -2163,7 +2163,7 @@ mod tests {
new_msg.set_quote(&t, Some(&incoming_msg)).await.unwrap(); new_msg.set_quote(&t, Some(&incoming_msg)).await.unwrap();
} }
let mf = MimeFactory::from_msg(&t, &new_msg).await.unwrap(); let mf = MimeFactory::from_msg(&t, new_msg).await.unwrap();
mf.subject_str(&t).await.unwrap() mf.subject_str(&t).await.unwrap()
} }
@@ -2211,7 +2211,7 @@ mod tests {
) )
.await; .await;
let mimefactory = MimeFactory::from_msg(&t, &msg).await.unwrap(); let mimefactory = MimeFactory::from_msg(&t, msg).await.unwrap();
let recipients = mimefactory.recipients(); let recipients = mimefactory.recipients();
assert_eq!(recipients, vec!["charlie@example.com"]); assert_eq!(recipients, vec!["charlie@example.com"]);

View File

@@ -2800,8 +2800,13 @@ async fn test_read_receipts_dont_create_chats() -> Result<()> {
assert_eq!(chats.len(), 0); assert_eq!(chats.len(), 0);
// Bob sends a read receipt. // Bob sends a read receipt.
let mdn_mimefactory = let mdn_mimefactory = crate::mimefactory::MimeFactory::from_mdn(
crate::mimefactory::MimeFactory::from_mdn(&bob, &received_msg, vec![]).await?; &bob,
received_msg.from_id,
received_msg.rfc724_mid,
vec![],
)
.await?;
let rendered_mdn = mdn_mimefactory.render(&bob).await?; let rendered_mdn = mdn_mimefactory.render(&bob).await?;
let mdn_body = rendered_mdn.message; let mdn_body = rendered_mdn.message;
@@ -2830,8 +2835,13 @@ async fn test_read_receipts_dont_unmark_bots() -> Result<()> {
let received_msg = bob.get_last_msg().await; let received_msg = bob.get_last_msg().await;
// Bob sends a read receipt. // Bob sends a read receipt.
let mdn_mimefactory = let mdn_mimefactory = crate::mimefactory::MimeFactory::from_mdn(
crate::mimefactory::MimeFactory::from_mdn(bob, &received_msg, vec![]).await?; bob,
received_msg.from_id,
received_msg.rfc724_mid,
vec![],
)
.await?;
let rendered_mdn = mdn_mimefactory.render(bob).await?; let rendered_mdn = mdn_mimefactory.render(bob).await?;
let mdn_body = rendered_mdn.message; let mdn_body = rendered_mdn.message;

View File

@@ -361,7 +361,7 @@ pub(crate) async fn smtp_send(
recipients: &[async_smtp::EmailAddress], recipients: &[async_smtp::EmailAddress],
message: &str, message: &str,
smtp: &mut Smtp, smtp: &mut Smtp,
msg_id: MsgId, msg_id: Option<MsgId>,
) -> SendResult { ) -> SendResult {
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!(context, "SMTP-sending out mime message:\n{message}"); info!(context, "SMTP-sending out mime message:\n{message}");
@@ -489,19 +489,22 @@ pub(crate) async fn smtp_send(
}; };
if let SendResult::Failure(err) = &status { if let SendResult::Failure(err) = &status {
// We couldn't send the message, so mark it as failed if let Some(msg_id) = msg_id {
match Message::load_from_db(context, msg_id).await { // We couldn't send the message, so mark it as failed
Ok(mut msg) => { match Message::load_from_db(context, msg_id).await {
if let Err(err) = message::set_msg_failed(context, &mut msg, &err.to_string()).await Ok(mut msg) => {
{ if let Err(err) =
error!(context, "Failed to mark {msg_id} as failed: {err:#}."); message::set_msg_failed(context, &mut msg, &err.to_string()).await
{
error!(context, "Failed to mark {msg_id} as failed: {err:#}.");
}
}
Err(err) => {
error!(
context,
"Failed to load {msg_id} to mark it as failed: {err:#}."
);
} }
}
Err(err) => {
error!(
context,
"Failed to load {msg_id} to mark it as failed: {err:#}."
);
} }
} }
} }
@@ -577,7 +580,7 @@ pub(crate) async fn send_msg_to_smtp(
) )
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let status = smtp_send(context, &recipients_list, body.as_str(), smtp, msg_id).await; let status = smtp_send(context, &recipients_list, body.as_str(), smtp, Some(msg_id)).await;
match status { match status {
SendResult::Retry => {} SendResult::Retry => {}
@@ -706,7 +709,7 @@ pub(crate) async fn send_smtp_messages(context: &Context, connection: &mut Smtp)
Ok(()) Ok(())
} }
/// Tries to send MDN for message `msg_id` to `contact_id`. /// Tries to send MDN for message identified by `rfc724_mdn` to `contact_id`.
/// ///
/// Attempts to aggregate additional MDNs for `contact_id` into sent MDN. /// Attempts to aggregate additional MDNs for `contact_id` into sent MDN.
/// ///
@@ -715,9 +718,9 @@ pub(crate) async fn send_smtp_messages(context: &Context, connection: &mut Smtp)
/// points to non-existent message or contact. /// points to non-existent message or contact.
/// ///
/// Returns true on success, false on temporary error. /// Returns true on success, false on temporary error.
async fn send_mdn_msg_id( async fn send_mdn_rfc724_mid(
context: &Context, context: &Context,
msg_id: MsgId, rfc724_mid: &str,
contact_id: ContactId, contact_id: ContactId,
smtp: &mut Smtp, smtp: &mut Smtp,
) -> Result<bool> { ) -> Result<bool> {
@@ -727,26 +730,30 @@ async fn send_mdn_msg_id(
} }
// Try to aggregate additional MDNs into this MDN. // Try to aggregate additional MDNs into this MDN.
let (additional_msg_ids, additional_rfc724_mids): (Vec<MsgId>, Vec<String>) = context let additional_rfc724_mids: Vec<String> = context
.sql .sql
.query_map( .query_map(
"SELECT msg_id, rfc724_mid "SELECT rfc724_mid
FROM smtp_mdns FROM smtp_mdns
WHERE from_id=? AND msg_id!=?", WHERE from_id=? AND rfc724_mid!=?",
(contact_id, msg_id), (contact_id, &rfc724_mid),
|row| { |row| {
let msg_id: MsgId = row.get(0)?; let rfc724_mid: String = row.get(0)?;
let rfc724_mid: String = row.get(1)?; Ok(rfc724_mid)
Ok((msg_id, rfc724_mid))
}, },
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into), |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
) )
.await? .await?
.into_iter() .into_iter()
.unzip(); .collect();
let msg = Message::load_from_db(context, msg_id).await?; let mimefactory = MimeFactory::from_mdn(
let mimefactory = MimeFactory::from_mdn(context, &msg, additional_rfc724_mids).await?; context,
contact_id,
rfc724_mid.to_string(),
additional_rfc724_mids.clone(),
)
.await?;
let rendered_msg = mimefactory.render(context).await?; let rendered_msg = mimefactory.render(context).await?;
let body = rendered_msg.message; let body = rendered_msg.message;
@@ -755,21 +762,21 @@ async fn send_mdn_msg_id(
.map_err(|err| format_err!("invalid recipient: {} {:?}", addr, err))?; .map_err(|err| format_err!("invalid recipient: {} {:?}", addr, err))?;
let recipients = vec![recipient]; let recipients = vec![recipient];
match smtp_send(context, &recipients, &body, smtp, msg_id).await { match smtp_send(context, &recipients, &body, smtp, None).await {
SendResult::Success => { SendResult::Success => {
info!(context, "Successfully sent MDN for {msg_id}."); info!(context, "Successfully sent MDN for {rfc724_mid}.");
context context
.sql .sql
.execute("DELETE FROM smtp_mdns WHERE msg_id = ?", (msg_id,)) .execute("DELETE FROM smtp_mdns WHERE rfc724_mid = ?", (rfc724_mid,))
.await?; .await?;
if !additional_msg_ids.is_empty() { if !additional_rfc724_mids.is_empty() {
let q = format!( let q = format!(
"DELETE FROM smtp_mdns WHERE msg_id IN({})", "DELETE FROM smtp_mdns WHERE rfc724_mid IN({})",
sql::repeat_vars(additional_msg_ids.len()) sql::repeat_vars(additional_rfc724_mids.len())
); );
context context
.sql .sql
.execute(&q, rusqlite::params_from_iter(additional_msg_ids)) .execute(&q, rusqlite::params_from_iter(additional_rfc724_mids))
.await?; .await?;
} }
Ok(true) Ok(true)
@@ -777,7 +784,7 @@ async fn send_mdn_msg_id(
SendResult::Retry => { SendResult::Retry => {
info!( info!(
context, context,
"Temporary SMTP failure while sending an MDN for {msg_id}." "Temporary SMTP failure while sending an MDN for {rfc724_mid}."
); );
Ok(false) Ok(false)
} }
@@ -802,40 +809,40 @@ async fn send_mdn(context: &Context, smtp: &mut Smtp) -> Result<bool> {
let Some(msg_row) = context let Some(msg_row) = context
.sql .sql
.query_row_optional( .query_row_optional(
"SELECT msg_id, from_id FROM smtp_mdns ORDER BY retries LIMIT 1", "SELECT rfc724_mid, from_id FROM smtp_mdns ORDER BY retries LIMIT 1",
[], [],
|row| { |row| {
let msg_id: MsgId = row.get(0)?; let rfc724_mid: String = row.get(0)?;
let from_id: ContactId = row.get(1)?; let from_id: ContactId = row.get(1)?;
Ok((msg_id, from_id)) Ok((rfc724_mid, from_id))
}, },
) )
.await? .await?
else { else {
return Ok(false); return Ok(false);
}; };
let (msg_id, contact_id) = msg_row; let (rfc724_mid, contact_id) = msg_row;
context context
.sql .sql
.execute( .execute(
"UPDATE smtp_mdns SET retries=retries+1 WHERE msg_id=?", "UPDATE smtp_mdns SET retries=retries+1 WHERE rfc724_mid=?",
(msg_id,), (rfc724_mid.clone(),),
) )
.await .await
.context("Failed to update MDN retries count")?; .context("Failed to update MDN retries count")?;
match send_mdn_msg_id(context, msg_id, contact_id, smtp).await { match send_mdn_rfc724_mid(context, &rfc724_mid, contact_id, smtp).await {
Err(err) => { Err(err) => {
// If there is an error, for example there is no message corresponding to the msg_id in the // If there is an error, for example there is no message corresponding to the msg_id in the
// database, do not try to send this MDN again. // database, do not try to send this MDN again.
warn!( warn!(
context, context,
"Error sending MDN for {msg_id}, removing it: {err:#}." "Error sending MDN for {rfc724_mid}, removing it: {err:#}."
); );
context context
.sql .sql
.execute("DELETE FROM smtp_mdns WHERE msg_id = ?", (msg_id,)) .execute("DELETE FROM smtp_mdns WHERE rfc724_mid = ?", (rfc724_mid,))
.await?; .await?;
Err(err) Err(err)
} }

View File

@@ -578,7 +578,7 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
if dbversion < 90 { if dbversion < 90 {
sql.execute_migration( sql.execute_migration(
r#"CREATE TABLE smtp_mdns ( r#"CREATE TABLE smtp_mdns (
msg_id INTEGER NOT NULL, -- id of the message in msgs table which requested MDN msg_id INTEGER NOT NULL, -- id of the message in msgs table which requested MDN (DEPRECATED 2024-06-21)
from_id INTEGER NOT NULL, -- id of the contact that sent the message, MDN destination from_id INTEGER NOT NULL, -- id of the contact that sent the message, MDN destination
rfc724_mid TEXT NOT NULL, -- Message-ID header rfc724_mid TEXT NOT NULL, -- Message-ID header
retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send MDN retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send MDN

View File

@@ -83,9 +83,6 @@ pub enum StockMessage {
#[strum(props(fallback = "Return receipt"))] #[strum(props(fallback = "Return receipt"))]
ReadRcpt = 31, ReadRcpt = 31,
#[strum(props(fallback = "This is a return receipt for the message \"%1$s\"."))]
ReadRcptMailBody = 32,
#[strum(props(fallback = "End-to-end encryption preferred"))] #[strum(props(fallback = "End-to-end encryption preferred"))]
E2ePreferred = 34, E2ePreferred = 34,
@@ -443,6 +440,9 @@ pub enum StockMessage {
fallback = "Could not yet establish guaranteed end-to-end encryption, but you may already send a message." fallback = "Could not yet establish guaranteed end-to-end encryption, but you may already send a message."
))] ))]
SecurejoinWaitTimeout = 191, SecurejoinWaitTimeout = 191,
#[strum(props(fallback = "This message is a receipt notification."))]
ReadRcptMailBody = 192,
} }
impl StockMessage { impl StockMessage {
@@ -770,11 +770,6 @@ pub(crate) async fn gif(context: &Context) -> String {
translated(context, StockMessage::Gif).await translated(context, StockMessage::Gif).await
} }
/// Stock string: `Encrypted message`.
pub(crate) async fn encrypted_msg(context: &Context) -> String {
translated(context, StockMessage::EncryptedMsg).await
}
/// Stock string: `End-to-end encryption available.`. /// Stock string: `End-to-end encryption available.`.
pub(crate) async fn e2e_available(context: &Context) -> String { pub(crate) async fn e2e_available(context: &Context) -> String {
translated(context, StockMessage::E2eAvailable).await translated(context, StockMessage::E2eAvailable).await
@@ -805,11 +800,9 @@ pub(crate) async fn read_rcpt(context: &Context) -> String {
translated(context, StockMessage::ReadRcpt).await translated(context, StockMessage::ReadRcpt).await
} }
/// Stock string: `This is a return receipt for the message "%1$s".`. /// Stock string: `This message is a receipt notification.`.
pub(crate) async fn read_rcpt_mail_body(context: &Context, message: &str) -> String { pub(crate) async fn read_rcpt_mail_body(context: &Context) -> String {
translated(context, StockMessage::ReadRcptMailBody) translated(context, StockMessage::ReadRcptMailBody).await
.await
.replace1(message)
} }
/// Stock string: `Group image deleted.`. /// Stock string: `Group image deleted.`.

View File

@@ -542,7 +542,7 @@ async fn test_mdn_doesnt_disable_verification() -> Result<()> {
let rcvd = tcm.send_recv_accept(&alice, &bob, "Heyho").await; let rcvd = tcm.send_recv_accept(&alice, &bob, "Heyho").await;
message::markseen_msgs(&bob, vec![rcvd.id]).await?; message::markseen_msgs(&bob, vec![rcvd.id]).await?;
let mimefactory = MimeFactory::from_mdn(&bob, &rcvd, vec![]).await?; let mimefactory = MimeFactory::from_mdn(&bob, rcvd.from_id, rcvd.rfc724_mid, vec![]).await?;
let rendered_msg = mimefactory.render(&bob).await?; let rendered_msg = mimefactory.render(&bob).await?;
let body = rendered_msg.message; let body = rendered_msg.message;
receive_imf(&alice, body.as_bytes(), false).await.unwrap(); receive_imf(&alice, body.as_bytes(), false).await.unwrap();