diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index 12cc98412..3fc8823a5 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -1343,7 +1343,6 @@ def test_quote_encrypted(acfactory, lp): for quoted_msg in msg1, msg3: # Save the draft with a quote. - # It should be encrypted if quoted message is encrypted. msg_draft = Message.new_empty(ac1, "text") msg_draft.set_text("message reply") msg_draft.quote = quoted_msg @@ -1357,10 +1356,14 @@ def test_quote_encrypted(acfactory, lp): chat.set_draft(None) assert chat.get_draft() is None + # Quote should be replaced with "..." if quoted message is encrypted. msg_in = ac2._evtracker.wait_next_incoming_message() assert msg_in.text == "message reply" - assert msg_in.quoted_text == quoted_msg.text - assert msg_in.is_encrypted() == quoted_msg.is_encrypted() + assert not msg_in.is_encrypted() + if quoted_msg.is_encrypted(): + assert msg_in.quoted_text == "..." + else: + assert msg_in.quoted_text == quoted_msg.text def test_quote_attachment(tmp_path, acfactory, lp): diff --git a/src/message.rs b/src/message.rs index 3fed2daef..9c581cdcb 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1127,7 +1127,7 @@ impl Message { .get_bool(Param::GuaranteeE2ee) .unwrap_or_default() { - self.param.set(Param::GuaranteeE2ee, "1"); + self.param.set(Param::ProtectQuote, "1"); } let text = quote.get_text(); @@ -2015,7 +2015,9 @@ mod tests { use num_traits::FromPrimitive; use super::*; - use crate::chat::{self, marknoticed_chat, send_text_msg, ChatItem}; + use crate::chat::{ + self, add_contact_to_chat, marknoticed_chat, send_text_msg, ChatItem, ProtectionStatus, + }; use crate::chatlist::Chatlist; use crate::config::Config; use crate::reaction::send_reaction; @@ -2211,6 +2213,42 @@ mod tests { assert_eq!(quoted_msg.get_text(), msg2.quoted_text().unwrap()); } + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_unencrypted_quote_encrypted_message() -> Result<()> { + let mut tcm = TestContextManager::new(); + + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + + let alice_group = alice + .create_group_with_members(ProtectionStatus::Unprotected, "Group chat", &[bob]) + .await; + let sent = alice.send_text(alice_group, "Hi! I created a group").await; + let bob_received_message = bob.recv_msg(&sent).await; + + let bob_group = bob_received_message.chat_id; + bob_group.accept(bob).await?; + let sent = bob.send_text(bob_group, "Encrypted message").await; + let alice_received_message = alice.recv_msg(&sent).await; + assert!(alice_received_message.get_showpadlock()); + + // Alice adds contact without key so chat becomes unencrypted. + let alice_flubby_contact_id = + Contact::create(alice, "Flubby", "flubby@example.org").await?; + add_contact_to_chat(alice, alice_group, alice_flubby_contact_id).await?; + + // Alice quotes encrypted message in unencrypted chat. + let mut msg = Message::new(Viewtype::Text); + msg.set_quote(alice, Some(&alice_received_message)).await?; + chat::send_msg(alice, alice_group, &mut msg).await?; + + let bob_received_message = bob.recv_msg(&alice.pop_sent_msg().await).await; + assert_eq!(bob_received_message.quoted_text().unwrap(), "..."); + assert_eq!(bob_received_message.get_showpadlock(), false); + + Ok(()) + } + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_get_chat_id() { // Alice receives a message that pops up as a contact request diff --git a/src/mimefactory.rs b/src/mimefactory.rs index cd52c3966..066892331 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -669,19 +669,19 @@ impl<'a> MimeFactory<'a> { let mut is_gossiped = false; - let (main_part, parts) = match self.loaded { - Loaded::Message { .. } => { - self.render_message(context, &mut headers, &grpimage) - .await? - } - Loaded::Mdn { .. } => (self.render_mdn(context).await?, Vec::new()), - }; - let peerstates = self.peerstates_for_recipients(context).await?; let should_encrypt = encrypt_helper.should_encrypt(context, e2ee_guaranteed, &peerstates)?; let is_encrypted = should_encrypt && !force_plaintext; + let (main_part, parts) = match self.loaded { + Loaded::Message { .. } => { + self.render_message(context, &mut headers, &grpimage, is_encrypted) + .await? + } + Loaded::Mdn { .. } => (self.render_mdn(context).await?, Vec::new()), + }; + let message = if parts.is_empty() { // Single part, render as regular message. main_part @@ -960,6 +960,7 @@ impl<'a> MimeFactory<'a> { context: &Context, headers: &mut MessageHeaders, grpimage: &Option, + is_encrypted: bool, ) -> Result<(PartBuilder, Vec)> { let chat = match &self.loaded { Loaded::Message { chat } => chat, @@ -1221,6 +1222,16 @@ impl<'a> MimeFactory<'a> { .msg .quoted_text() .map(|quote| format_flowed_quote("e) + "\r\n\r\n"); + if !is_encrypted + && self + .msg + .param + .get_bool(Param::ProtectQuote) + .unwrap_or_default() + { + // Message is not encrypted but quotes encrypted message. + quoted_text = Some("> ...\r\n\r\n".to_string()); + } if quoted_text.is_none() && final_text.starts_with('>') { // Insert empty line to avoid receiver treating user-sent quote as topquote inserted by // Delta Chat. diff --git a/src/param.rs b/src/param.rs index 9971d2457..434842261 100644 --- a/src/param.rs +++ b/src/param.rs @@ -48,6 +48,11 @@ pub enum Param { /// For Messages: message is encrypted, outgoing: guarantee E2EE or the message is not send GuaranteeE2ee = b'c', + /// For Messages: quoted message is encrypted. + /// + /// If this message is sent unencrypted, quote text should be replaced. + ProtectQuote = b'0', + /// For Messages: decrypted with validation errors or without mutual set, if neither /// 'c' nor 'e' are preset, the messages is only transport encrypted. ErroneousE2ee = b'e',