feat: mimefactory: Order message recipients by time of addition (#6872)

Sort recipients by `add_timestamp DESC` so that if the group is large and there are multiple SMTP
messages, a newly added member receives the member addition message earlier and has gossiped keys of
other members (otherwise the new member may receive messages from other members earlier and fail to
verify them).
This commit is contained in:
iequidoo
2025-07-20 15:56:32 -03:00
committed by iequidoo
parent 1209e95e34
commit d6af8d2526
2 changed files with 45 additions and 1 deletions

View File

@@ -253,6 +253,10 @@ impl MimeFactory {
let mut missing_key_addresses = BTreeSet::new(); let mut missing_key_addresses = BTreeSet::new();
context context
.sql .sql
// Sort recipients by `add_timestamp DESC` so that if the group is large and there
// are multiple SMTP messages, a newly added member receives the member addition
// message earlier and has gossiped keys of other members (otherwise the new member
// may receive messages from other members earlier and fail to verify them).
.query_map( .query_map(
"SELECT "SELECT
c.authname, c.authname,
@@ -266,7 +270,8 @@ impl MimeFactory {
LEFT JOIN contacts c ON cc.contact_id=c.id LEFT JOIN contacts c ON cc.contact_id=c.id
LEFT JOIN public_keys k ON k.fingerprint=c.fingerprint LEFT JOIN public_keys k ON k.fingerprint=c.fingerprint
WHERE cc.chat_id=? WHERE cc.chat_id=?
AND (cc.contact_id>9 OR (cc.contact_id=1 AND ?))", AND (cc.contact_id>9 OR (cc.contact_id=1 AND ?))
ORDER BY cc.add_timestamp DESC",
(msg.chat_id, chat.typ == Chattype::Group), (msg.chat_id, chat.typ == Chattype::Group),
|row| { |row| {
let authname: String = row.get(0)?; let authname: String = row.get(0)?;

View File

@@ -2,6 +2,7 @@ use deltachat_contact_tools::ContactAddress;
use mail_builder::headers::Header; use mail_builder::headers::Header;
use mailparse::{MailHeaderMap, addrparse_header}; use mailparse::{MailHeaderMap, addrparse_header};
use std::str; use std::str;
use std::time::Duration;
use super::*; use super::*;
use crate::chat::{ use crate::chat::{
@@ -16,6 +17,7 @@ use crate::message;
use crate::mimeparser::MimeMessage; use crate::mimeparser::MimeMessage;
use crate::receive_imf::receive_imf; use crate::receive_imf::receive_imf;
use crate::test_utils::{TestContext, TestContextManager, get_chat_msg}; use crate::test_utils::{TestContext, TestContextManager, get_chat_msg};
use crate::tools::SystemTime;
fn render_email_address(display_name: &str, addr: &str) -> String { fn render_email_address(display_name: &str, addr: &str) -> String {
let mut output = Vec::<u8>::new(); let mut output = Vec::<u8>::new();
@@ -867,6 +869,43 @@ async fn test_dont_remove_self() -> Result<()> {
Ok(()) Ok(())
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_new_member_is_first_recipient() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let charlie = &tcm.charlie().await;
let bob_id = alice.add_or_lookup_contact_id(bob).await;
let charlie_id = alice.add_or_lookup_contact_id(charlie).await;
let group = alice
.create_group_with_members(ProtectionStatus::Unprotected, "Group", &[bob])
.await;
alice.send_text(group, "Hi! I created a group.").await;
SystemTime::shift(Duration::from_secs(60));
add_contact_to_chat(alice, group, charlie_id).await?;
let sent_msg = alice.pop_sent_msg().await;
assert!(
sent_msg
.recipients
.starts_with(&charlie.get_config(Config::Addr).await?.unwrap())
);
remove_contact_from_chat(alice, group, bob_id).await?;
alice.pop_sent_msg().await;
SystemTime::shift(Duration::from_secs(60));
add_contact_to_chat(alice, group, bob_id).await?;
let sent_msg = alice.pop_sent_msg().await;
assert!(
sent_msg
.recipients
.starts_with(&bob.get_config(Config::Addr).await?.unwrap())
);
Ok(())
}
/// Regression test: mimefactory should never create an empty to header, /// Regression test: mimefactory should never create an empty to header,
/// also not if the Selftalk parameter is missing /// also not if the Selftalk parameter is missing
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]