feat(report): add logo and TLP classification badge to the recovery report
The HTML/PDF report had no visual identity (no logo) and no sharing classification. Embeds the Guará logo as a base64 data URI (so the standalone export still renders offline, same approach as Sabiá's report) and adds a TLP:AMBER badge next to the existing "Uso autorizado" one — forensic recovery findings are sensitive and worth marking for limited disclosure, same as any other Ciberfauna report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
43e9860b41
commit
7683967650
1 file changed
+37
-1
@@ -101,6 +101,37 @@ pub fn csv(found: &[&Found]) -> String {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The Guará logo, embedded so the report renders identically as a
|
||||||
|
/// standalone, offline HTML/PDF export (same reasoning as
|
||||||
|
/// `Sabiá/crates/sabia-core/src/report.rs`, which this mirrors): a
|
||||||
|
/// `src="/logo.png"` reference would 404 once the file is saved outside the
|
||||||
|
/// app's own server, and a forensic report that only half-renders without
|
||||||
|
/// network access defeats its purpose as a portable piece of evidence.
|
||||||
|
const LOGO_PNG: &[u8] = include_bytes!("../../../logo.png");
|
||||||
|
const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
|
/// Encodes the embedded logo as a `data:image/png;base64,...` URI.
|
||||||
|
fn logo_data_uri() -> String {
|
||||||
|
let mut out = String::with_capacity(LOGO_PNG.len().div_ceil(3) * 4 + 22);
|
||||||
|
out.push_str("data:image/png;base64,");
|
||||||
|
for chunk in LOGO_PNG.chunks(3) {
|
||||||
|
let b0 = chunk[0];
|
||||||
|
let b1 = chunk.get(1).copied();
|
||||||
|
let b2 = chunk.get(2).copied();
|
||||||
|
out.push(B64[(b0 >> 2) as usize] as char);
|
||||||
|
out.push(B64[(((b0 & 0b11) << 4) | (b1.unwrap_or(0) >> 4)) as usize] as char);
|
||||||
|
out.push(match b1 {
|
||||||
|
Some(b1) => B64[(((b1 & 0b1111) << 2) | (b2.unwrap_or(0) >> 6)) as usize] as char,
|
||||||
|
None => '=',
|
||||||
|
});
|
||||||
|
out.push(match b2 {
|
||||||
|
Some(b2) => B64[(b2 & 0b111111) as usize] as char,
|
||||||
|
None => '=',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
fn esc(s: &str) -> String {
|
fn esc(s: &str) -> String {
|
||||||
s.replace('&', "&")
|
s.replace('&', "&")
|
||||||
.replace('<', "<")
|
.replace('<', "<")
|
||||||
@@ -135,6 +166,9 @@ pub fn html(r: &ReportInput) -> String {
|
|||||||
r#"<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Guará: relatório de recuperação</title><style>
|
r#"<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Guará: relatório de recuperação</title><style>
|
||||||
:root{color-scheme:light}*{box-sizing:border-box}body{margin:0;font:14px/1.5 -apple-system,"Segoe UI",Inter,Roboto,sans-serif;color:#10201f;background:#f3f7f7}
|
:root{color-scheme:light}*{box-sizing:border-box}body{margin:0;font:14px/1.5 -apple-system,"Segoe UI",Inter,Roboto,sans-serif;color:#10201f;background:#f3f7f7}
|
||||||
main{max-width:1100px;margin:0 auto;padding:36px 28px 60px}header{border-bottom:3px solid #0f766e;padding-bottom:14px;margin-bottom:22px}
|
main{max-width:1100px;margin:0 auto;padding:36px 28px 60px}header{border-bottom:3px solid #0f766e;padding-bottom:14px;margin-bottom:22px}
|
||||||
|
.brand{display:flex;align-items:center;gap:10px}.brand img{width:30px;height:30px;object-fit:contain}
|
||||||
|
.badges{display:flex;gap:6px;margin-top:8px}.badge{font:700 11px ui-monospace,Menlo,Consolas,monospace;letter-spacing:.08em;text-transform:uppercase;padding:2px 8px;border-radius:3px;border:1px solid #0f766e;color:#0f766e;background:#e7f3f2}
|
||||||
|
.badge-tlp{color:#8a5a00;border-color:#8a5a00;background:#fff0cf}
|
||||||
h1{margin:0;font-size:28px;letter-spacing:-.02em}h2{margin:30px 0 10px;font-size:17px;color:#0f766e}small,.mut{color:#5b6f6d}
|
h1{margin:0;font-size:28px;letter-spacing:-.02em}h2{margin:30px 0 10px;font-size:17px;color:#0f766e}small,.mut{color:#5b6f6d}
|
||||||
table{width:100%;border-collapse:collapse;background:#fff;border-radius:10px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.08)}
|
table{width:100%;border-collapse:collapse;background:#fff;border-radius:10px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.08)}
|
||||||
th,td{padding:7px 10px;text-align:left;border-bottom:1px solid #e4eded;vertical-align:top;font-size:13px}th{background:#e7f3f2;color:#0b4f4a;font-weight:600}
|
th,td{padding:7px 10px;text-align:left;border-bottom:1px solid #e4eded;vertical-align:top;font-size:13px}th{background:#e7f3f2;color:#0b4f4a;font-weight:600}
|
||||||
@@ -145,7 +179,9 @@ td.n{text-align:right;font-variant-numeric:tabular-nums}.mono{font-family:ui-mon
|
|||||||
footer{margin-top:34px;color:#5b6f6d;font-size:12px}@media print{body{background:#fff}main{padding:0}table{box-shadow:none}h2{break-after:avoid}tr{break-inside:avoid}}
|
footer{margin-top:34px;color:#5b6f6d;font-size:12px}@media print{body{background:#fff}main{padding:0}table{box-shadow:none}h2{break-after:avoid}tr{break-inside:avoid}}
|
||||||
</style></head><body><main>"#,
|
</style></head><body><main>"#,
|
||||||
);
|
);
|
||||||
o.push_str("<header><h1>Relatório de recuperação de arquivos</h1><div class=\"mut\">Gerado pelo Guará ");
|
o.push_str("<header><div class=\"brand\"><img src=\"");
|
||||||
|
o.push_str(&logo_data_uri());
|
||||||
|
o.push_str("\" alt=\"\"><h1>Relatório de recuperação de arquivos</h1></div><div class=\"badges\"><span class=\"badge badge-tlp\">TLP:AMBER</span><span class=\"badge\">Uso autorizado</span></div><div class=\"mut\">Gerado pelo Guará ");
|
||||||
o.push_str(env!("CARGO_PKG_VERSION"));
|
o.push_str(env!("CARGO_PKG_VERSION"));
|
||||||
o.push_str(&format!(" em {}</div></header>", fmt_unix(now)));
|
o.push_str(&format!(" em {}</div></header>", fmt_unix(now)));
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user