From 48df2e7e2e78e009ada6fa73a8e3f7d5b79ccb72 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 2 Aug 2026 17:43:15 +0200 Subject: [PATCH 1/5] fix: recognize self addresses in various places (instead of just the "primary") --- src/chat.rs | 10 +++++----- src/contact.rs | 6 +----- src/contact/contact_tests.rs | 14 ++++++++++++++ src/mimefactory.rs | 7 +------ src/mimefactory/mimefactory_tests.rs | 16 ++++++++++++++++ src/receive_imf.rs | 16 +++++++--------- 6 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 8a0ea1cc9f..a3a6b10761 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -10,7 +10,9 @@ use std::time::Duration; use anyhow::{Context as _, Result, anyhow, bail, ensure}; use chrono::TimeZone; -use deltachat_contact_tools::{ContactAddress, sanitize_bidi_characters, sanitize_single_line}; +use deltachat_contact_tools::{ + ContactAddress, addr_cmp, sanitize_bidi_characters, sanitize_single_line, +}; use humansize::{BINARY, format_size}; use mail_builder::mime::MimePart; use serde::{Deserialize, Serialize}; @@ -2851,10 +2853,8 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) - let attach_selfavatar = mimefactory.attach_selfavatar; let mut recipients = mimefactory.recipients(); - let from = context.get_primary_self_addr().await?; - let lowercase_from = from.to_lowercase(); - - recipients.retain(|x| x.to_lowercase() != lowercase_from); + let self_addrs = context.get_all_self_addrs().await?; + recipients.retain(|x| !self_addrs.iter().any(|a| addr_cmp(a, x))); // Default Webxdc integrations are hidden messages and must not be sent out: if (msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden) diff --git a/src/contact.rs b/src/contact.rs index 47bc5c3073..728c5ed498 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1230,17 +1230,13 @@ ORDER BY c.origin>=? DESC, c.last_seen DESC, c.id DESC .await?; if let Some(query) = query { - let self_addr = context - .get_config(Config::ConfiguredAddr) - .await? - .unwrap_or_default(); let self_name = context .get_config(Config::Displayname) .await? .unwrap_or_default(); let self_name2 = stock_str::self_msg(context); - if self_addr.contains(query) + if self_addrs.iter().any(|a| a.contains(query)) || self_name.contains(query) || self_name2.contains(query) { diff --git a/src/contact/contact_tests.rs b/src/contact/contact_tests.rs index 44a62253fd..d7442f0465 100644 --- a/src/contact/contact_tests.rs +++ b/src/contact/contact_tests.rs @@ -152,6 +152,20 @@ async fn test_get_contacts() -> Result<()> { assert_eq!(contacts.len(), 1); let contacts = Contact::get_all(&context, 0, Some("δ")).await?; assert_eq!(contacts.len(), 1); + + // Searching for a secondary self address finds "Me", + // even if the transport is unpublished. + crate::transport::add_pseudo_transport(&context, "bob@second.example").await?; + context + .set_transport_unpublished("bob@second.example", true) + .await?; + let contacts = Contact::get_all( + &context, + constants::DC_GCL_ADD_SELF, + Some("bob@second.example"), + ) + .await?; + assert_eq!(contacts, vec![ContactId::SELF]); Ok(()) } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 7abae86212..ff991cdc57 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1421,12 +1421,7 @@ impl MimeFactory { let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default(); let fingerprint_to_remove = msg.param.get(Param::Arg4).unwrap_or_default(); - if email_to_remove - == context - .get_config(Config::ConfiguredAddr) - .await? - .unwrap_or_default() - { + if context.is_self_addr(email_to_remove).await? { placeholdertext = Some(format!("{email_to_remove} left the group.")); } else { placeholdertext = Some(format!("Member {email_to_remove} was removed.")); diff --git a/src/mimefactory/mimefactory_tests.rs b/src/mimefactory/mimefactory_tests.rs index 937fdacbd6..2cc93b0c63 100644 --- a/src/mimefactory/mimefactory_tests.rs +++ b/src/mimefactory/mimefactory_tests.rs @@ -747,6 +747,22 @@ async fn test_remove_member_bcc() -> Result<()> { } } + // A member whose address became a secondary self address is treated + // as ourselves on removal and rendered as "left the group". + let secondary_self_addr = "alice@second.example"; + let secondary_self_id = crate::contact::Contact::create(alice, "", secondary_self_addr).await?; + add_contact_to_chat(alice, alice_chat_id, secondary_self_id).await?; + alice.pop_sent_msg().await; + crate::transport::add_pseudo_transport(alice, secondary_self_addr).await?; + remove_contact_from_chat(alice, alice_chat_id, secondary_self_id).await?; + let remove = alice.pop_sent_msg().await; + assert!( + remove + .payload() + .contains("alice@second.example left the group.") + ); + assert!(!remove.payload().contains("was removed")); + Ok(()) } diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 8d8144e48f..dfd0b7cef4 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -3177,15 +3177,13 @@ async fn apply_group_changes( .await?; } else { let mut new_members: BTreeSet; - // True if a Delta Chat client has explicitly and really added our primary address to an - // already existing group. - let self_added = - if let Some(added_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) { - addr_cmp(&context.get_primary_self_addr().await?, added_addr) - && !chat_contacts.contains(&ContactId::SELF) - } else { - false - }; + let self_added = if let Some(added_addr) = + mime_parser.get_header(HeaderDef::ChatGroupMemberAdded) + { + context.is_self_addr(added_addr).await? && !chat_contacts.contains(&ContactId::SELF) + } else { + false + }; if self_added { new_members = BTreeSet::from_iter(to_ids_flat.iter().copied()); new_members.insert(ContactId::SELF); From 55d30b12f954127f042f387cb92b25922c957b43 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 2 Aug 2026 23:46:26 +0200 Subject: [PATCH 2/5] follow link2xt review comments --- src/chat.rs | 7 +------ src/chat/chat_tests.rs | 2 +- src/mimefactory.rs | 8 +++----- src/mimefactory/mimefactory_tests.rs | 16 ---------------- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index a3a6b10761..0fd5ffca4f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -10,9 +10,7 @@ use std::time::Duration; use anyhow::{Context as _, Result, anyhow, bail, ensure}; use chrono::TimeZone; -use deltachat_contact_tools::{ - ContactAddress, addr_cmp, sanitize_bidi_characters, sanitize_single_line, -}; +use deltachat_contact_tools::{ContactAddress, sanitize_bidi_characters, sanitize_single_line}; use humansize::{BINARY, format_size}; use mail_builder::mime::MimePart; use serde::{Deserialize, Serialize}; @@ -2853,9 +2851,6 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) - let attach_selfavatar = mimefactory.attach_selfavatar; let mut recipients = mimefactory.recipients(); - let self_addrs = context.get_all_self_addrs().await?; - recipients.retain(|x| !self_addrs.iter().any(|a| addr_cmp(a, x))); - // Default Webxdc integrations are hidden messages and must not be sent out: if (msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden) // This may happen eg. for groups with only SELF and bcc_self disabled: diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index c54e107b9f..6a8e753267 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -3957,7 +3957,7 @@ async fn test_leave_broadcast_multidevice() -> Result<()> { let leave_msg = bob0.pop_sent_msg().await; let parsed = MimeMessage::from_bytes(bob1, leave_msg.payload().as_bytes()).await?; - assert_eq!(parsed.parts[0].msg, "bob@example.net left the group."); + assert_eq!(parsed.parts[0].msg, "Member bob@example.net was removed."); let rcvd = bob1.recv_msg(&leave_msg).await; diff --git a/src/mimefactory.rs b/src/mimefactory.rs index ff991cdc57..169a474424 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1421,11 +1421,9 @@ impl MimeFactory { let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default(); let fingerprint_to_remove = msg.param.get(Param::Arg4).unwrap_or_default(); - if context.is_self_addr(email_to_remove).await? { - placeholdertext = Some(format!("{email_to_remove} left the group.")); - } else { - placeholdertext = Some(format!("Member {email_to_remove} was removed.")); - }; + // Only visible in classic email clients, + // Delta Chat renders removals from the headers. + placeholdertext = Some(format!("Member {email_to_remove} was removed.")); if !email_to_remove.is_empty() { headers.push(( diff --git a/src/mimefactory/mimefactory_tests.rs b/src/mimefactory/mimefactory_tests.rs index 2cc93b0c63..937fdacbd6 100644 --- a/src/mimefactory/mimefactory_tests.rs +++ b/src/mimefactory/mimefactory_tests.rs @@ -747,22 +747,6 @@ async fn test_remove_member_bcc() -> Result<()> { } } - // A member whose address became a secondary self address is treated - // as ourselves on removal and rendered as "left the group". - let secondary_self_addr = "alice@second.example"; - let secondary_self_id = crate::contact::Contact::create(alice, "", secondary_self_addr).await?; - add_contact_to_chat(alice, alice_chat_id, secondary_self_id).await?; - alice.pop_sent_msg().await; - crate::transport::add_pseudo_transport(alice, secondary_self_addr).await?; - remove_contact_from_chat(alice, alice_chat_id, secondary_self_id).await?; - let remove = alice.pop_sent_msg().await; - assert!( - remove - .payload() - .contains("alice@second.example left the group.") - ); - assert!(!remove.payload().contains("was removed")); - Ok(()) } From 7869b09e11915364d1c5835fbbd1deb660347b93 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Mon, 3 Aug 2026 17:51:53 +0200 Subject: [PATCH 3/5] test: Add test_bcc_self --- src/mimefactory/mimefactory_tests.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/mimefactory/mimefactory_tests.rs b/src/mimefactory/mimefactory_tests.rs index 937fdacbd6..d9232c4c5b 100644 --- a/src/mimefactory/mimefactory_tests.rs +++ b/src/mimefactory/mimefactory_tests.rs @@ -887,6 +887,30 @@ async fn test_new_member_is_first_recipient() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_bcc_self() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + + for bcc_self in [false, true] { + alice.set_config_bool(Config::BccSelf, bcc_self).await?; + + let group = alice.create_group_with_members("Group", &[bob]).await; + let single_chat = alice.create_chat_id(&bob).await; + + for chat_id in [group, single_chat] { + let sent = alice.send_text(chat_id, "Heyho!").await; + if bcc_self { + assert!(sent.recipients.ends_with("alice@example.org")); + } else { + assert_eq!(sent.recipients.contains("alice@example.org"), false); + } + } + } + Ok(()) +} + /// Regression test: mimefactory should never create an empty to header, /// also not if the Selftalk parameter is missing #[tokio::test(flavor = "multi_thread", worker_threads = 2)] From 6ed74341aec43ef43ecaf3aba5b3f1d18c54d22d Mon Sep 17 00:00:00 2001 From: Hocuri Date: Mon, 3 Aug 2026 17:55:13 +0200 Subject: [PATCH 4/5] test: Make the test check for the exact set of recipients --- src/mimefactory/mimefactory_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mimefactory/mimefactory_tests.rs b/src/mimefactory/mimefactory_tests.rs index d9232c4c5b..57d47ecf58 100644 --- a/src/mimefactory/mimefactory_tests.rs +++ b/src/mimefactory/mimefactory_tests.rs @@ -902,9 +902,9 @@ async fn test_bcc_self() -> Result<()> { for chat_id in [group, single_chat] { let sent = alice.send_text(chat_id, "Heyho!").await; if bcc_self { - assert!(sent.recipients.ends_with("alice@example.org")); + assert_eq!(sent.recipients, "bob@example.net alice@example.org"); } else { - assert_eq!(sent.recipients.contains("alice@example.org"), false); + assert_eq!(sent.recipients, "bob@example.net"); } } } From b5b57992894fb4d41882cc55f7398bfc6dece210 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Mon, 3 Aug 2026 17:58:16 +0200 Subject: [PATCH 5/5] clippy --- src/mimefactory/mimefactory_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mimefactory/mimefactory_tests.rs b/src/mimefactory/mimefactory_tests.rs index 57d47ecf58..5825234611 100644 --- a/src/mimefactory/mimefactory_tests.rs +++ b/src/mimefactory/mimefactory_tests.rs @@ -897,7 +897,7 @@ async fn test_bcc_self() -> Result<()> { alice.set_config_bool(Config::BccSelf, bcc_self).await?; let group = alice.create_group_with_members("Group", &[bob]).await; - let single_chat = alice.create_chat_id(&bob).await; + let single_chat = alice.create_chat_id(bob).await; for chat_id in [group, single_chat] { let sent = alice.send_text(chat_id, "Heyho!").await;