diff --git a/src/message.rs b/src/message.rs index 2711c5bf5..05f602be6 100644 --- a/src/message.rs +++ b/src/message.rs @@ -757,7 +757,7 @@ impl Message { self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0 } - /// Returns true if message is Auto-Submitted. + /// Returns true if message is auto-generated. pub fn is_bot(&self) -> bool { self.param.get_bool(Param::Bot).unwrap_or_default() } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index ba22efcd6..36d38277f 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -117,8 +117,8 @@ pub(crate) struct MimeMessage { /// Hop info for debugging. pub(crate) hop_info: String, - /// Whether the contact sending this should be marked as bot. - pub(crate) is_bot: bool, + /// Whether the contact sending this should be marked as bot or non-bot. + pub(crate) is_bot: Option, /// When the message was received, in secs since epoch. pub(crate) timestamp_rcvd: i64, @@ -394,9 +394,6 @@ impl MimeMessage { } } - // Auto-submitted is also set by holiday-notices so we also check `chat-version` - let is_bot = headers.contains_key("auto-submitted") && headers.contains_key("chat-version"); - let timestamp_rcvd = smeared_time(context); let timestamp_sent = headers .get(HeaderDef::Date.get_headername()) @@ -431,7 +428,7 @@ impl MimeMessage { is_mime_modified: false, decoded_data: Vec::new(), hop_info, - is_bot, + is_bot: None, timestamp_rcvd, timestamp_sent, }; @@ -464,6 +461,13 @@ impl MimeMessage { }, }; + if parser.mdn_reports.is_empty() { + // "Auto-Submitted" is also set by holiday-notices so we also check "chat-version". + let is_bot = parser.headers.get("auto-submitted") + == Some(&"auto-generated".to_string()) + && parser.headers.contains_key("chat-version"); + parser.is_bot = Some(is_bot); + } parser.maybe_remove_bad_parts(); parser.maybe_remove_inline_mailinglist_footer(); parser.heuristically_parse_ndn(context).await; @@ -702,7 +706,7 @@ impl MimeMessage { self.do_add_single_part(part); } - if self.headers.contains_key("auto-submitted") { + if self.is_bot == Some(true) { for part in &mut self.parts { part.param.set(Param::Bot, "1"); } @@ -2740,6 +2744,7 @@ Chat-Version: 1.0\n\ Message-ID: \n\ To: Alice \n\ From: Bob \n\ +Auto-Submitted: auto-replied\n\ Content-Type: multipart/report; report-type=disposition-notification;\n\t\ boundary=\"kJBbU58X1xeWNHgBtTbMk80M5qnV4N\"\n\ \n\ @@ -2775,6 +2780,7 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\ assert_eq!(message.parts.len(), 1); assert_eq!(message.mdn_reports.len(), 1); + assert_eq!(message.is_bot, None); } /// Test parsing multiple MDNs combined in a single message. diff --git a/src/param.rs b/src/param.rs index 6465c4443..4519ce598 100644 --- a/src/param.rs +++ b/src/param.rs @@ -65,7 +65,7 @@ pub enum Param { /// For Messages: the message is a reaction. Reaction = b'x', - /// For Messages: a message with Auto-Submitted header ("bot"). + /// For Messages: a message with "Auto-Submitted: auto-generated" header ("bot"). Bot = b'b', /// For Messages: unset or 0=not forwarded, diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 93f24fe0f..a4e9d516f 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -539,7 +539,9 @@ pub(crate) async fn receive_imf_inner( .handle_reports(context, from_id, &mime_parser.parts) .await; - from_id.mark_bot(context, mime_parser.is_bot).await?; + if let Some(is_bot) = mime_parser.is_bot { + from_id.mark_bot(context, is_bot).await?; + } Ok(Some(received_msg)) } diff --git a/src/receive_imf/tests.rs b/src/receive_imf/tests.rs index a9c912714..85bc39555 100644 --- a/src/receive_imf/tests.rs +++ b/src/receive_imf/tests.rs @@ -2684,6 +2684,36 @@ async fn test_read_receipts_dont_create_chats() -> Result<()> { Ok(()) } +/// Test that read receipts don't unmark contacts as bots. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_read_receipts_dont_unmark_bots() -> Result<()> { + let alice = &TestContext::new_alice().await; + let bob = &TestContext::new_bob().await; + let ab_contact = alice.add_or_lookup_contact(bob).await; + ab_contact.id.mark_bot(alice, true).await?; + let alice_chat = alice.create_chat(bob).await; + + // Alice sends and Bob receives a message. + bob.recv_msg(&alice.send_text(alice_chat.id, "Message").await) + .await; + let received_msg = bob.get_last_msg().await; + + // Bob sends a read receipt. + let mdn_mimefactory = + crate::mimefactory::MimeFactory::from_mdn(bob, &received_msg, vec![]).await?; + let rendered_mdn = mdn_mimefactory.render(bob).await?; + let mdn_body = rendered_mdn.message; + + // Alice receives the read receipt. + receive_imf(alice, mdn_body.as_bytes(), false).await?; + let msg = alice.get_last_msg_in(alice_chat.id).await; + assert_eq!(msg.state, MessageState::OutMdnRcvd); + let ab_contact = alice.add_or_lookup_contact(bob).await; + assert!(ab_contact.is_bot()); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_gmx_forwarded_msg() -> Result<()> { let t = TestContext::new_alice().await;