From 370cc5c1fdd88d675f81172bca1bda3aedb6785e Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 22 Aug 2026 02:49:03 +0200 Subject: [PATCH] fix: trash early MDNs that reference no message A report referencing no message can never be applied to one, so it must not create a contact, a chat or a `last_seen` update on its way to the trash. --- src/receive_imf.rs | 12 +++++++++ src/receive_imf/receive_imf_tests.rs | 27 +++++++++++++++++++ .../message/mdn_without_message_reference.eml | 23 ++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 test-data/message/mdn_without_message_reference.eml diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 9a0df5e2d..4dd51d59b 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -506,6 +506,18 @@ pub(crate) async fn receive_imf_inner( Ok(mime_parser) => mime_parser, }; + if !mime_parser.mdn_reports.is_empty() + && mime_parser.mdn_reports.iter().all(|report| { + report.original_message_id.is_none() && report.additional_message_ids.is_empty() + }) + { + // A report naming no message can never be applied to one, + // and nothing else should come out of it: no contact, no chat, + // and no `last_seen` update lighting up an online dot. + info!(context, "Report without message reference (TRASH)."); + return trash().await; + } + if !mime_parser.was_encrypted() && mime_parser.get_header(HeaderDef::SecureJoin).is_none() && context.get_config_bool(Config::ForceEncryption).await? diff --git a/src/receive_imf/receive_imf_tests.rs b/src/receive_imf/receive_imf_tests.rs index 3808a3c1c..1c6ad18dd 100644 --- a/src/receive_imf/receive_imf_tests.rs +++ b/src/receive_imf/receive_imf_tests.rs @@ -271,6 +271,33 @@ async fn test_mdn_and_alias() -> Result<()> { Ok(()) } +/// Tests that an MDN referencing no message is trashed early: +/// there is nothing it could ever be applied to, +/// so it must not create a contact or a chat on the way. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_mdn_without_message_reference() -> Result<()> { + let alice = TestContext::new_alice().await; + alice + .set_config_bool(Config::ForceEncryption, false) + .await?; + let contacts = Contact::get_real_cnt(&alice).await?; + let chatlist_len = Chatlist::try_load(&alice, 0, None, None).await?.len(); + + receive_imf( + &alice, + include_bytes!("../../test-data/message/mdn_without_message_reference.eml"), + false, + ) + .await?; + + assert_eq!(Contact::get_real_cnt(&alice).await?, contacts); + assert_eq!( + Chatlist::try_load(&alice, 0, None, None).await?.len(), + chatlist_len + ); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_no_from() { // if there is no from given, from_id stays 0 which is just fine. These messages diff --git a/test-data/message/mdn_without_message_reference.eml b/test-data/message/mdn_without_message_reference.eml new file mode 100644 index 000000000..14591ec95 --- /dev/null +++ b/test-data/message/mdn_without_message_reference.eml @@ -0,0 +1,23 @@ +From: bob@example.net +To: alice@example.org +Subject: message opened +Date: Sun, 22 Mar 2020 23:37:57 +0000 +Message-ID: +Content-Type: multipart/report; report-type=disposition-notification; boundary="SNIPP" + + +--SNIPP +Content-Type: text/plain; charset=utf-8 + +Read receipts do not guarantee sth. was read. + + +--SNIPP +Content-Type: message/disposition-notification + +Original-Recipient: rfc822;bob@example.net +Final-Recipient: rfc822;bob@example.net +Disposition: automatic-action/MDN-sent-automatically; processed + + +--SNIPP-- \ No newline at end of file