use std::fmt::Write;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthResult {
pub method: String,
pub result: String,
pub reason: Option<String>,
}
pub fn format_auth_results(hostname: &str, results: &[AuthResult]) -> String {
let mut buf = String::new();
write!(buf, "{hostname}").unwrap();
if results.is_empty() {
buf.push_str("; none");
return buf;
}
for r in results {
write!(buf, ";\r\n\t{}={}", r.method, r.result).unwrap();
if let Some(ref reason) = r.reason {
write!(buf, " reason=\"{reason}\"").unwrap();
}
}
buf
}
pub fn format_auth_results_header(hostname: &str, results: &[AuthResult]) -> String {
format!(
"Authentication-Results: {}\r\n",
format_auth_results(hostname, results)
)
}
pub fn build_auth_header(
hostname: &str,
spf: &str,
dkim: &str,
arc: &str,
dmarc: &str,
dmarc_reason: Option<&str>,
) -> String {
let results = vec![
AuthResult {
method: "spf".into(),
result: spf.into(),
reason: None,
},
AuthResult {
method: "dkim".into(),
result: dkim.into(),
reason: None,
},
AuthResult {
method: "arc".into(),
result: arc.into(),
reason: None,
},
AuthResult {
method: "dmarc".into(),
result: dmarc.into(),
reason: dmarc_reason.map(|s| s.to_string()),
},
];
format_auth_results_header(hostname, &results)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_pass() {
let results = vec![
AuthResult {
method: "spf".into(),
result: "pass".into(),
reason: None,
},
AuthResult {
method: "dkim".into(),
result: "pass".into(),
reason: None,
},
AuthResult {
method: "dmarc".into(),
result: "pass".into(),
reason: None,
},
];
let header = format_auth_results("mx.example.com", &results);
assert!(header.starts_with("mx.example.com;"));
assert!(header.contains("spf=pass"));
assert!(header.contains("dkim=pass"));
assert!(header.contains("dmarc=pass"));
}
#[test]
fn spf_fail_with_reason() {
let results = vec![AuthResult {
method: "spf".into(),
result: "fail".into(),
reason: Some("mechanism -all matched".into()),
}];
let header = format_auth_results("mx.example.com", &results);
assert!(header.contains("spf=fail"));
assert!(header.contains("reason=\"mechanism -all matched\""));
}
#[test]
fn no_results_yields_none() {
let header = format_auth_results("mx.example.com", &[]);
assert_eq!(header, "mx.example.com; none");
}
#[test]
fn full_header_starts_and_ends_correctly() {
let results = vec![AuthResult {
method: "spf".into(),
result: "pass".into(),
reason: None,
}];
let header = format_auth_results_header("mx.example.com", &results);
assert!(header.starts_with("Authentication-Results: mx.example.com;"));
assert!(header.ends_with("\r\n"));
}
#[test]
fn dmarc_policy_reason_round_trips() {
let results = vec![AuthResult {
method: "dmarc".into(),
result: "fail".into(),
reason: Some("policy=quarantine".into()),
}];
let header = format_auth_results("mx.example.com", &results);
assert!(header.contains("reason=\"policy=quarantine\""));
}
#[test]
fn full_pipeline_quadruple() {
let results = vec![
AuthResult { method: "spf".into(), result: "pass".into(), reason: None },
AuthResult { method: "dkim".into(), result: "pass".into(), reason: None },
AuthResult { method: "arc".into(), result: "none".into(), reason: None },
AuthResult { method: "dmarc".into(), result: "pass".into(), reason: None },
];
let header = format_auth_results("mx.mail.com", &results);
assert!(header.contains("spf=pass"));
assert!(header.contains("dkim=pass"));
assert!(header.contains("arc=none"));
assert!(header.contains("dmarc=pass"));
}
#[test]
fn multiline_folding() {
let results = vec![
AuthResult { method: "spf".into(), result: "pass".into(), reason: None },
AuthResult { method: "dmarc".into(), result: "pass".into(), reason: None },
];
let header = format_auth_results("mx.example.com", &results);
assert!(header.contains(";\r\n\t"));
}
#[test]
fn temperror_and_permerror_results_pass_through() {
for code in &["temperror", "permerror"] {
let results = vec![AuthResult {
method: "dmarc".into(),
result: (*code).into(),
reason: None,
}];
let header = format_auth_results("mx.example.com", &results);
assert!(header.contains(&format!("dmarc={code}")));
}
}
#[test]
fn build_auth_header_canonical_quadruple() {
let header = build_auth_header("mx.test.com", "pass", "pass", "none", "pass", None);
assert!(header.contains("Authentication-Results: mx.test.com"));
assert!(header.contains("spf=pass"));
assert!(header.contains("dkim=pass"));
assert!(header.contains("arc=none"));
assert!(header.contains("dmarc=pass"));
}
#[test]
fn build_auth_header_threads_dmarc_reason() {
let header = build_auth_header(
"mx.test.com", "pass", "fail", "none", "fail", Some("policy=reject"),
);
assert!(header.contains("dmarc=fail"));
assert!(header.contains("reason=\"policy=reject\""));
}
#[test]
fn build_auth_header_omits_dmarc_reason_when_none() {
let header = build_auth_header(
"mx.test.com", "pass", "pass", "none", "pass", None,
);
assert!(!header.contains("reason="));
}
}