use std::fmt::Write as _;
use std::io::Write;
use crate::engine::analyzer::{AnalysisResult, Severity};
pub(crate) fn render(results: &[AnalysisResult], out: &mut dyn Write) -> anyhow::Result<()> {
for result in results {
writeln!(out, "==============================")?;
writeln!(out, "Host: {}", sanitize_control(&result.host))?;
if let Some(os) = &result.os_guess {
writeln!(out, "OS: {}", sanitize_control(os))?;
}
if let Some(fp) = &result.impl_fingerprint {
writeln!(out, "Impl: {}", sanitize_control(fp))?;
}
writeln!(out, "NFS versions: {}", result.nfs_versions.join(", "))?;
writeln!(out, "Timestamp: {}", result.timestamp)?;
writeln!(out)?;
if result.findings.is_empty() {
writeln!(out, " No findings.")?;
} else {
for finding in &result.findings {
writeln!(out, " [{}] {} -- {}", severity_label(finding.severity), finding.id, sanitize_control(&finding.title))?;
if let Some(export) = &finding.export {
writeln!(out, " Export: {}", sanitize_control(export))?;
}
writeln!(out, " Description: {}", sanitize_control(&finding.description))?;
writeln!(out, " Evidence: {}", sanitize_control(&finding.evidence))?;
writeln!(out, " Remediation: {}", sanitize_control(&finding.remediation))?;
writeln!(out)?;
}
}
}
Ok(())
}
pub(super) fn sanitize_control(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
let cp = u32::from(ch);
if cp < 0x20 || cp == 0x7f || (0x80..=0x9f).contains(&cp) {
let _ = write!(out, "\\x{cp:02x}");
} else if is_bidi_or_zero_width(cp) {
let _ = write!(out, "\\u{{{cp:04x}}}");
} else {
out.push(ch);
}
}
out
}
const fn is_bidi_or_zero_width(cp: u32) -> bool {
matches!(
cp,
0x200b..=0x200f | 0x202a..=0x202e | 0x2066..=0x2069 | 0xfeff )
}
const fn severity_label(sev: Severity) -> &'static str {
match sev {
Severity::Critical => "CRITICAL",
Severity::High => "HIGH",
Severity::Medium => "MEDIUM",
Severity::Low => "LOW",
Severity::Info => "INFO",
}
}