Skip to content

Commit 18dede1

Browse files
committed
fix: Receiving of re-sent locally deleted messages (#7115)
Before, locally deleted messages (because of DeleteDeviceAfter set or external deletion requests) couldn't be received if they're re-sent because tombstones prevented that forever. This is critical for webxdcs.
1 parent 37e0aae commit 18dede1

File tree

4 files changed

+64
-35
lines changed

4 files changed

+64
-35
lines changed

src/imap.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,8 +2330,11 @@ pub(crate) async fn prefetch_should_download(
23302330
pub(crate) fn is_dup_msg(is_chat_msg: bool, ts_sent: i64, ts_sent_old: i64) -> bool {
23312331
// If the existing message has timestamp_sent == 0, that means we don't know its actual sent
23322332
// timestamp, so don't delete the new message. E.g. outgoing messages have zero timestamp_sent
2333-
// because they are stored to the db before sending. Also consider as duplicates only messages
2334-
// with greater timestamp to avoid deleting both messages in a multi-device setting.
2333+
// because they are stored to the db before sending. Trashed messages also have zero
2334+
// timestamp_sent and mustn't make new messages "duplicates", otherwise if a webxdc message is
2335+
// deleted because of DeleteDeviceAfter set, it won't be recovered from a re-sent message. Also
2336+
// consider as duplicates only messages with greater timestamp to avoid deleting both messages
2337+
// in a multi-device setting.
23352338
is_chat_msg && ts_sent_old != 0 && ts_sent > ts_sent_old
23362339
}
23372340

src/message.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,8 +1823,9 @@ pub async fn delete_msgs_ex(
18231823
}
18241824

18251825
/// Removes from the database a locally deleted message that also doesn't have a server UID.
1826-
pub(crate) async fn prune_tombstone(context: &Context, rfc724_mid: &str) -> Result<()> {
1827-
context
1826+
/// Returns whether the removal happened.
1827+
pub(crate) async fn prune_tombstone(context: &Context, rfc724_mid: &str) -> Result<bool> {
1828+
Ok(context
18281829
.sql
18291830
.execute(
18301831
"DELETE FROM msgs
@@ -1835,8 +1836,8 @@ pub(crate) async fn prune_tombstone(context: &Context, rfc724_mid: &str) -> Resu
18351836
)",
18361837
(rfc724_mid, DC_CHAT_ID_TRASH),
18371838
)
1838-
.await?;
1839-
Ok(())
1839+
.await?
1840+
> 0)
18401841
}
18411842

18421843
/// Marks requested messages as seen.

src/receive_imf.rs

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -560,10 +560,13 @@ pub(crate) async fn receive_imf_inner(
560560
return Ok(None);
561561
}
562562
let msg = Message::load_from_db_optional(context, old_msg_id).await?;
563-
if msg.is_none() {
564-
message::prune_tombstone(context, rfc724_mid).await?;
563+
// The tombstone being pruned means that we expected the message to appear on IMAP after
564+
// deletion. NB: Not all such messages have `msgs.deleted=1`, see how external deletion
565+
// requests deal with message reordering.
566+
match msg.is_none() && !message::prune_tombstone(context, rfc724_mid).await? {
567+
true => replace_msg_id = None,
568+
false => replace_msg_id = Some(old_msg_id),
565569
}
566-
replace_msg_id = Some(old_msg_id);
567570
if let Some(msg) = msg.filter(|msg| msg.download_state() != DownloadState::Done) {
568571
// the message was partially downloaded before and is fully downloaded now.
569572
info!(
@@ -574,32 +577,39 @@ pub(crate) async fn receive_imf_inner(
574577
} else {
575578
replace_chat_id = None;
576579
}
577-
} else {
578-
replace_msg_id = if rfc724_mid_orig == rfc724_mid {
579-
None
580-
} else if let Some((old_msg_id, old_ts_sent)) =
581-
message::rfc724_mid_exists(context, rfc724_mid_orig).await?
582-
{
583-
message::prune_tombstone(context, rfc724_mid_orig).await?;
584-
if imap::is_dup_msg(
585-
mime_parser.has_chat_version(),
586-
mime_parser.timestamp_sent,
587-
old_ts_sent,
588-
) {
589-
info!(context, "Deleting duplicate message {rfc724_mid_orig}.");
590-
let target = context.get_delete_msgs_target().await?;
591-
context
592-
.sql
593-
.execute(
594-
"UPDATE imap SET target=? WHERE folder=? AND uidvalidity=? AND uid=?",
595-
(target, folder, uidvalidity, uid),
596-
)
597-
.await?;
598-
}
599-
Some(old_msg_id)
580+
} else if rfc724_mid_orig == rfc724_mid {
581+
replace_msg_id = None;
582+
replace_chat_id = None;
583+
} else if let Some((old_msg_id, old_ts_sent, is_trash)) = message::rfc724_mid_exists_ex(
584+
context,
585+
rfc724_mid_orig,
586+
"chat_id=3", // Trash
587+
)
588+
.await?
589+
{
590+
if is_trash && !message::prune_tombstone(context, rfc724_mid_orig).await? {
591+
replace_msg_id = None;
592+
} else if imap::is_dup_msg(
593+
mime_parser.has_chat_version(),
594+
mime_parser.timestamp_sent,
595+
old_ts_sent,
596+
) {
597+
info!(context, "Deleting duplicate message {rfc724_mid_orig}.");
598+
let target = context.get_delete_msgs_target().await?;
599+
context
600+
.sql
601+
.execute(
602+
"UPDATE imap SET target=? WHERE folder=? AND uidvalidity=? AND uid=?",
603+
(target, folder, uidvalidity, uid),
604+
)
605+
.await?;
606+
replace_msg_id = Some(old_msg_id);
600607
} else {
601-
None
602-
};
608+
replace_msg_id = Some(old_msg_id);
609+
}
610+
replace_chat_id = None;
611+
} else {
612+
replace_msg_id = None;
603613
replace_chat_id = None;
604614
}
605615

src/webxdc/webxdc_tests.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1853,6 +1853,14 @@ async fn test_status_update_vs_delete_device_after() -> Result<()> {
18531853
.await?;
18541854
let alice_chat = alice.create_chat(bob).await;
18551855
let alice_instance = send_webxdc_instance(alice, alice_chat.id).await?;
1856+
// Needed to test receiving of re-sent locally deleted webxdc.
1857+
bob.sql
1858+
.execute(
1859+
"INSERT INTO imap (rfc724_mid, folder, uid, uidvalidity, target)
1860+
VALUES (?1, ?2, ?3, ?4, ?5)",
1861+
(&alice_instance.rfc724_mid, "INBOX", 1, 1, "INBOX"),
1862+
)
1863+
.await?;
18561864
let bob_instance = bob.recv_msg(&alice.pop_sent_msg().await).await;
18571865
assert_eq!(bob.add_or_lookup_contact(alice).await.is_bot(), false);
18581866

@@ -1882,8 +1890,15 @@ async fn test_status_update_vs_delete_device_after() -> Result<()> {
18821890
SystemTime::shift(Duration::from_secs(2700));
18831891
ephemeral::delete_expired_messages(bob, tools::time()).await?;
18841892
let bob_instance = Message::load_from_db(bob, bob_instance.id).await?;
1885-
assert_eq!(bob_instance.chat_id.is_trash(), false);
18861893

1894+
SystemTime::shift(Duration::from_secs(1800));
1895+
ephemeral::delete_expired_messages(bob, tools::time()).await?;
1896+
let bob_instance = Message::load_from_db_optional(bob, bob_instance.id).await?;
1897+
assert!(bob_instance.is_none());
1898+
1899+
// Additionally test that a re-sent instance can be received after deletion.
1900+
resend_msgs(alice, &[alice_instance.id]).await?;
1901+
bob.recv_msg(&alice.pop_sent_msg().await).await;
18871902
Ok(())
18881903
}
18891904

0 commit comments

Comments
 (0)