Skip to content

Commit bbf9f2b

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 exceptions are Subject and List-ID because there are known cases when they are only present in the outer message part. 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 bbf9f2b

File tree

4 files changed

+81
-42
lines changed

4 files changed

+81
-42
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: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ impl MimeMessage {
246246
MimeMessage::merge_headers(
247247
context,
248248
&mut headers,
249+
&mut headers_removed,
249250
&mut recipients,
250251
&mut past_members,
251252
&mut from,
@@ -273,6 +274,7 @@ impl MimeMessage {
273274
MimeMessage::merge_headers(
274275
context,
275276
&mut headers,
277+
&mut headers_removed,
276278
&mut recipients,
277279
&mut past_members,
278280
&mut from,
@@ -446,26 +448,11 @@ impl MimeMessage {
446448
});
447449
if let (Ok(mail), true) = (mail, is_encrypted) {
448450
if !signatures.is_empty() {
449-
// Remove unsigned opportunistically protected headers from messages considered
450-
// Autocrypt-encrypted / displayed with padlock.
451-
// 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-
}
451+
// Unsigned "Subject" mustn't be prepended to messages shown as encrypted
452+
// (<https://github.com/deltachat/deltachat-core-rust/issues/1790>).
453+
// Other headers are removed by `MimeMessage::merge_headers()` except for "List-ID".
454+
remove_header(&mut headers, "subject", &mut headers_removed);
455+
remove_header(&mut headers, "list-id", &mut headers_removed);
469456
}
470457

471458
// let known protected headers from the decrypted
@@ -478,6 +465,7 @@ impl MimeMessage {
478465
MimeMessage::merge_headers(
479466
context,
480467
&mut headers,
468+
&mut headers_removed,
481469
&mut recipients,
482470
&mut past_members,
483471
&mut inner_from,
@@ -1558,19 +1546,28 @@ impl MimeMessage {
15581546
fn merge_headers(
15591547
context: &Context,
15601548
headers: &mut HashMap<String, String>,
1549+
headers_removed: &mut HashSet<String>,
15611550
recipients: &mut Vec<SingleInfo>,
15621551
past_members: &mut Vec<SingleInfo>,
15631552
from: &mut Option<SingleInfo>,
15641553
list_post: &mut Option<String>,
15651554
chat_disposition_notification_to: &mut Option<SingleInfo>,
15661555
fields: &[mailparse::MailHeader<'_>],
15671556
) {
1557+
// There are known cases when Subject and List-ID only appear in the IMF section of
1558+
// signed-only messages. Such messages are shown as unencrypted anyway.
1559+
headers.retain(|k, _| {
1560+
!is_protected(k) || k == "subject" || k == "list-id" || {
1561+
headers_removed.insert(k.to_string());
1562+
false
1563+
}
1564+
});
15681565
for field in fields {
15691566
// lowercasing all headers is technically not correct, but makes things work better
15701567
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-
{
1568+
// Don't overwrite unprotected headers, but overwrite protected ones because DKIM
1569+
// signature applies to the last headers.
1570+
if !headers.contains_key(&key) || is_protected(&key) {
15741571
if key == HeaderDef::ChatDispositionNotificationTo.get_headername() {
15751572
match addrparse_header(field) {
15761573
Ok(addrlist) => {
@@ -2011,24 +2008,27 @@ pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
20112008

20122009
/// Returns true if the header overwrites outer header
20132010
/// 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-
)
2011+
fn is_protected(key: &str) -> bool {
2012+
key.starts_with("chat-")
2013+
|| matches!(
2014+
key,
2015+
"return-path"
2016+
| "auto-submitted"
2017+
| "autocrypt-setup-message"
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+
)
20322032
}
20332033

20342034
/// 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)