diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index a82cd6305a..57821f6a87 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -2632,12 +2632,6 @@ async fn test_can_send_group() -> Result<()> { /// the recipients can't see the identity of their fellow recipients. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_broadcast_members_cant_see_each_other() -> Result<()> { - fn contains(parsed: &MimeMessage, s: &str) -> bool { - assert_eq!(parsed.decrypting_failed, false); - let decoded_str = std::str::from_utf8(&parsed.decoded_data).unwrap(); - decoded_str.contains(s) - } - let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; @@ -2669,8 +2663,8 @@ async fn test_broadcast_members_cant_see_each_other() -> Result<()> { ); let parsed = charlie.parse_msg(&auth_required).await; assert!(parsed.get_header(HeaderDef::AutocryptGossip).is_some()); - assert!(contains(&parsed, "charlie@example.net")); - assert_eq!(contains(&parsed, "bob@example.net"), false); + assert!(parsed.decoded_data_contains("charlie@example.net")); + assert_eq!(parsed.decoded_data_contains("bob@example.net"), false); let parsed_by_bob = bob.parse_msg(&auth_required).await; assert!(parsed_by_bob.decrypting_failed); @@ -2698,8 +2692,8 @@ async fn test_broadcast_members_cant_see_each_other() -> Result<()> { ); let parsed = charlie.parse_msg(&member_added).await; assert!(parsed.get_header(HeaderDef::AutocryptGossip).is_some()); - assert!(contains(&parsed, "charlie@example.net")); - assert_eq!(contains(&parsed, "bob@example.net"), false); + assert!(parsed.decoded_data_contains("charlie@example.net")); + assert_eq!(parsed.decoded_data_contains("bob@example.net"), false); let parsed_by_bob = bob.parse_msg(&member_added).await; assert!(parsed_by_bob.decrypting_failed); @@ -2713,8 +2707,8 @@ async fn test_broadcast_members_cant_see_each_other() -> Result<()> { let hi_msg = alice.send_text(alice_broadcast_id, "hi").await; let parsed = charlie.parse_msg(&hi_msg).await; assert_eq!(parsed.header_exists(HeaderDef::AutocryptGossip), false); - assert_eq!(contains(&parsed, "charlie@example.net"), false); - assert_eq!(contains(&parsed, "bob@example.net"), false); + assert_eq!(parsed.decoded_data_contains("charlie@example.net"), false); + assert_eq!(parsed.decoded_data_contains("bob@example.net"), false); let parsed_by_bob = bob.parse_msg(&hi_msg).await; assert_eq!(parsed_by_bob.decrypting_failed, false); @@ -2730,8 +2724,8 @@ async fn test_broadcast_members_cant_see_each_other() -> Result<()> { "charlie@example.net alice@example.org" ); let parsed = charlie.parse_msg(&member_removed).await; - assert!(contains(&parsed, "charlie@example.net")); - assert_eq!(contains(&parsed, "bob@example.net"), false); + assert!(parsed.decoded_data_contains("charlie@example.net")); + assert_eq!(parsed.decoded_data_contains("bob@example.net"), false); let parsed_by_bob = bob.parse_msg(&member_removed).await; assert!(parsed_by_bob.decrypting_failed); diff --git a/src/config.rs b/src/config.rs index 7cb7617f1d..b4b504666e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -438,8 +438,19 @@ pub enum Config { /// storing the same token multiple times on the server. EncryptedDeviceToken, + /// Enables running test hooks, e.g. see `InnerContext::pre_encrypt_mime_hook`. + /// This way is better than conditional compilation, i.e. `#[cfg(test)]`, because tests not + /// using this still run unmodified code. + TestHooks, + /// Return an error from `receive_imf_inner()` for a fully downloaded message. For tests. FailOnReceivingFullMsg, + + /// Enable composing emails with Header Protection as defined in + /// "Header Protection for Cryptographically + /// Protected Email". + #[strum(props(default = "1"))] + StdHeaderProtectionComposing, } impl Config { diff --git a/src/context.rs b/src/context.rs index 9287777c96..610b2887cb 100644 --- a/src/context.rs +++ b/src/context.rs @@ -303,6 +303,17 @@ pub struct InnerContext { /// `Connectivity` values for mailboxes, unordered. Used to compute the aggregate connectivity, /// see [`Context::get_connectivity()`]. pub(crate) connectivities: parking_lot::Mutex>, + + #[expect(clippy::type_complexity)] + /// Transforms the root of the cryptographic payload before encryption. + pub(crate) pre_encrypt_mime_hook: parking_lot::Mutex< + Option< + for<'a> fn( + &Context, + mail_builder::mime::MimePart<'a>, + ) -> mail_builder::mime::MimePart<'a>, + >, + >, } /// The state of ongoing process. @@ -467,6 +478,7 @@ impl Context { iroh: Arc::new(RwLock::new(None)), self_fingerprint: OnceLock::new(), connectivities: parking_lot::Mutex::new(Vec::new()), + pre_encrypt_mime_hook: None.into(), }; let ctx = Context { @@ -1051,6 +1063,13 @@ impl Context { .await? .to_string(), ); + res.insert( + "test_hooks", + self.sql + .get_raw_config("test_hooks") + .await? + .unwrap_or_default(), + ); res.insert( "fail_on_receiving_full_msg", self.sql @@ -1058,6 +1077,13 @@ impl Context { .await? .unwrap_or_default(), ); + res.insert( + "std_header_protection_composing", + self.sql + .get_raw_config("std_header_protection_composing") + .await? + .unwrap_or_default(), + ); let elapsed = time_elapsed(&self.creation_time); res.insert("uptime", duration_to_str(elapsed)); diff --git a/src/dehtml.rs b/src/dehtml.rs index a6d70b1f7d..12cd4e6098 100644 --- a/src/dehtml.rs +++ b/src/dehtml.rs @@ -13,6 +13,7 @@ use quick_xml::{ use crate::simplify::{SimplifiedText, simplify_quote}; +#[derive(Default)] struct Dehtml { strbuilder: String, quote: String, @@ -25,6 +26,9 @@ struct Dehtml { /// Everything between `
` and `
` is usually metadata /// If this is > `0`, then we are inside a `
`. divs_since_quoted_content_div: u32, + /// `
` elements should be omitted, see + /// . + divs_since_hp_legacy_display: u32, /// All-Inkl just puts the quote into `
`. This count is /// increased at each `
` and decreased at each `
`. blockquotes_since_blockquote: u32, @@ -48,20 +52,25 @@ impl Dehtml { } fn get_add_text(&self) -> AddText { - if self.divs_since_quote_div > 0 && self.divs_since_quoted_content_div == 0 { - AddText::No // Everything between `
` and `
` is metadata which we don't want + // Everything between `
` and `
` is + // metadata which we don't want. + if self.divs_since_quote_div > 0 && self.divs_since_quoted_content_div == 0 + || self.divs_since_hp_legacy_display > 0 + { + AddText::No } else { self.add_text } } } -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, Default, PartialEq, Clone, Copy)] enum AddText { /// Inside `