diff --git a/src/chat.rs b/src/chat.rs index e33a7d7a5..2fb20634d 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -4711,6 +4711,7 @@ impl Context { #[cfg(test)] mod tests { use super::*; + use crate::chat; use crate::chatlist::get_archived_cnt; use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS}; use crate::headerdef::HeaderDef; @@ -7696,4 +7697,61 @@ mod tests { Ok(()) } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_reply_with_alias() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = tcm.alice().await; + let bob = tcm.bob().await; + + // Alice sends a message to Bob + let alice_bob_chat = alice.create_chat(&bob).await; + let alice_msg = alice.send_text(alice_bob_chat.id, "Hello Bob!").await; + let msg = alice_msg.load_from_db().await; + bob.recv_msg(&alice_msg).await; + + // Bob replies using an alias email address + let alias_email = "bob.alias@example.org"; + let reply_imf = format!( + "From: Bob Alias <{}>\r\n\ + To: {}\r\n\ + In-Reply-To: <{}>\r\n\ + Message-ID: \r\n\ + \r\n\ + Hi Alice, this is Bob's alias.", + alias_email, + alice.get_primary_self_addr().await?, + msg.rfc724_mid + ); + + // Alice receives the raw message + let received = receive_imf(&alice, reply_imf.as_bytes(), false) + .await? + .unwrap(); + + // The message should appear in the existing 1:1 chat between Alice and Bob + assert_eq!(received.chat_id, alice_bob_chat.id); + + let msg = Message::load_from_db(&alice, received.msg_ids[0]).await?; + assert_eq!(msg.text, "Hi Alice, this is Bob's alias."); + + // Alias is displayed in chat + assert_eq!( + msg.param.get(Param::OverrideSenderDisplayname).unwrap(), + "Bob Alias" + ); + + // Chat remains 1:1 + let contacts = chat::get_chat_contacts(&alice, alice_bob_chat.id) + .await + .unwrap(); + assert_eq!(contacts, vec![ContactId::new(10)]); + + // Following messages still go to 1:1 chat + let sent = alice.send_text(alice_bob_chat.id, "Hello again!").await; + let msg = Message::load_from_db(&alice, sent.sender_msg_id).await?; + assert_eq!(msg.chat_id, alice_bob_chat.id); + + Ok(()) + } } diff --git a/src/contact.rs b/src/contact.rs index 1fa2bea6b..ed8dde353 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -3239,7 +3239,13 @@ Until the false-positive is fixed: "bob@example.net sent a message from another device." ); - let msg = tcm.send_recv(alice, bob, "Unencrypted").await; + let msg = tcm + .send_recv( + alice, + bob, + "[This message is not encrypted. See 'Info' for more details]", + ) + .await; assert_eq!(msg.get_showpadlock(), false); Ok(()) diff --git a/src/receive_imf.rs b/src/receive_imf.rs index e7fdb81c3..f778aa396 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -842,6 +842,7 @@ async fn add_parts( } } + // Lookup or create adhoc group chat. if chat_id.is_none() { if let Some((new_chat_id, new_chat_id_blocked)) = lookup_chat_or_create_adhoc_group( context, @@ -966,52 +967,55 @@ async fn add_parts( ); } } + } + } - // Check if the message was sent with verified encryption and set the protection of - // the 1:1 chat accordingly. - let chat = match is_partial_download.is_none() - && mime_parser.get_header(HeaderDef::SecureJoin).is_none() - && !is_mdn - { - true => Some(Chat::load_from_db(context, chat_id).await?) - .filter(|chat| chat.typ == Chattype::Single), - false => None, + if let Some(chat_id) = chat_id { + let contact = Contact::get_by_id(context, from_id).await?; + // Check if the message was sent with verified encryption and set the protection of + // the 1:1 chat accordingly. + let chat = match is_partial_download.is_none() + && mime_parser.get_header(HeaderDef::SecureJoin).is_none() + && !is_mdn + { + true => Some(Chat::load_from_db(context, chat_id).await?) + .filter(|chat| chat.typ == Chattype::Single), + false => None, + }; + if let Some(chat) = chat { + debug_assert!(chat.typ == Chattype::Single); + let mut new_protection = match verified_encryption { + VerifiedEncryption::Verified => ProtectionStatus::Protected, + VerifiedEncryption::NotVerified(_) => ProtectionStatus::Unprotected, }; - if let Some(chat) = chat { - debug_assert!(chat.typ == Chattype::Single); - let mut new_protection = match verified_encryption { - VerifiedEncryption::Verified => ProtectionStatus::Protected, - VerifiedEncryption::NotVerified(_) => ProtectionStatus::Unprotected, - }; - if chat.protected != ProtectionStatus::Unprotected - && new_protection == ProtectionStatus::Unprotected - // `chat.protected` must be maintained regardless of the `Config::VerifiedOneOnOneChats`. - // That's why the config is checked here, and not above. - && context.get_config_bool(Config::VerifiedOneOnOneChats).await? - { - new_protection = ProtectionStatus::ProtectionBroken; - } - if chat.protected != new_protection { - // The message itself will be sorted under the device message since the device - // message is `MessageState::InNoticed`, which means that all following - // messages are sorted under it. - chat_id - .set_protection( - context, - new_protection, - mime_parser.timestamp_sent, - Some(from_id), - ) - .await?; - } - if let Some(peerstate) = &mime_parser.peerstate { - restore_protection = new_protection != ProtectionStatus::Protected - && peerstate.prefer_encrypt == EncryptPreference::Mutual - // Check that the contact still has the Autocrypt key same as the - // verified key, see also `Peerstate::is_using_verified_key()`. - && contact.is_verified(context).await?; - } + if chat.protected != ProtectionStatus::Unprotected + && new_protection == ProtectionStatus::Unprotected + // `chat.protected` must be maintained regardless of the `Config::VerifiedOneOnOneChats`. + // That's why the config is checked here, and not above. + && context.get_config_bool(Config::VerifiedOneOnOneChats).await? + { + new_protection = ProtectionStatus::ProtectionBroken; + } + if chat.protected != new_protection { + // The message itself will be sorted under the device message since the device + // message is `MessageState::InNoticed`, which means that all following + // messages are sorted under it. + chat_id + .set_protection( + context, + new_protection, + mime_parser.timestamp_sent, + Some(from_id), + ) + .await?; + } + if let Some(peerstate) = &mime_parser.peerstate { + restore_protection = new_protection != ProtectionStatus::Protected + && peerstate.prefer_encrypt == EncryptPreference::Mutual + // Check that the contact still has the Autocrypt key same as the + // verified key, see also `Peerstate::is_using_verified_key()`. + && contact.is_verified(context).await?; } } } @@ -1805,7 +1809,13 @@ async fn lookup_chat_by_reply( // If this was a private message just to self, it was probably a private reply. // It should not go into the group then, but into the private chat. - if is_probably_private_reply(context, to_ids, from_id, mime_parser, parent_chat.id).await? { + if parent_chat.get_type() != Chattype::Single + && is_probably_private_reply(context, to_ids, from_id, mime_parser, parent_chat.id).await? + { + return Ok(None); + } + + if parent_chat.is_protected() || mime_parser.decrypting_failed { return Ok(None); }