Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Ignore protected headers in outer message part (#6357) #6370

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ mod tests {
let bob = TestContext::new_bob().await;
receive_imf(&bob, attachment_mime, false).await?;
let msg = bob.get_last_msg().await;
assert_eq!(msg.text, "Hello from Thunderbird!");
// Subject should be prepended because the attachment doesn't have "Chat-Version".
assert_eq!(msg.text, "Hello, Bob! – Hello from Thunderbird!");

Ok(())
}
Expand Down
61 changes: 28 additions & 33 deletions src/mimeparser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,20 +419,9 @@ impl MimeMessage {
gossip_headers,
)
.await?;
// Remove unsigned opportunistically protected headers from messages considered
// Autocrypt-encrypted / displayed with padlock.
// For "Subject" see <https://github.com/deltachat/deltachat-core-rust/issues/1790>.
for h in [
HeaderDef::Subject,
HeaderDef::ChatGroupId,
HeaderDef::ChatGroupName,
HeaderDef::ChatGroupNameChanged,
HeaderDef::ChatGroupAvatar,
HeaderDef::ChatGroupMemberRemoved,
HeaderDef::ChatGroupMemberAdded,
] {
headers.remove(h.get_headername());
}
// Other headers are removed by `MimeMessage::merge_headers()`.
headers.remove("subject");
}

// let known protected headers from the decrypted
Expand Down Expand Up @@ -1528,12 +1517,15 @@ impl MimeMessage {
chat_disposition_notification_to: &mut Option<SingleInfo>,
fields: &[mailparse::MailHeader<'_>],
) {
// Keep Subject so that it's displayed for signed-only messages. They are shown w/o a
// padlock anyway.
headers.retain(|k, _| !is_protected(k) || k == "subject");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also extend a test test_is_bot with a non-chat message? It's important that non-DC unencrypted messages from auto-repliers have dc_msg_is_bot returning true. Seems to work because for such messages merge_headers is never called, but better test it explicitly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried, but this doesn't work, if it's a non-chat message, we don't mark a contact as bot and this looks intentional:

            let is_bot = parser.headers.get("auto-submitted")
                == Some(&"auto-generated".to_string())
                && parser.headers.contains_key("chat-version");

Anyway i improved the test to record the current behaviour.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a bug, message should be marked as a bot message, but the contact should not become a bot in this case. I created an issue #6373

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the second commit because you're fixing this in #6374

for field in fields {
// lowercasing all headers is technically not correct, but makes things work better
let key = field.get_key().to_lowercase();
if !headers.contains_key(&key) || // key already exists, only overwrite known types (protected headers)
is_known(&key) || key.starts_with("chat-")
{
// Don't overwrite unprotected headers, but overwrite protected ones because DKIM
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment, why is it important here that DKIM signature applies to last headers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least test_take_last_header() breaks if i remove || is_protected(&key) here. For unprotected headers we just don't mind. Feel free to improve the comment

// signature applies to the last headers.
if !headers.contains_key(&key) || is_protected(&key) {
if key == HeaderDef::ChatDispositionNotificationTo.get_headername() {
match addrparse_header(field) {
Ok(addrlist) => {
Expand Down Expand Up @@ -1929,23 +1921,26 @@ pub(crate) fn parse_message_id(ids: &str) -> Result<String> {

/// Returns true if the header overwrites outer header
/// when it comes from protected headers.
fn is_known(key: &str) -> bool {
matches!(
key,
"return-path"
| "date"
| "from"
| "sender"
| "reply-to"
| "to"
| "cc"
| "bcc"
| "message-id"
| "in-reply-to"
| "references"
| "subject"
| "secure-join"
)
fn is_protected(key: &str) -> bool {
key.starts_with("chat-")
|| matches!(
key,
"return-path"
| "auto-submitted"
| "autocrypt-setup-message"
| "date"
| "from"
| "sender"
| "reply-to"
| "to"
| "cc"
| "bcc"
| "message-id"
| "in-reply-to"
| "references"
| "subject"
| "secure-join"
)
}

/// Parsed MIME part.
Expand Down
18 changes: 18 additions & 0 deletions src/receive_imf/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4052,6 +4052,24 @@ async fn test_unsigned_chat_group_hdr() -> Result<()> {
Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_ignore_protected_headers_in_outer_msg() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let bob_chat_id = tcm.send_recv_accept(alice, bob, "hi").await.chat_id;
send_text_msg(bob, bob_chat_id, "hi all!".to_string()).await?;
let mut sent_msg = bob.pop_sent_msg().await;
sent_msg.payload = sent_msg.payload.replace(
"Chat-Version:",
"Auto-Submitted: auto-generated\r\nChat-Version:",
);
alice.recv_msg(&sent_msg).await;
let ab_contact = alice.add_or_lookup_contact(bob).await;
assert!(!ab_contact.is_bot());
Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sync_member_list_on_rejoin() -> Result<()> {
let mut tcm = TestContextManager::new();
Expand Down
Loading