mirror of
https://github.com/chatmail/core.git
synced 2026-05-05 14:26:30 +03:00
feat: Add Config::StdHeaderProtectionComposing (enables composing as defined in RFC 9788) (#7130)
And enable it by default as the standard Header Protection is backward-compatible. Also this tests extra IMF header removal when a message has standard Header Protection since now we can send such messages.
This commit is contained in:
@@ -445,6 +445,12 @@ pub enum Config {
|
|||||||
|
|
||||||
/// Return an error from `receive_imf_inner()` for a fully downloaded message. For tests.
|
/// Return an error from `receive_imf_inner()` for a fully downloaded message. For tests.
|
||||||
FailOnReceivingFullMsg,
|
FailOnReceivingFullMsg,
|
||||||
|
|
||||||
|
/// Enable composing emails with Header Protection as defined in
|
||||||
|
/// <https://www.rfc-editor.org/rfc/rfc9788.html> "Header Protection for Cryptographically
|
||||||
|
/// Protected Email".
|
||||||
|
#[strum(props(default = "1"))]
|
||||||
|
StdHeaderProtectionComposing,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
|||||||
@@ -1077,6 +1077,13 @@ impl Context {
|
|||||||
.await?
|
.await?
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
res.insert(
|
||||||
|
"std_header_protection_composing",
|
||||||
|
self.sql
|
||||||
|
.get_raw_config("std_header_protection_composing")
|
||||||
|
.await?
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
let elapsed = time_elapsed(&self.creation_time);
|
let elapsed = time_elapsed(&self.creation_time);
|
||||||
res.insert("uptime", duration_to_str(elapsed));
|
res.insert("uptime", duration_to_str(elapsed));
|
||||||
|
|||||||
@@ -1083,6 +1083,9 @@ impl MimeFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let use_std_header_protection = context
|
||||||
|
.get_config_bool(Config::StdHeaderProtectionComposing)
|
||||||
|
.await?;
|
||||||
let outer_message = if let Some(encryption_pubkeys) = self.encryption_pubkeys {
|
let outer_message = if let Some(encryption_pubkeys) = self.encryption_pubkeys {
|
||||||
// Store protected headers in the inner message.
|
// Store protected headers in the inner message.
|
||||||
let message = protected_headers
|
let message = protected_headers
|
||||||
@@ -1098,6 +1101,22 @@ impl MimeFactory {
|
|||||||
message.header(header, value)
|
message.header(header, value)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if use_std_header_protection {
|
||||||
|
message = unprotected_headers
|
||||||
|
.iter()
|
||||||
|
// Structural headers shouldn't be added as "HP-Outer". They are defined in
|
||||||
|
// <https://www.rfc-editor.org/rfc/rfc9787.html#structural-header-fields>.
|
||||||
|
.filter(|(name, _)| {
|
||||||
|
!(name.eq_ignore_ascii_case("mime-version")
|
||||||
|
|| name.eq_ignore_ascii_case("content-type")
|
||||||
|
|| name.eq_ignore_ascii_case("content-transfer-encoding")
|
||||||
|
|| name.eq_ignore_ascii_case("content-disposition"))
|
||||||
|
})
|
||||||
|
.fold(message, |message, (name, value)| {
|
||||||
|
message.header(format!("HP-Outer: {name}"), value.clone())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add gossip headers in chats with multiple recipients
|
// Add gossip headers in chats with multiple recipients
|
||||||
let multiple_recipients =
|
let multiple_recipients =
|
||||||
encryption_pubkeys.len() > 1 || context.get_config_bool(Config::BccSelf).await?;
|
encryption_pubkeys.len() > 1 || context.get_config_bool(Config::BccSelf).await?;
|
||||||
@@ -1187,7 +1206,13 @@ impl MimeFactory {
|
|||||||
for (h, v) in &mut message.headers {
|
for (h, v) in &mut message.headers {
|
||||||
if h == "Content-Type" {
|
if h == "Content-Type" {
|
||||||
if let mail_builder::headers::HeaderType::ContentType(ct) = v {
|
if let mail_builder::headers::HeaderType::ContentType(ct) = v {
|
||||||
*ct = ct.clone().attribute("protected-headers", "v1");
|
let mut ct_new = ct.clone();
|
||||||
|
ct_new = ct_new.attribute("protected-headers", "v1");
|
||||||
|
if use_std_header_protection {
|
||||||
|
ct_new = ct_new.attribute("hp", "cipher");
|
||||||
|
}
|
||||||
|
*ct = ct_new;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1325,7 +1350,13 @@ impl MimeFactory {
|
|||||||
for (h, v) in &mut message.headers {
|
for (h, v) in &mut message.headers {
|
||||||
if h == "Content-Type" {
|
if h == "Content-Type" {
|
||||||
if let mail_builder::headers::HeaderType::ContentType(ct) = v {
|
if let mail_builder::headers::HeaderType::ContentType(ct) = v {
|
||||||
*ct = ct.clone().attribute("protected-headers", "v1");
|
let mut ct_new = ct.clone();
|
||||||
|
ct_new = ct_new.attribute("protected-headers", "v1");
|
||||||
|
if use_std_header_protection {
|
||||||
|
ct_new = ct_new.attribute("hp", "clear");
|
||||||
|
}
|
||||||
|
*ct = ct_new;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -832,8 +832,8 @@ async fn test_protected_headers_directive() -> Result<()> {
|
|||||||
.count(),
|
.count(),
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
assert_eq!(part.match_indices("Subject:").count(), 1);
|
assert_eq!(part.match_indices("Subject:").count(), 2);
|
||||||
|
assert_eq!(part.match_indices("HP-Outer: Subject:").count(), 1);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1401,22 +1401,28 @@ async fn test_x_microsoft_original_message_id_precedence() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_extra_imf_chat_header() -> Result<()> {
|
async fn test_extra_imf_headers() -> Result<()> {
|
||||||
let mut tcm = TestContextManager::new();
|
let mut tcm = TestContextManager::new();
|
||||||
let t = &tcm.alice().await;
|
let t = &tcm.alice().await;
|
||||||
let chat_id = t.get_self_chat().await.id;
|
let chat_id = t.get_self_chat().await.id;
|
||||||
|
|
||||||
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
|
for std_hp_composing in [false, true] {
|
||||||
let sent_msg = t.pop_sent_msg().await;
|
t.set_config_bool(Config::StdHeaderProtectionComposing, std_hp_composing)
|
||||||
// Check removal of some nonexistent "Chat-*" header to protect the code from future breakages.
|
.await?;
|
||||||
let payload = sent_msg
|
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
|
||||||
.payload
|
let sent_msg = t.pop_sent_msg().await;
|
||||||
.replace("Message-ID:", "Chat-Forty-Two: 42\r\nMessage-ID:");
|
// Check removal of some nonexistent "Chat-*" header to protect the code from future
|
||||||
let msg = MimeMessage::from_bytes(t, payload.as_bytes(), None)
|
// breakages. But headers not prefixed with "Chat-" remain unless a message has standard
|
||||||
.await
|
// Header Protection.
|
||||||
.unwrap();
|
let payload = sent_msg.payload.replace(
|
||||||
assert!(msg.headers.contains_key("chat-version"));
|
"Message-ID:",
|
||||||
assert!(!msg.headers.contains_key("chat-forty-two"));
|
"Chat-Forty-Two: 42\r\nForty-Two: 42\r\nMessage-ID:",
|
||||||
|
);
|
||||||
|
let msg = MimeMessage::from_bytes(t, payload.as_bytes(), None).await?;
|
||||||
|
assert!(msg.headers.contains_key("chat-version"));
|
||||||
|
assert!(!msg.headers.contains_key("chat-forty-two"));
|
||||||
|
assert_ne!(msg.headers.contains_key("forty-two"), std_hp_composing);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user