feat: Ignore unprotected headers if Content-Type has "hp" parameter (#7130)

This is a part of implementation of https://www.rfc-editor.org/rfc/rfc9788 "Header Protection for
Cryptographically Protected Email".
This commit is contained in:
iequidoo
2025-10-26 16:33:21 -03:00
committed by iequidoo
parent 6611a9fa02
commit 966ea28f83

View File

@@ -271,7 +271,7 @@ impl MimeMessage {
&mut from, &mut from,
&mut list_post, &mut list_post,
&mut chat_disposition_notification_to, &mut chat_disposition_notification_to,
&mail.headers, &mail,
); );
headers.retain(|k, _| { headers.retain(|k, _| {
!is_hidden(k) || { !is_hidden(k) || {
@@ -299,7 +299,7 @@ impl MimeMessage {
&mut from, &mut from,
&mut list_post, &mut list_post,
&mut chat_disposition_notification_to, &mut chat_disposition_notification_to,
&part.headers, part,
); );
(part, part.ctype.mimetype.parse::<Mime>()?) (part, part.ctype.mimetype.parse::<Mime>()?)
} else { } else {
@@ -536,7 +536,7 @@ impl MimeMessage {
&mut inner_from, &mut inner_from,
&mut list_post, &mut list_post,
&mut chat_disposition_notification_to, &mut chat_disposition_notification_to,
&mail.headers, mail,
); );
if !signatures.is_empty() { if !signatures.is_empty() {
@@ -1632,6 +1632,11 @@ impl MimeMessage {
} }
} }
/// Merges headers from the email `part` into `headers` respecting header protection.
/// Should only be called with nonempty `headers` if `part` is a root of the Cryptographic
/// Payload as defined in <https://www.rfc-editor.org/rfc/rfc9788.html> "Header Protection for
/// Cryptographically Protected Email", otherwise this may unnecessarily discard headers from
/// outer parts.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn merge_headers( fn merge_headers(
context: &Context, context: &Context,
@@ -1642,10 +1647,14 @@ impl MimeMessage {
from: &mut Option<SingleInfo>, from: &mut Option<SingleInfo>,
list_post: &mut Option<String>, list_post: &mut Option<String>,
chat_disposition_notification_to: &mut Option<SingleInfo>, chat_disposition_notification_to: &mut Option<SingleInfo>,
fields: &[mailparse::MailHeader<'_>], part: &mailparse::ParsedMail,
) { ) {
let fields = &part.headers;
// See <https://www.rfc-editor.org/rfc/rfc9788.html>.
let has_header_protection = part.ctype.params.contains_key("hp");
headers.retain(|k, _| { headers.retain(|k, _| {
!is_protected(k) || { !(has_header_protection || is_protected(k)) || {
headers_removed.insert(k.to_string()); headers_removed.insert(k.to_string());
false false
} }
@@ -2096,7 +2105,8 @@ pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
} }
/// Returns whether the outer header value must be ignored if the message contains a signed (and /// Returns whether the outer header value must be ignored if the message contains a signed (and
/// optionally encrypted) part. /// optionally encrypted) part. This is independent from the modern Header Protection defined in
/// <https://www.rfc-editor.org/rfc/rfc9788.html>.
/// ///
/// NB: There are known cases when Subject and List-ID only appear in the outer headers of /// NB: There are known cases when Subject and List-ID only appear in the outer headers of
/// signed-only messages. Such messages are shown as unencrypted anyway. /// signed-only messages. Such messages are shown as unencrypted anyway.