keyhog_core/report/
html.rs1use std::io::Write;
4
5use crate::VerifiedFinding;
6
7use super::{ReportError, Reporter, WriterBackedReporter};
8
9fn escape_for_script(serialized: &str) -> String {
21 let mut out = String::with_capacity(serialized.len());
22 for ch in serialized.chars() {
23 match ch {
24 '<' => out.push_str("\\u003c"),
25 '>' => out.push_str("\\u003e"),
26 '/' => out.push_str("\\u002f"),
27 '\u{2028}' => out.push_str("\\u2028"),
29 '\u{2029}' => out.push_str("\\u2029"),
30 other => out.push(other),
31 }
32 }
33 out
34}
35
36pub struct HtmlReporter<W: Write + Send> {
38 writer: W,
39 findings: Vec<VerifiedFinding>,
40}
41
42impl<W: Write + Send> HtmlReporter<W> {
43 pub fn new(writer: W) -> Self {
45 Self {
46 writer,
47 findings: Vec::new(),
48 }
49 }
50}
51
52impl<W: Write + Send> Reporter for HtmlReporter<W> {
53 fn report(&mut self, finding: &VerifiedFinding) -> Result<(), ReportError> {
54 self.findings.push(finding.clone());
55 Ok(())
56 }
57
58 fn finish(&mut self) -> Result<(), ReportError> {
59 let mut findings_value = serde_json::to_value(&self.findings)?;
67 if let Some(arr) = findings_value.as_array_mut() {
68 for finding in arr {
69 if let Some(v) = finding.get_mut("verification") {
70 if v.as_object().is_some_and(|o| o.contains_key("error")) {
71 *v = serde_json::Value::String("error".to_string());
72 }
73 }
74 }
75 }
76 let serialized_findings = escape_for_script(&serde_json::to_string(&findings_value)?);
77
78 writeln!(self.writer, "<!DOCTYPE html>")?;
79 writeln!(self.writer, "<html lang=\"en\" data-theme=\"obsidian\">")?;
80 writeln!(self.writer, "<head>")?;
81 writeln!(self.writer, " <meta charset=\"UTF-8\">")?;
82 writeln!(
83 self.writer,
84 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
85 )?;
86 writeln!(self.writer, " <title>KeyHog Secret Scan Report</title>")?;
87 writeln!(self.writer, " <style>")?;
88 writeln!(self.writer, "{}", include_str!("html_styles.css"))?;
89 writeln!(self.writer, " </style>")?;
90 writeln!(self.writer, "</head>")?;
91 writeln!(self.writer, "<body>")?;
92
93 writeln!(self.writer, "{}", include_str!("html_body.html"))?;
94
95 writeln!(self.writer, " <script>")?;
96 writeln!(
97 self.writer,
98 " const rawFindings = {};",
99 serialized_findings
100 )?;
101 writeln!(self.writer, "{}", include_str!("html_script.js"))?;
102 writeln!(self.writer, " </script>")?;
103 writeln!(self.writer, "</body>")?;
104 writeln!(self.writer, "</html>")?;
105
106 self.flush_writer()
107 }
108}
109
110impl<W: Write + Send> WriterBackedReporter for HtmlReporter<W> {
111 type Writer = W;
112
113 fn writer_mut(&mut self) -> &mut Self::Writer {
114 &mut self.writer
115 }
116}