use std::io::Write;
use crate::VerifiedFinding;
use super::{ReportError, Reporter, WriterBackedReporter};
fn escape_for_script(serialized: &str) -> String {
let mut out = String::with_capacity(serialized.len());
for ch in serialized.chars() {
match ch {
'<' => out.push_str("\\u003c"),
'>' => out.push_str("\\u003e"),
'/' => out.push_str("\\u002f"),
'\u{2028}' => out.push_str("\\u2028"),
'\u{2029}' => out.push_str("\\u2029"),
other => out.push(other),
}
}
out
}
pub struct HtmlReporter<W: Write + Send> {
writer: W,
findings: Vec<VerifiedFinding>,
}
impl<W: Write + Send> HtmlReporter<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
findings: Vec::new(),
}
}
}
impl<W: Write + Send> Reporter for HtmlReporter<W> {
fn report(&mut self, finding: &VerifiedFinding) -> Result<(), ReportError> {
self.findings.push(finding.clone());
Ok(())
}
fn finish(&mut self) -> Result<(), ReportError> {
let mut findings_value = serde_json::to_value(&self.findings)?;
if let Some(arr) = findings_value.as_array_mut() {
for finding in arr {
if let Some(v) = finding.get_mut("verification") {
if v.as_object().is_some_and(|o| o.contains_key("error")) {
*v = serde_json::Value::String("error".to_string());
}
}
}
}
let serialized_findings = escape_for_script(&serde_json::to_string(&findings_value)?);
writeln!(self.writer, "<!DOCTYPE html>")?;
writeln!(self.writer, "<html lang=\"en\" data-theme=\"obsidian\">")?;
writeln!(self.writer, "<head>")?;
writeln!(self.writer, " <meta charset=\"UTF-8\">")?;
writeln!(
self.writer,
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
)?;
writeln!(self.writer, " <title>KeyHog Secret Scan Report</title>")?;
writeln!(self.writer, " <style>")?;
writeln!(self.writer, "{}", include_str!("html_styles.css"))?;
writeln!(self.writer, " </style>")?;
writeln!(self.writer, "</head>")?;
writeln!(self.writer, "<body>")?;
writeln!(self.writer, "{}", include_str!("html_body.html"))?;
writeln!(self.writer, " <script>")?;
writeln!(
self.writer,
" const rawFindings = {};",
serialized_findings
)?;
writeln!(self.writer, "{}", include_str!("html_script.js"))?;
writeln!(self.writer, " </script>")?;
writeln!(self.writer, "</body>")?;
writeln!(self.writer, "</html>")?;
self.flush_writer()
}
}
impl<W: Write + Send> WriterBackedReporter for HtmlReporter<W> {
type Writer = W;
fn writer_mut(&mut self) -> &mut Self::Writer {
&mut self.writer
}
}