Skip to content

Commit 8b77749

Browse files
committed
feat: send pre-message on messages with large attachments
1 parent cc653dc commit 8b77749

File tree

4 files changed

+152
-10
lines changed

4 files changed

+152
-10
lines changed

src/chat.rs

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ use crate::constants::{
2727
use crate::contact::{self, Contact, ContactId, Origin};
2828
use crate::context::Context;
2929
use crate::debug_logging::maybe_set_logging_xdc;
30-
use crate::download::DownloadState;
30+
use crate::download::{
31+
DownloadState, PRE_MESSAGE_ATTACHMENT_SIZE_THRESHOLD, PRE_MESSAGE_SIZE_WARNING_THRESHOLD,
32+
};
3133
use crate::ephemeral::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
3234
use crate::events::EventType;
3335
use crate::key::self_fingerprint;
3436
use crate::location;
3537
use crate::log::{LogExt, error, info, warn};
3638
use crate::logged_debug_assert;
3739
use crate::message::{self, Message, MessageState, MsgId, Viewtype};
38-
use crate::mimefactory::MimeFactory;
40+
use crate::mimefactory::{MimeFactory, RenderedEmail};
3941
use crate::mimeparser::SystemMessage;
4042
use crate::param::{Param, Params};
4143
use crate::receive_imf::ReceivedMsg;
@@ -2804,7 +2806,47 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
28042806
return Ok(Vec::new());
28052807
}
28062808

2807-
let rendered_msg = match mimefactory.render(context).await {
2809+
// render message and pre message.
2810+
// pre message is a small message with metadata
2811+
// which announces a larger message. Large messages are not downloaded in the background.
2812+
2813+
let needs_pre_message = msg.viewtype.has_file()
2814+
&& msg
2815+
.get_filebytes(context)
2816+
.await?
2817+
.context("filebytes not available, even though message has attachment")?
2818+
> PRE_MESSAGE_ATTACHMENT_SIZE_THRESHOLD;
2819+
2820+
let render_result: Result<(RenderedEmail, Option<RenderedEmail>)> = async {
2821+
if needs_pre_message {
2822+
let mut mimefactory_full_msg = mimefactory.clone();
2823+
mimefactory_full_msg.set_as_full_message();
2824+
let rendered_msg = mimefactory_full_msg.render(context).await?;
2825+
2826+
let mut mimefactory_pre_msg = mimefactory;
2827+
mimefactory_pre_msg.set_as_pre_message_for(&rendered_msg);
2828+
let rendered_pre_msg = mimefactory_pre_msg
2829+
.render(context)
2830+
.await
2831+
.context("pre-message failed to render")?;
2832+
2833+
if rendered_pre_msg.message.len() > PRE_MESSAGE_SIZE_WARNING_THRESHOLD {
2834+
warn!(
2835+
context,
2836+
"pre message for message (MsgId={}) is larger than expected: {}",
2837+
msg.id,
2838+
rendered_pre_msg.message.len()
2839+
);
2840+
}
2841+
2842+
Ok((rendered_msg, Some(rendered_pre_msg)))
2843+
} else {
2844+
Ok((mimefactory.render(context).await?, None))
2845+
}
2846+
}
2847+
.await;
2848+
2849+
let (rendered_msg, rendered_pre_msg) = match render_result {
28082850
Ok(res) => Ok(res),
28092851
Err(err) => {
28102852
message::set_msg_failed(context, msg, &err.to_string()).await?;
@@ -2825,7 +2867,7 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
28252867
msg.id,
28262868
needs_encryption
28272869
);
2828-
}
2870+
};
28292871

28302872
let now = smeared_time(context);
28312873

@@ -2870,12 +2912,26 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
28702912
} else {
28712913
for recipients_chunk in recipients.chunks(chunk_size) {
28722914
let recipients_chunk = recipients_chunk.join(" ");
2915+
// send pre-message before actual message
2916+
if let Some(pre_msg) = &rendered_pre_msg {
2917+
let row_id = t.execute(
2918+
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
2919+
VALUES (?1, ?2, ?3, ?4)",
2920+
(
2921+
&pre_msg.rfc724_mid,
2922+
&recipients_chunk,
2923+
&pre_msg.message,
2924+
msg.id, // TODO: check if this is correct or we need another id here?
2925+
),
2926+
)?;
2927+
row_ids.push(row_id.try_into()?);
2928+
}
28732929
let row_id = t.execute(
28742930
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
28752931
VALUES (?1, ?2, ?3, ?4)",
28762932
(
28772933
&rendered_msg.rfc724_mid,
2878-
recipients_chunk,
2934+
&recipients_chunk,
28792935
&rendered_msg.message,
28802936
msg.id,
28812937
),

src/download.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,20 @@ use crate::{EventType, chatlist_events};
1818
/// `MIN_DELETE_SERVER_AFTER` increases the timeout in this case.
1919
pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60;
2020

21+
/// From this point onward outgoing messages are considered large
22+
/// and get a pre-message, which announces the full message.
23+
// this is only about sending so we can modify it any time.
24+
pub(crate) const PRE_MESSAGE_ATTACHMENT_SIZE_THRESHOLD: u64 = 140_000;
25+
26+
/// Max message size to be fetched in the background.
27+
/// This limit defines what messages are fully fetched in the background.
28+
/// This is for all messages that don't have the full message header.
29+
pub(crate) const MAX_FETCH_MSG_SIZE: usize = 1_000_000;
30+
31+
/// Max size for pre messages. A warning is emitted when this is exceeded.
32+
/// Should be well below `MAX_FETCH_MSG_SIZE`
33+
pub(crate) const PRE_MESSAGE_SIZE_WARNING_THRESHOLD: usize = 150_000;
34+
2135
/// Download state of the message.
2236
#[derive(
2337
Debug,

src/headerdef.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ pub enum HeaderDef {
102102
/// used to encrypt and decrypt messages.
103103
/// This secret is sent to a new member in the member-addition message.
104104
ChatBroadcastSecret,
105+
/// This message announces a bigger message with attachment that is refereced by rfc724_mid.
106+
#[strum(serialize = "Chat-Full-Message-ID")] // correct casing
107+
ChatFullMessageId,
108+
109+
/// This message has a pre-message
110+
/// and thus this message can be skipped while fetching messages.
111+
/// This is a cleartext / unproteced header.
112+
#[strum(serialize = "Chat-Is-Full-Message")] // correct casing
113+
ChatIsFullMessage,
105114

106115
/// [Autocrypt](https://autocrypt.org/) header.
107116
Autocrypt,
@@ -194,4 +203,17 @@ mod tests {
194203
);
195204
assert_eq!(headers.get_header_value(HeaderDef::Autocrypt), None);
196205
}
206+
207+
#[test]
208+
/// Tests that headers have correct casing for sending.
209+
fn header_name_correct_casing_for_sending() {
210+
assert_eq!(
211+
HeaderDef::ChatFullMessageId.get_headername(),
212+
"Chat-Full-Message-ID"
213+
);
214+
assert_eq!(
215+
HeaderDef::ChatIsFullMessage.get_headername(),
216+
"Chat-Is-Full-Message"
217+
);
218+
}
197219
}

src/mimefactory.rs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ pub enum Loaded {
5858
},
5959
}
6060

61+
#[derive(Debug, Clone, PartialEq)]
62+
pub enum PreMessageMode {
63+
/// adds a is full message header in unpretected part
64+
FullMessage,
65+
/// adds reference to full message to protected part
66+
/// also adds metadata and hashes and explicitly excludes attachment
67+
PreMessage { full_msg_rfc724_mid: String },
68+
}
69+
6170
/// Helper to construct mime messages.
6271
#[derive(Debug, Clone)]
6372
pub struct MimeFactory {
@@ -145,6 +154,9 @@ pub struct MimeFactory {
145154

146155
/// This field is used to sustain the topic id of webxdcs needed for peer channels.
147156
webxdc_topic: Option<TopicId>,
157+
158+
/// This field is used when this is either a pre-message or a full-message.
159+
pre_message_mode: Option<PreMessageMode>,
148160
}
149161

150162
/// Result of rendering a message, ready to be submitted to a send job.
@@ -498,6 +510,7 @@ impl MimeFactory {
498510
sync_ids_to_delete: None,
499511
attach_selfavatar,
500512
webxdc_topic,
513+
pre_message_mode: None,
501514
};
502515
Ok(factory)
503516
}
@@ -546,6 +559,7 @@ impl MimeFactory {
546559
sync_ids_to_delete: None,
547560
attach_selfavatar: false,
548561
webxdc_topic: None,
562+
pre_message_mode: None,
549563
};
550564

551565
Ok(res)
@@ -778,7 +792,10 @@ impl MimeFactory {
778792
headers.push(("Date", mail_builder::headers::raw::Raw::new(date).into()));
779793

780794
let rfc724_mid = match &self.loaded {
781-
Loaded::Message { msg, .. } => msg.rfc724_mid.clone(),
795+
Loaded::Message { msg, .. } => match &self.pre_message_mode {
796+
Some(PreMessageMode::PreMessage { .. }) => create_outgoing_rfc724_mid(),
797+
_ => msg.rfc724_mid.clone(),
798+
},
782799
Loaded::Mdn { .. } => create_outgoing_rfc724_mid(),
783800
};
784801
headers.push((
@@ -980,6 +997,22 @@ impl MimeFactory {
980997
"MIME-Version",
981998
mail_builder::headers::raw::Raw::new("1.0").into(),
982999
));
1000+
1001+
if self.pre_message_mode == Some(PreMessageMode::FullMessage) {
1002+
unprotected_headers.push((
1003+
HeaderDef::ChatIsFullMessage.get_headername(),
1004+
mail_builder::headers::raw::Raw::new("1").into(),
1005+
));
1006+
} else if let Some(PreMessageMode::PreMessage {
1007+
full_msg_rfc724_mid,
1008+
}) = self.pre_message_mode.clone()
1009+
{
1010+
unprotected_headers.push((
1011+
HeaderDef::ChatFullMessageId.get_headername(),
1012+
mail_builder::headers::raw::Raw::new(full_msg_rfc724_mid).into(),
1013+
));
1014+
}
1015+
9831016
for header @ (original_header_name, _header_value) in &headers {
9841017
let header_name = original_header_name.to_lowercase();
9851018
if header_name == "message-id" {
@@ -1111,7 +1144,10 @@ impl MimeFactory {
11111144
for (addr, key) in &encryption_pubkeys {
11121145
let fingerprint = key.dc_fingerprint().hex();
11131146
let cmd = msg.param.get_cmd();
1114-
let should_do_gossip = cmd == SystemMessage::MemberAddedToGroup
1147+
let is_full_msg =
1148+
self.pre_message_mode != Some(PreMessageMode::FullMessage);
1149+
let should_do_gossip = is_full_msg
1150+
&& cmd == SystemMessage::MemberAddedToGroup
11151151
|| cmd == SystemMessage::SecurejoinMessage
11161152
|| multiple_recipients && {
11171153
let gossiped_timestamp: Option<i64> = context
@@ -1837,8 +1873,12 @@ impl MimeFactory {
18371873

18381874
// add attachment part
18391875
if msg.viewtype.has_file() {
1840-
let file_part = build_body_file(context, &msg).await?;
1841-
parts.push(file_part);
1876+
if let Some(PreMessageMode::PreMessage { .. }) = self.pre_message_mode {
1877+
// TODO: generate thumbnail and attach it instead (if it makes sense)
1878+
} else {
1879+
let file_part = build_body_file(context, &msg).await?;
1880+
parts.push(file_part);
1881+
}
18421882
}
18431883

18441884
if let Some(msg_kml_part) = self.get_message_kml_part() {
@@ -1883,7 +1923,7 @@ impl MimeFactory {
18831923
}
18841924
}
18851925

1886-
if self.attach_selfavatar {
1926+
if self.attach_selfavatar && self.pre_message_mode != Some(PreMessageMode::FullMessage) {
18871927
match context.get_config(Config::Selfavatar).await? {
18881928
Some(path) => match build_avatar_file(context, &path).await {
18891929
Ok(avatar) => headers.push((
@@ -1952,6 +1992,16 @@ impl MimeFactory {
19521992

19531993
Ok(message)
19541994
}
1995+
1996+
pub fn set_as_full_message(&mut self) {
1997+
self.pre_message_mode = Some(PreMessageMode::FullMessage);
1998+
}
1999+
2000+
pub fn set_as_pre_message_for(&mut self, full_message: &RenderedEmail) {
2001+
self.pre_message_mode = Some(PreMessageMode::PreMessage {
2002+
full_msg_rfc724_mid: full_message.rfc724_mid.clone(),
2003+
});
2004+
}
19552005
}
19562006

19572007
fn hidden_recipients() -> Address<'static> {

0 commit comments

Comments
 (0)