///|
pub fn suite_input(
domain : String,
spf_text : String,
dmarc_text : String,
dkim_text : String,
mtasts_text : String,
tlsrpt_text : String,
bimi_text : String,
) -> MailSecuritySuiteInput {
{
domain,
spf_text,
dmarc_text,
dkim_text,
mtasts_text,
tlsrpt_text,
bimi_text,
}
}
///|
pub fn parse_suite_input(row : String) -> MailSecuritySuiteInput {
let cells = row.split("|").to_array()
suite_input(
row_cell(cells, 0),
row_cell(cells, 1),
row_cell(cells, 2),
row_cell(cells, 3),
row_cell(cells, 4).replace_all(old="\\n", new="\n"),
row_cell(cells, 5),
row_cell(cells, 6),
)
}
///|
fn row_cell(cells : Array[StringView], index : Int) -> String {
if index < cells.length() {
cells[index].trim().to_owned()
} else {
""
}
}
///|
pub fn analyze_suite_input(
input : MailSecuritySuiteInput,
) -> MailSecuritySuiteReport {
analyze_suite(
input.domain,
input.spf_text,
input.dmarc_text,
input.dkim_text,
input.mtasts_text,
input.tlsrpt_text,
input.bimi_text,
)
}
///|
pub fn analyze_suite_inputs(
inputs : Array[MailSecuritySuiteInput],
) -> BatchSecurityReport {
let reports = Array::new(capacity=inputs.length())
for input in inputs {
reports.push(analyze_suite_input(input))
}
batch_from_reports(reports)
}
///|
pub fn analyze_suite_rows(rows : Array[String]) -> BatchSecurityReport {
let inputs = Array::new(capacity=rows.length())
for row in rows {
let trimmed = row.trim().to_owned()
if trimmed.is_empty() {
continue
}
if trimmed.has_prefix("#") {
continue
}
inputs.push(parse_suite_input(trimmed))
}
analyze_suite_inputs(inputs)
}
///|
pub fn batch_from_reports(
reports : Array[MailSecuritySuiteReport],
) -> BatchSecurityReport {
let findings = Array::new(capacity=reports.length() + 4)
let actions = Array::new()
let domains_checked = reports.length()
let mut score_total = 0
let mut lowest_score = 100
let mut highest_score = 0
if reports.length() == 0 {
lowest_score = 0
findings.push(
fail(
"batch.empty", "Batch has no domain rows", "A batch report needs at least one domain input.",
"", "Provide one or more pipe-separated domain rows.",
),
)
}
for report in reports {
score_total += positive_or_zero(report.score)
lowest_score = min_int(lowest_score, report.score)
highest_score = max_int(highest_score, report.score)
append_batch_actions(actions, report.actions)
if report.verdict == "risky" {
findings.push(
fail(
"batch.domain-risky",
"Batch contains a risky domain",
"At least one domain has hard failures.",
report.domain,
"Review that domain's action list before rollout.",
penalty=8,
),
)
} else if report.verdict == "monitor" {
findings.push(
warn(
"batch.domain-monitor",
"Batch contains a monitor domain",
"At least one domain still has warnings.",
report.domain,
"Resolve warnings or document accepted rollout risk.",
penalty=3,
),
)
}
}
let average_score = if domains_checked == 0 {
0
} else {
score_total / domains_checked
}
if domains_checked > 0 {
findings.push(
pass(
"batch.domains",
"Batch domain rows were analyzed",
domains_checked.to_string(),
),
)
findings.push(
pass(
"batch.average-score",
"Batch average score was calculated",
average_score.to_string(),
),
)
}
let summary = summarize_findings(findings)
{
reports,
domains_checked,
average_score,
lowest_score,
highest_score,
summary,
actions,
findings,
}
}
///|
fn append_batch_actions(
target : Array[ActionItem],
source : Array[ActionItem],
) -> Unit {
for action in source {
target.push(action)
}
}
///|
pub fn BatchSecurityReport::risky_count(self : BatchSecurityReport) -> Int {
let mut count = 0
for report in self.reports {
if report.verdict == "risky" {
count += 1
}
}
count
}
///|
pub fn BatchSecurityReport::monitor_count(self : BatchSecurityReport) -> Int {
let mut count = 0
for report in self.reports {
if report.verdict == "monitor" {
count += 1
}
}
count
}
///|
pub fn BatchSecurityReport::protected_count(self : BatchSecurityReport) -> Int {
let mut count = 0
for report in self.reports {
if report.verdict == "protected" {
count += 1
}
}
count
}
///|
pub fn BatchSecurityReport::has_failures(self : BatchSecurityReport) -> Bool {
self.summary.fail_count > 0 || self.risky_count() > 0
}
///|
pub fn BatchSecurityReport::to_markdown(self : BatchSecurityReport) -> String {
let out = StringBuilder()
out.write_string("# MailShield Batch Report\n\n")
out.write_string("- Domains checked: ")
out.write_string(self.domains_checked.to_string())
out.write_string("\n")
out.write_string("- Average score: ")
out.write_string(self.average_score.to_string())
out.write_string("\n")
out.write_string("- Lowest score: ")
out.write_string(self.lowest_score.to_string())
out.write_string("\n")
out.write_string("- Highest score: ")
out.write_string(self.highest_score.to_string())
out.write_string("\n")
out.write_string("- Protected: ")
out.write_string(self.protected_count().to_string())
out.write_string("\n")
out.write_string("- Monitor: ")
out.write_string(self.monitor_count().to_string())
out.write_string("\n")
out.write_string("- Risky: ")
out.write_string(self.risky_count().to_string())
out.write_string("\n\n")
out.write_string("## Domains\n\n")
for report in self.reports {
out.write_string("- ")
out.write_string(report.domain)
out.write_string(": ")
out.write_string(report.verdict)
out.write_string(" (")
out.write_string(report.score.to_string())
out.write_string("/100, ")
out.write_string(report.coverage_label())
out.write_string(")\n")
}
out.write_string("\n## Action Items\n\n")
if self.actions.length() == 0 {
out.write_string("- No action items.\n")
} else {
for action in self.actions {
out.write_string(action.to_markdown())
out.write_string("\n")
}
}
out.to_string()
}
///|
pub fn BatchSecurityReport::to_json(self : BatchSecurityReport) -> String {
let out = StringBuilder()
out.write_string("{")
append_json_int(out, "domains_checked", self.domains_checked)
out.write_string(",")
append_json_int(out, "average_score", self.average_score)
out.write_string(",")
append_json_int(out, "lowest_score", self.lowest_score)
out.write_string(",")
append_json_int(out, "highest_score", self.highest_score)
out.write_string(",")
append_json_int(out, "protected", self.protected_count())
out.write_string(",")
append_json_int(out, "monitor", self.monitor_count())
out.write_string(",")
append_json_int(out, "risky", self.risky_count())
out.write_string(",\"domains\":[")
for i in 0.. 0 {
out.write_string(",")
}
let report = self.reports[i]
out.write_string("{")
append_json_string(out, "domain", report.domain)
out.write_string(",")
append_json_string(out, "verdict", report.verdict)
out.write_string(",")
append_json_int(out, "score", report.score)
out.write_string(",")
append_json_string(out, "coverage", report.coverage_label())
out.write_string("}")
}
out.write_string("]}")
out.to_string()
}