Repository: chatmail/core (Rust)

Issue:
Members added by legacy DC set timestamps of all recipients to the timestamp of receiving a message
When a member using new DC that has new group consistency algorithm https://github.com/deltachat/deltachat-core-rust/pull/6404 and stores member timestamps gets added to groups by old DC client (or outdated invite bot), they set the timestamps for all members to the date when the message was received. This may result in re-adding members that have left the group before this new DC client received a "Member added" message.

Same may happen if DC client using the latest version with group consistency algorithm gets added to a group by non-DC MUA, but in that case it does not matter as MUA is allowed to add members anyway.

We should set all timestamps to 0 instead because actual timestamps are not known.


Discussion:

Patch:
diff --git a/src/receive_imf.rs b/src/receive_imf.rs
index 76c9934ae3..3f2588233a 100644
--- a/src/receive_imf.rs
+++ b/src/receive_imf.rs
@@ -2093,13 +2093,14 @@ async fn create_group(
             }
             members.extend(to_ids);
 
-            chat::add_to_chat_contacts_table(
-                context,
-                mime_parser.timestamp_sent,
-                new_chat_id,
-                &members,
-            )
-            .await?;
+            // Add all members with 0 timestamp
+            // because we don't know the real timestamp of their addition.
+            // This will allow other senders who support
+            // `Chat-Group-Member-Timestamps` to overwrite
+            // timestamps later.
+            let timestamp = 0;
+
+            chat::add_to_chat_contacts_table(context, timestamp, new_chat_id, &members).await?;
         }
 
         context.emit_event(EventType::ChatModified(new_chat_id));


Test patch:
diff --git a/src/receive_imf/receive_imf_tests.rs b/src/receive_imf/receive_imf_tests.rs
index 588a74e4b1..f2798e9e9b 100644
--- a/src/receive_imf/receive_imf_tests.rs
+++ b/src/receive_imf/receive_imf_tests.rs
@@ -5485,3 +5485,69 @@ async fn test_prefer_chat_group_id_over_references() -> Result<()> {
     assert_ne!(chat1.id, chat2.id);
     Ok(())
 }
+
+/// Tests that if member timestamps are unknown
+/// because of the missing `Chat-Group-Member-Timestamps` header,
+/// then timestamps default to zero.
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_default_member_timestamps_to_zero() -> Result<()> {
+    let bob = &TestContext::new_bob().await;
+
+    let now = time();
+
+    let date = chrono::DateTime::<chrono::Utc>::from_timestamp(now - 1000, 0)
+        .unwrap()
+        .to_rfc2822();
+    let msg = receive_imf(
+        bob,
+        format!(
+            "Subject: Some group\r\n\
+             From: <alice@example.org>\r\n\
+             To: <bob@example.net>, <claire@example.org>, <fiona@example.org>\r\n\
+             Date: {date}\r\n\
+             Message-ID: <first@localhost>\r\n\
+             Chat-Group-ID: foobarbaz12\n\
+             Chat-Group-Name: foo\n\
+             Chat-Version: 1.0\r\n\
+             \r\n\
+             Hi!\r\n"
+        )
+        .as_bytes(),
+        false,
+    )
+    .await?
+    .unwrap();
+    let chat = Chat::load_from_db(bob, msg.chat_id).await?;
+    assert_eq!(chat.typ, Chattype::Group);
+    assert_eq!(chat::get_chat_contacts(bob, chat.id).await?.len(), 4);
+
+    let date = chrono::DateTime::<chrono::Utc>::from_timestamp(now, 0)
+        .unwrap()
+        .to_rfc2822();
+    receive_imf(
+        bob,
+        format!(
+            "Subject: Some group\r\n\
+             From: <claire@example.org>\r\n\
+             To: <alice@example.org>, <bob@example.net>\r\n\
+             Chat-Group-Past-Members: <fiona@example.org>\r\n\
+             Chat-Group-Member-Timestamps: 1737783000 1737783100 1737783200\r\n\
+             Chat-Group-ID: foobarbaz12\n\
+             Chat-Group-Name: foo\n\
+             Chat-Version: 1.0\r\n\
+             Date: {date}\r\n\
+             Message-ID: <second@localhost>\r\n\
+             \r\n\
+            Hi back!\r\n"
+        )
+        .as_bytes(),
+        false,
+    )
+    .await?
+    .unwrap();
+
+    let chat = Chat::load_from_db(bob, msg.chat_id).await?;
+    assert_eq!(chat.typ, Chattype::Group);
+    assert_eq!(chat::get_chat_contacts(bob, chat.id).await?.len(), 3);
+    Ok(())
+}