Skip to content

Commit 4d718ed

Browse files
committed
fix: Ignore protected headers in outer message part (#6357)
Delta Chat always adds protected headers to the inner encrypted or signed message, so if a protected header is only present in the outer part, it should be ignored because it's probably added by the server or somebody else. The exception is Subject because there are known cases when it's only present in the outer message part, e.g. an encrypted unsigned Thunderbird message. Also treat any Chat-* headers as protected. This fixes e.g. a case when the server injects a "Chat-Version" IMF header tricking Delta Chat into thinking that it's a chat message. Also handle "Auto-Submitted" and "Autocrypt-Setup-Message" as protected headers on the receiver side, this was apparently forgotten.
1 parent f5e8c80 commit 4d718ed

File tree

4 files changed

+69
-41
lines changed

4 files changed

+69
-41
lines changed

src/decrypt.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ mod tests {
178178
let bob = TestContext::new_bob().await;
179179
receive_imf(&bob, attachment_mime, false).await?;
180180
let msg = bob.get_last_msg().await;
181-
assert_eq!(msg.text, "Hello from Thunderbird!");
181+
// Subject should be prepended because the attachment doesn't have "Chat-Version".
182+
assert_eq!(msg.text, "Hello, Bob! – Hello from Thunderbird!");
182183

183184
Ok(())
184185
}

src/mimeparser.rs

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -446,26 +446,9 @@ impl MimeMessage {
446446
});
447447
if let (Ok(mail), true) = (mail, is_encrypted) {
448448
if !signatures.is_empty() {
449-
// Remove unsigned opportunistically protected headers from messages considered
450-
// Autocrypt-encrypted / displayed with padlock.
451449
// For "Subject" see <https://github.com/deltachat/deltachat-core-rust/issues/1790>.
452-
for h in [
453-
HeaderDef::Subject,
454-
HeaderDef::ChatGroupId,
455-
HeaderDef::ChatGroupName,
456-
HeaderDef::ChatGroupNameChanged,
457-
HeaderDef::ChatGroupNameTimestamp,
458-
HeaderDef::ChatGroupAvatar,
459-
HeaderDef::ChatGroupMemberRemoved,
460-
HeaderDef::ChatGroupMemberAdded,
461-
HeaderDef::ChatGroupMemberTimestamps,
462-
HeaderDef::ChatGroupPastMembers,
463-
HeaderDef::ChatDelete,
464-
HeaderDef::ChatEdit,
465-
HeaderDef::ChatUserAvatar,
466-
] {
467-
remove_header(&mut headers, h.get_headername(), &mut headers_removed);
468-
}
450+
// Other headers are removed by `MimeMessage::merge_headers()`.
451+
remove_header(&mut headers, "subject", &mut headers_removed);
469452
}
470453

471454
// let known protected headers from the decrypted
@@ -1565,12 +1548,15 @@ impl MimeMessage {
15651548
chat_disposition_notification_to: &mut Option<SingleInfo>,
15661549
fields: &[mailparse::MailHeader<'_>],
15671550
) {
1551+
// Keep Subject so that it's displayed for signed-only messages. They are shown w/o a
1552+
// padlock anyway.
1553+
headers.retain(|k, _| !is_protected(k) || k == "subject");
15681554
for field in fields {
15691555
// lowercasing all headers is technically not correct, but makes things work better
15701556
let key = field.get_key().to_lowercase();
1571-
if !headers.contains_key(&key) || // key already exists, only overwrite known types (protected headers)
1572-
is_known(&key) || key.starts_with("chat-")
1573-
{
1557+
// Don't overwrite unprotected headers, but overwrite protected ones because DKIM
1558+
// signature applies to the last headers.
1559+
if !headers.contains_key(&key) || is_protected(&key) {
15741560
if key == HeaderDef::ChatDispositionNotificationTo.get_headername() {
15751561
match addrparse_header(field) {
15761562
Ok(addrlist) => {
@@ -2011,24 +1997,27 @@ pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
20111997

20121998
/// Returns true if the header overwrites outer header
20131999
/// when it comes from protected headers.
2014-
fn is_known(key: &str) -> bool {
2015-
matches!(
2016-
key,
2017-
"return-path"
2018-
| "date"
2019-
| "from"
2020-
| "sender"
2021-
| "reply-to"
2022-
| "to"
2023-
| "cc"
2024-
| "bcc"
2025-
| "message-id"
2026-
| "in-reply-to"
2027-
| "references"
2028-
| "subject"
2029-
| "secure-join"
2030-
| "list-id"
2031-
)
2000+
fn is_protected(key: &str) -> bool {
2001+
key.starts_with("chat-")
2002+
|| matches!(
2003+
key,
2004+
"return-path"
2005+
| "auto-submitted"
2006+
| "autocrypt-setup-message"
2007+
| "date"
2008+
| "from"
2009+
| "sender"
2010+
| "reply-to"
2011+
| "to"
2012+
| "cc"
2013+
| "bcc"
2014+
| "message-id"
2015+
| "in-reply-to"
2016+
| "references"
2017+
| "subject"
2018+
| "secure-join"
2019+
| "list-id"
2020+
)
20322021
}
20332022

20342023
/// Returns if the header is hidden and must be ignored in the IMF section.

src/mimeparser/mimeparser_tests.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,26 @@ async fn test_x_microsoft_original_message_id_precedence() -> Result<()> {
14021402
Ok(())
14031403
}
14041404

1405+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1406+
async fn test_extra_imf_chat_header() -> Result<()> {
1407+
let mut tcm = TestContextManager::new();
1408+
let t = &tcm.alice().await;
1409+
let chat_id = t.get_self_chat().await.id;
1410+
1411+
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
1412+
let sent_msg = t.pop_sent_msg().await;
1413+
// Check removal of some nonexistent "Chat-*" header to protect the code from future breakages.
1414+
let payload = sent_msg
1415+
.payload
1416+
.replace("Message-ID:", "Chat-Forty-Two: 42\r\nMessage-ID:");
1417+
let msg = MimeMessage::from_bytes(t, payload.as_bytes(), None)
1418+
.await
1419+
.unwrap();
1420+
assert!(msg.headers.contains_key("chat-version"));
1421+
assert!(!msg.headers.contains_key("chat-forty-two"));
1422+
Ok(())
1423+
}
1424+
14051425
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
14061426
async fn test_long_in_reply_to() -> Result<()> {
14071427
let t = TestContext::new_alice().await;

src/receive_imf/receive_imf_tests.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3682,6 +3682,24 @@ async fn test_unsigned_chat_group_hdr() -> Result<()> {
36823682
Ok(())
36833683
}
36843684

3685+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3686+
async fn test_ignore_protected_headers_in_outer_msg() -> Result<()> {
3687+
let mut tcm = TestContextManager::new();
3688+
let alice = &tcm.alice().await;
3689+
let bob = &tcm.bob().await;
3690+
let bob_chat_id = tcm.send_recv_accept(alice, bob, "hi").await.chat_id;
3691+
send_text_msg(bob, bob_chat_id, "hi all!".to_string()).await?;
3692+
let mut sent_msg = bob.pop_sent_msg().await;
3693+
sent_msg.payload = sent_msg.payload.replace(
3694+
"Chat-Version:",
3695+
"Auto-Submitted: auto-generated\r\nChat-Version:",
3696+
);
3697+
alice.recv_msg(&sent_msg).await;
3698+
let ab_contact = alice.add_or_lookup_contact(bob).await;
3699+
assert!(!ab_contact.is_bot());
3700+
Ok(())
3701+
}
3702+
36853703
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
36863704
async fn test_sync_member_list_on_rejoin() -> Result<()> {
36873705
let mut tcm = TestContextManager::new();

0 commit comments

Comments
 (0)