diff --git a/src/chat.rs b/src/chat.rs index aaff1a695..351e3f21d 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2846,6 +2846,7 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) msg.param.remove(Param::ForcePlaintext); msg.param.remove(Param::Cmd); msg.param.remove(Param::OverrideSenderDisplayname); + msg.in_reply_to = None; // do not leak data as group names; a default subject is generated by mimfactory msg.subject = "".to_string(); @@ -4291,6 +4292,98 @@ mod tests { Ok(()) } + #[async_std::test] + async fn test_forward_quote() -> Result<()> { + let alice = TestContext::new_alice().await; + let bob = TestContext::new_bob().await; + let alice_chat = alice.create_chat(&bob).await; + let bob_chat = bob.create_chat(&alice).await; + + // Alice sends a message to Bob. + let sent_msg = alice.send_text(alice_chat.id, "Hi Bob").await; + bob.recv_msg(&sent_msg).await; + let received_msg = bob.get_last_msg().await; + + // Bob quotes received message and sends a reply to Alice. + let mut reply = Message::new(Viewtype::Text); + reply.set_text(Some("Reply".to_owned())); + reply.set_quote(&bob, &received_msg).await?; + let sent_reply = bob.send_msg(bob_chat.id, &mut reply).await; + alice.recv_msg(&sent_reply).await; + let received_reply = alice.get_last_msg().await; + + // Alice forwards a reply. + forward_msgs(&alice, &[received_reply.id], alice_chat.get_id()).await?; + let forwarded_msg = alice.pop_sent_msg().await; + bob.recv_msg(&forwarded_msg).await; + + let alice_forwarded_msg = alice.get_last_msg().await; + assert!(alice_forwarded_msg.quoted_message(&alice).await?.is_none()); + assert_eq!( + alice_forwarded_msg.quoted_text(), + Some("Hi Bob".to_string()) + ); + + let bob_forwarded_msg = bob.get_last_msg().await; + assert!(bob_forwarded_msg.quoted_message(&bob).await?.is_none()); + assert_eq!(bob_forwarded_msg.quoted_text(), Some("Hi Bob".to_string())); + + Ok(()) + } + + #[async_std::test] + async fn test_forward_group() -> Result<()> { + let alice = TestContext::new_alice().await; + let bob = TestContext::new_bob().await; + + let alice_chat = alice.create_chat(&bob).await; + let bob_chat = bob.create_chat(&alice).await; + + // Alice creates a group with Bob. + let alice_group_chat_id = + create_group_chat(&alice, ProtectionStatus::Unprotected, "Group").await?; + let bob_id = Contact::create(&alice, "Bob", "bob@example.net").await?; + let claire_id = Contact::create(&alice, "Claire", "claire@example.net").await?; + add_contact_to_chat(&alice, alice_group_chat_id, bob_id).await?; + add_contact_to_chat(&alice, alice_group_chat_id, claire_id).await?; + let sent_group_msg = alice + .send_text(alice_group_chat_id, "Hi Bob and Claire") + .await; + bob.recv_msg(&sent_group_msg).await; + let bob_group_chat_id = bob.get_last_msg().await.chat_id; + + // Alice deletes a message on her device. + // This is needed to make assignment of further messages received in this group + // based on `References:` header harder. + // Previously this exposed a bug, so this is a regression test. + message::delete_msgs(&alice, &[sent_group_msg.sender_msg_id]).await?; + + // Alice sends a message to Bob. + let sent_msg = alice.send_text(alice_chat.id, "Hi Bob").await; + bob.recv_msg(&sent_msg).await; + let received_msg = bob.get_last_msg().await; + assert_eq!(received_msg.get_text(), Some("Hi Bob".to_string())); + assert_eq!(received_msg.chat_id, bob_chat.id); + + // Alice sends another message to Bob, this has first message as a parent. + let sent_msg = alice.send_text(alice_chat.id, "Hello Bob").await; + bob.recv_msg(&sent_msg).await; + let received_msg = bob.get_last_msg().await; + assert_eq!(received_msg.get_text(), Some("Hello Bob".to_string())); + assert_eq!(received_msg.chat_id, bob_chat.id); + + // Bob forwards message to a group chat with Alice. + forward_msgs(&bob, &[received_msg.id], bob_group_chat_id).await?; + let forwarded_msg = bob.pop_sent_msg().await; + alice.recv_msg(&forwarded_msg).await; + + let received_forwarded_msg = alice.get_last_msg_in(alice_group_chat_id).await; + assert!(received_forwarded_msg.is_forwarded()); + assert_eq!(received_forwarded_msg.chat_id, alice_group_chat_id); + + Ok(()) + } + #[async_std::test] async fn test_only_minimal_data_are_forwarded() -> Result<()> { // send a message from Alice to a group with Bob diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 7b06d75a8..f3fcc54c5 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -2113,6 +2113,8 @@ fn set_better_msg(mime_parser: &mut MimeMessage, better_msg: impl AsRef) { /// Returns the last message referenced from `References` header if it is in the database. /// /// For Delta Chat messages it is the last message in the chat of the sender. +/// +/// Note that the returned message may be trashed. async fn get_previous_message( context: &Context, mime_parser: &MimeMessage, @@ -2128,6 +2130,8 @@ async fn get_previous_message( } /// Given a list of Message-IDs, returns the latest message found in the database. +/// +/// Only messages that are not in the trash chat are considered. async fn get_rfc724_mid_in_list(context: &Context, mid_list: &str) -> Result> { if mid_list.is_empty() { return Ok(None); @@ -2135,7 +2139,10 @@ async fn get_rfc724_mid_in_list(context: &Context, mid_list: &str) -> Result Result<()> { + let t = TestContext::new_alice().await; + t.set_config(Config::ShowEmails, Some("2")).await?; + + let mime = br#"Subject: First +Message-ID: first@example.net +To: Alice +From: Bob +Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no + +First."#; + dc_receive_imf(&t, mime, "INBOX", 1, false).await?; + let first = t.get_last_msg().await; + let mime = br#"Subject: Second +Message-ID: second@example.net +To: Alice +From: Bob +Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no + +First."#; + dc_receive_imf(&t, mime, "INBOX", 2, false).await?; + let second = t.get_last_msg().await; + let mime = br#"Subject: Third +Message-ID: third@example.net +To: Alice +From: Bob +Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no + +First."#; + dc_receive_imf(&t, mime, "INBOX", 3, false).await?; + let third = t.get_last_msg().await; + + let mime = br#"Subject: Message with references. +Message-ID: second@example.net +To: Alice +From: Bob +In-Reply-To: +References: +Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no + +Message with references."#; + let mime_parser = MimeMessage::from_bytes(&t, &mime[..]).await?; + + let parent = get_parent_message(&t, &mime_parser).await?.unwrap(); + assert_eq!(parent.id, first.id); + + message::delete_msgs(&t, &[first.id]).await?; + let parent = get_parent_message(&t, &mime_parser).await?.unwrap(); + assert_eq!(parent.id, second.id); + + message::delete_msgs(&t, &[second.id]).await?; + let parent = get_parent_message(&t, &mime_parser).await?.unwrap(); + assert_eq!(parent.id, third.id); + + message::delete_msgs(&t, &[third.id]).await?; + let parent = get_parent_message(&t, &mime_parser).await?; + assert!(parent.is_none()); + + Ok(()) + } } diff --git a/src/message.rs b/src/message.rs index 46a9cbe9f..41f1cc002 100644 --- a/src/message.rs +++ b/src/message.rs @@ -877,7 +877,7 @@ impl Message { } pub async fn quoted_message(&self, context: &Context) -> Result> { - if self.param.get(Param::Quote).is_some() { + if self.param.get(Param::Quote).is_some() && !self.is_forwarded() { if let Some(in_reply_to) = &self.in_reply_to { if let Some((_, _, msg_id)) = rfc724_mid_exists(context, in_reply_to).await? { let msg = Message::load_from_db(context, msg_id).await?;