Skip to main content

rootle/
sanitize.rs

1//! Sanitization boundary: remote bytes are hostile terminal input.
2//! Everything the UI draws from network/disk passes through here (PLAN.md ยง9).
3
4/// Heuristic binary detection: NUL byte or >10% C0/C1 control chars
5/// (excluding \n, \t, \r) in the first 8 KiB.
6pub fn is_binary(bytes: &[u8]) -> bool {
7    const WINDOW: usize = 8 * 1024;
8    let sample = &bytes[..bytes.len().min(WINDOW)];
9    if sample.is_empty() {
10        return false;
11    }
12    if sample.contains(&0) {
13        return true;
14    }
15    let control = sample
16        .iter()
17        .filter(|&&b| (b < 0x20 && b != b'\n' && b != b'\t' && b != b'\r') || b == 0x7f)
18        .count();
19    control * 10 > sample.len()
20}
21
22/// Lossy UTF-8 decode + strip C0/C1 controls except \n and \t.
23/// Critically strips ESC (\x1b): a single escape byte in file content
24/// would inject terminal sequences and corrupt the whole screen.
25pub fn sanitize(bytes: &[u8]) -> String {
26    let text = String::from_utf8_lossy(bytes);
27    text.chars()
28        .filter(|&c| c == '\n' || c == '\t' || !is_control(c))
29        .collect()
30}
31
32/// Single-line variant for names (file/dir entries): drops \n and \t too.
33pub fn sanitize_inline(name: &str) -> String {
34    name.chars().filter(|&c| !is_control(c)).collect()
35}
36
37fn is_control(c: char) -> bool {
38    (c as u32) < 0x20 || (0x7f..=0x9f).contains(&(c as u32))
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn strips_escape_and_control_bytes() {
47        let raw = b"hello \x1b[2Jevil\x07 world\nkeep\ttabs";
48        assert_eq!(sanitize(raw), "hello [2Jevil world\nkeep\ttabs");
49    }
50
51    #[test]
52    fn invalid_utf8_becomes_replacement_char() {
53        let raw = b"ok \xff\xfe bytes";
54        let out = sanitize(raw);
55        assert!(out.contains('\u{FFFD}'));
56        assert!(out.starts_with("ok "));
57    }
58
59    #[test]
60    fn detects_binary_by_nul() {
61        assert!(is_binary(b"\x7fELF\x00\x01\x02"));
62        assert!(!is_binary(b"fn main() {}\n"));
63    }
64
65    #[test]
66    fn detects_binary_by_control_ratio() {
67        let mut raw = vec![0x01u8; 256];
68        raw.extend_from_slice(b"some text");
69        assert!(is_binary(&raw));
70    }
71
72    #[test]
73    fn inline_drops_newlines_and_tabs() {
74        assert_eq!(sanitize_inline("a\nb\tc\x1bd"), "abcd");
75    }
76}