// 输出层:Authentication-Results 头、text 与 json。
//
// 三种输出都来自同一份 AuthReport,因此不会出现「渲染和判定不一致」。
///|
fn json_escape(text : StringView) -> String {
let out = StringBuilder()
for c in text {
match c {
'"' => out.write_string("\\\"")
'\\' => out.write_string("\\\\")
'\n' => out.write_string("\\n")
'\r' => out.write_string("\\r")
'\t' => out.write_string("\\t")
_ => out.write_char(c)
}
}
out.to_string()
}
///|
/// 生成 RFC 8601 的 Authentication-Results 头(不含字段名)。
pub fn authentication_results(
report : AuthReport,
authserv_id : String,
) -> String {
let parts : Array[String] = []
for result in report.dkim {
parts.push(
"dkim=\{result.status.label()} header.d=\{result.domain} header.s=\{result.selector} (\{result.reason})",
)
}
match report.spf {
Some(result) =>
parts.push(
"spf=\{result.status.label()} smtp.mailfrom=\{result.domain} (\{result.mechanism})",
)
None => ()
}
match report.dmarc {
Some(result) =>
parts.push(
"dmarc=\{result.status.label()} header.from=\{result.domain} (policy=\{result.policy}, disposition=\{result.disposition})",
)
None => ()
}
let out = StringBuilder()
out.write_string(authserv_id)
for part in parts {
out.write_string(";\r\n\t")
out.write_string(part)
}
out.to_string()
}
///|
/// 人类可读的 text 输出。
pub fn render_text(report : AuthReport) -> String {
let out = StringBuilder()
if report.dkim.length() == 0 {
out.write_string("dkim: none\n")
}
for result in report.dkim {
out.write_string(
"dkim: \{result.status.label()} d=\{result.domain} s=\{result.selector}\n",
)
out.write_string(" reason: \{result.reason}\n")
for item in result.evidence {
out.write_string(" - \{item.label}: \{item.detail}\n")
}
}
match report.spf {
Some(result) => {
out.write_string(
"spf: \{result.status.label()} domain=\{result.domain}\n",
)
out.write_string(" mechanism: \{result.mechanism}\n")
out.write_string(" reason: \{result.reason}\n")
out.write_string(
" dns: \{result.dns_lookups} lookups, \{result.void_lookups} void\n",
)
}
None => out.write_string("spf: not evaluated\n")
}
match report.dmarc {
Some(result) => {
out.write_string(
"dmarc: \{result.status.label()} domain=\{result.domain}\n",
)
out.write_string(
" policy: \{result.policy}, disposition: \{result.disposition}\n",
)
out.write_string(
" aligned: dkim=\{result.dkim_aligned}, spf=\{result.spf_aligned}\n",
)
out.write_string(" reason: \{result.reason}\n")
}
None => out.write_string("dmarc: not evaluated\n")
}
out.to_string()
}
///|
/// 稳定的 json 输出,字段顺序固定,方便 CI 对拍。
pub fn render_json(report : AuthReport) -> String {
let out = StringBuilder()
out.write_string("{\"dkim\":[")
for i in 0.. 0 {
out.write_string(",")
}
let result = report.dkim[i]
out.write_string("{\"domain\":\"\{json_escape(result.domain)}\"")
out.write_string(",\"selector\":\"\{json_escape(result.selector)}\"")
out.write_string(",\"algorithm\":\"\{json_escape(result.algorithm)}\"")
out.write_string(",\"status\":\"\{result.status.label()}\"")
out.write_string(",\"reason\":\"\{json_escape(result.reason)}\"}")
}
out.write_string("],\"spf\":")
match report.spf {
Some(result) => {
out.write_string("{\"domain\":\"\{json_escape(result.domain)}\"")
out.write_string(",\"identity\":\"\{json_escape(result.identity)}\"")
out.write_string(",\"status\":\"\{result.status.label()}\"")
out.write_string(",\"mechanism\":\"\{json_escape(result.mechanism)}\"")
out.write_string(",\"reason\":\"\{json_escape(result.reason)}\"")
out.write_string(",\"dns_lookups\":\{result.dns_lookups}")
out.write_string(",\"void_lookups\":\{result.void_lookups}}")
}
None => out.write_string("null")
}
out.write_string(",\"dmarc\":")
match report.dmarc {
Some(result) => {
out.write_string("{\"domain\":\"\{json_escape(result.domain)}\"")
out.write_string(",\"status\":\"\{result.status.label()}\"")
out.write_string(",\"policy\":\"\{json_escape(result.policy)}\"")
out.write_string(
",\"disposition\":\"\{json_escape(result.disposition)}\"",
)
out.write_string(",\"dkim_aligned\":\{result.dkim_aligned}")
out.write_string(",\"spf_aligned\":\{result.spf_aligned}")
out.write_string(",\"reason\":\"\{json_escape(result.reason)}\"}")
}
None => out.write_string("null")
}
out.write_string("}")
out.to_string()
}