use std::io::{self, BufRead, ErrorKind, Write};
use std::panic::{self, AssertUnwindSafe};
use std::path::Path;
use crate::config;
use crate::scrub::{ScrubResult, Scrubber};
const WITHHELD: &str = "[redacted] line withheld";
const MAX_PEM_BLOCK_LINES: usize = 64;
pub fn run(cwd: Option<&Path>) -> Result<(), String> {
panic::set_hook(Box::new(|_| {}));
let home_dir = config::home_dir();
let home_dir = home_dir.as_deref();
let scrubber = config::load(home_dir, cwd)
.map_err(String::from)
.and_then(|config| Scrubber::new(&config, &config::load_engine(home_dir, cwd)?))
.map_err(|e| format!("scrub: {e}"))?;
let result = scrub_lines(io::stdin().lock(), &mut io::stdout().lock(), &|text| {
scrubber.scrub(text)
});
match result {
Err(error) if error.kind() != ErrorKind::BrokenPipe => {
Err(format!("scrub --stream: {error}"))
}
_ => Ok(()),
}
}
fn scrub_lines(
mut input: impl BufRead,
output: &mut impl Write,
scrub: &dyn Fn(&str) -> ScrubResult,
) -> io::Result<()> {
let mut line_bytes = Vec::new();
let mut pem_block = String::new();
let mut pem_block_lines = 0;
while input.read_until(b'\n', &mut line_bytes)? > 0 {
let line = String::from_utf8_lossy(&line_bytes);
let is_inside_pem_block = pem_block_lines > 0;
if !is_inside_pem_block && !line.contains("-----BEGIN") {
write_scrubbed(output, &line, scrub)?;
} else {
pem_block.push_str(&line);
pem_block_lines += 1;
if line.contains("-----END") || pem_block_lines == MAX_PEM_BLOCK_LINES {
write_scrubbed(output, &pem_block, scrub)?;
pem_block.clear();
pem_block_lines = 0;
}
}
line_bytes.clear();
}
if pem_block_lines > 0 {
write_scrubbed(output, &pem_block, scrub)?;
}
Ok(())
}
fn write_scrubbed(
output: &mut impl Write,
text: &str,
scrub: &dyn Fn(&str) -> ScrubResult,
) -> io::Result<()> {
let body = text.trim_end_matches(['\r', '\n']);
let ending = &text[body.len()..];
let scrubbed = panic::catch_unwind(AssertUnwindSafe(|| scrub(body).text))
.unwrap_or_else(|_| WITHHELD.to_string());
output.write_all(scrubbed.as_bytes())?;
output.write_all(ending.as_bytes())?;
output.flush()
}
#[cfg(test)]
mod tests;