pub fn sanitize(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\u{1b}' {
match chars.peek() {
Some('[') => {
chars.next();
while let Some(&n) = chars.peek() {
chars.next();
if ('\u{40}'..='\u{7e}').contains(&n) {
break;
}
}
}
Some(']') => {
chars.next();
while let Some(n) = chars.next() {
if n == '\u{07}' {
break;
}
if n == '\u{1b}' {
if chars.peek() == Some(&'\\') {
chars.next();
}
break;
}
}
}
Some(_) => {
chars.next();
}
None => {}
}
} else if !is_directional_control(c) && (!c.is_control() || c == '\n' || c == '\t') {
out.push(c);
}
}
out
}
pub fn sanitize_single_line(s: &str) -> String {
let cleaned = sanitize(s);
let mut out = String::with_capacity(cleaned.len());
for c in cleaned.chars() {
match c {
'\n' => out.push_str("\\n"),
'\t' => out.push_str("\\t"),
'\u{2028}' => out.push_str("\\u{2028}"),
'\u{2029}' => out.push_str("\\u{2029}"),
_ => out.push(c),
}
}
out
}
fn is_directional_control(c: char) -> bool {
matches!(
c,
'\u{061c}'
| '\u{200e}'
| '\u{200f}'
| '\u{202a}'..='\u{202e}'
| '\u{2066}'..='\u{2069}'
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strips_ansi_sequences_whole_and_control_bytes() {
assert_eq!(sanitize("\u{1b}[31mred\u{1b}[0m ok\u{7}"), "red ok");
assert_eq!(sanitize("\u{1b}]0;title\u{7}after"), "after");
assert_eq!(sanitize("\u{1b}]0;t\u{1b}\\after"), "after");
assert_eq!(sanitize("\u{1b}cwiped"), "wiped");
assert_eq!(sanitize("a\u{0}b\u{7f}c\u{9b}d"), "abcd");
assert_eq!(sanitize("tail\u{1b}"), "tail");
}
#[test]
fn keeps_newlines_tabs_and_printable_unicode() {
assert_eq!(sanitize("a\nb\tc"), "a\nb\tc");
assert_eq!(sanitize("café → ok"), "café → ok");
assert_eq!(sanitize(""), "");
}
#[test]
fn single_line_neutralizes_line_column_and_bidi_spoofing() {
assert_eq!(
sanitize_single_line("file\nfake error\tkey\u{202e}txt\u{2028}tail"),
"file\\nfake error\\tkeytxt\\u{2028}tail"
);
}
}