mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
fix: Only include one From: header in securejoin messages (#5917)
This fixes the bug that sometimes made QR scans fail. The problem was: When sorting headers into unprotected/hidden/protected, the From: header was added twice for all messages: Once into unprotected_headers and once into protected_headers. For messages that are `is_encrypted && verified || is_securejoin_message`, the display name is removed before pushing it into unprotected_headers. Later, duplicate headers are removed from unprotected_headers right before prepending unprotected_headers to the message. But since the unencrypted From: header got modified a bit when removing the display name, it's not exactly the same anymore, so it's not removed from unprotected_headers and consequently added again.
This commit is contained in:
@@ -726,18 +726,18 @@ impl MimeFactory {
|
|||||||
} else if header_name == "autocrypt" {
|
} else if header_name == "autocrypt" {
|
||||||
unprotected_headers.push(header.clone());
|
unprotected_headers.push(header.clone());
|
||||||
} else if header_name == "from" {
|
} else if header_name == "from" {
|
||||||
protected_headers.push(header.clone());
|
// Unencrypted securejoin messages should _not_ include the display name:
|
||||||
if is_encrypted && verified || is_securejoin_message {
|
if is_encrypted || !is_securejoin_message {
|
||||||
unprotected_headers.push(
|
protected_headers.push(header.clone());
|
||||||
Header::new_with_value(
|
|
||||||
header.name,
|
|
||||||
vec![Address::new_mailbox(self.from_addr.clone())],
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
unprotected_headers.push(header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unprotected_headers.push(
|
||||||
|
Header::new_with_value(
|
||||||
|
header.name,
|
||||||
|
vec![Address::new_mailbox(self.from_addr.clone())],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
} else if header_name == "to" {
|
} else if header_name == "to" {
|
||||||
protected_headers.push(header.clone());
|
protected_headers.push(header.clone());
|
||||||
if is_encrypted {
|
if is_encrypted {
|
||||||
@@ -902,12 +902,11 @@ impl MimeFactory {
|
|||||||
.fold(message, |message, header| message.header(header.clone()));
|
.fold(message, |message, header| message.header(header.clone()));
|
||||||
|
|
||||||
if skip_autocrypt || !context.get_config_bool(Config::SignUnencrypted).await? {
|
if skip_autocrypt || !context.get_config_bool(Config::SignUnencrypted).await? {
|
||||||
let protected: HashSet<Header> = HashSet::from_iter(protected_headers.into_iter());
|
// Deduplicate unprotected headers that also are in the protected headers:
|
||||||
for h in unprotected_headers.split_off(0) {
|
let protected: HashSet<&str> =
|
||||||
if !protected.contains(&h) {
|
HashSet::from_iter(protected_headers.iter().map(|h| h.name.as_str()));
|
||||||
unprotected_headers.push(h);
|
unprotected_headers.retain(|h| !protected.contains(&h.name.as_str()));
|
||||||
}
|
|
||||||
}
|
|
||||||
message
|
message
|
||||||
} else {
|
} else {
|
||||||
let message = message.header(get_content_type_directives_header());
|
let message = message.header(get_content_type_directives_header());
|
||||||
|
|||||||
@@ -843,6 +843,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let sent = bob.pop_sent_msg().await;
|
let sent = bob.pop_sent_msg().await;
|
||||||
|
assert!(!sent.payload.contains("Bob Examplenet"));
|
||||||
assert_eq!(sent.recipient(), EmailAddress::new(alice_addr).unwrap());
|
assert_eq!(sent.recipient(), EmailAddress::new(alice_addr).unwrap());
|
||||||
let msg = alice.parse_msg(&sent).await;
|
let msg = alice.parse_msg(&sent).await;
|
||||||
assert!(!msg.was_encrypted());
|
assert!(!msg.was_encrypted());
|
||||||
@@ -860,6 +861,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let sent = alice.pop_sent_msg().await;
|
let sent = alice.pop_sent_msg().await;
|
||||||
|
assert!(!sent.payload.contains("Alice Exampleorg"));
|
||||||
let msg = bob.parse_msg(&sent).await;
|
let msg = bob.parse_msg(&sent).await;
|
||||||
assert!(msg.was_encrypted());
|
assert!(msg.was_encrypted());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! This private module is only compiled for test runs.
|
//! This private module is only compiled for test runs.
|
||||||
#![allow(clippy::indexing_slicing)]
|
#![allow(clippy::indexing_slicing)]
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::panic;
|
use std::panic;
|
||||||
@@ -477,6 +477,36 @@ impl TestContext {
|
|||||||
update_msg_state(&self.ctx, msg_id, MessageState::OutDelivered)
|
update_msg_state(&self.ctx, msg_id, MessageState::OutDelivered)
|
||||||
.await
|
.await
|
||||||
.expect("failed to update message state");
|
.expect("failed to update message state");
|
||||||
|
|
||||||
|
let payload_headers = payload.split("\r\n\r\n").next().unwrap().lines();
|
||||||
|
let payload_header_names: Vec<_> = payload_headers
|
||||||
|
.map(|h| h.split(':').next().unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Check that we are sending exactly one From, Subject, Date, To, Message-ID, and MIME-Version header:
|
||||||
|
for header in &[
|
||||||
|
"From",
|
||||||
|
"Subject",
|
||||||
|
"Date",
|
||||||
|
"To",
|
||||||
|
"Message-ID",
|
||||||
|
"MIME-Version",
|
||||||
|
] {
|
||||||
|
assert_eq!(
|
||||||
|
payload_header_names.iter().filter(|h| *h == header).count(),
|
||||||
|
1,
|
||||||
|
"This sent email should contain the header {header} exactly 1 time:\n{payload}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Check that we aren't sending any header twice:
|
||||||
|
let mut hash_set = HashSet::new();
|
||||||
|
for header_name in payload_header_names {
|
||||||
|
assert!(
|
||||||
|
hash_set.insert(header_name),
|
||||||
|
"This sent email shouldn't contain the header {header_name} multiple times:\n{payload}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Some(SentMessage {
|
Some(SentMessage {
|
||||||
payload,
|
payload,
|
||||||
sender_msg_id: msg_id,
|
sender_msg_id: msg_id,
|
||||||
|
|||||||
@@ -881,7 +881,7 @@ async fn test_verified_member_added_reordering() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_no_unencrypted_name_if_verified() -> Result<()> {
|
async fn test_no_unencrypted_name_if_encrypted() -> Result<()> {
|
||||||
let mut tcm = TestContextManager::new();
|
let mut tcm = TestContextManager::new();
|
||||||
for verified in [false, true] {
|
for verified in [false, true] {
|
||||||
let alice = tcm.alice().await;
|
let alice = tcm.alice().await;
|
||||||
@@ -898,7 +898,7 @@ async fn test_no_unencrypted_name_if_verified() -> Result<()> {
|
|||||||
let chat_id = bob.create_chat(&alice).await.id;
|
let chat_id = bob.create_chat(&alice).await.id;
|
||||||
let msg = &bob.send_text(chat_id, "hi").await;
|
let msg = &bob.send_text(chat_id, "hi").await;
|
||||||
|
|
||||||
assert_eq!(msg.payload.contains("Bob Smith"), !verified);
|
assert_eq!(msg.payload.contains("Bob Smith"), false);
|
||||||
assert!(msg.payload.contains("BEGIN PGP MESSAGE"));
|
assert!(msg.payload.contains("BEGIN PGP MESSAGE"));
|
||||||
|
|
||||||
let msg = alice.recv_msg(msg).await;
|
let msg = alice.recv_msg(msg).await;
|
||||||
|
|||||||
Reference in New Issue
Block a user