Skip to main content

par_term/paste_transform/
sanitize.rs

1//! Paste content sanitization: strips dangerous terminal control characters.
2
3/// Check whether a paste string contains dangerous control characters that would
4/// be stripped by [`sanitize_paste_content`].
5///
6/// Returns `true` if the input contains any of:
7/// - C0 control characters (0x00-0x1F) other than Tab, Newline, Carriage Return
8/// - ESC (0x1B)
9/// - DEL (0x7F)
10/// - C1 control characters (0x80-0x9F) including CSI (0x9B)
11///
12/// Used to generate a warning log entry when `warn_paste_control_chars` is enabled.
13pub fn paste_contains_control_chars(input: &str) -> bool {
14    input.chars().any(|ch| {
15        let code = ch as u32;
16        if ch == '\t' || ch == '\n' || ch == '\r' {
17            return false;
18        }
19        if code <= 0x1F {
20            return true;
21        }
22        if code == 0x7F {
23            return true;
24        }
25        (0x80..=0x9F).contains(&code)
26    })
27}
28
29/// Sanitize clipboard paste content by stripping dangerous control characters.
30///
31/// Removes characters that could inject terminal escape sequences when pasted:
32/// - C0 control characters (0x00-0x1F) **except** Tab (0x09), Newline (0x0A),
33///   and Carriage Return (0x0D) which are safe/expected in paste content
34/// - ESC (0x1B) is explicitly stripped to prevent escape sequence injection
35/// - C1 control characters (0x80-0x9F) including CSI (0x9B)
36///
37/// All normal printable ASCII, extended Latin, and Unicode text passes through
38/// unchanged.
39pub fn sanitize_paste_content(input: &str) -> String {
40    input
41        .chars()
42        .filter(|&ch| {
43            let code = ch as u32;
44            // Allow safe C0 controls: Tab, Newline, Carriage Return
45            if ch == '\t' || ch == '\n' || ch == '\r' {
46                return true;
47            }
48            // Strip C0 control characters (0x00-0x1F) — includes ESC (0x1B)
49            if code <= 0x1F {
50                return false;
51            }
52            // Strip DEL (0x7F)
53            if code == 0x7F {
54                return false;
55            }
56            // Strip C1 control characters (0x80-0x9F) — includes CSI (0x9B)
57            if (0x80..=0x9F).contains(&code) {
58                return false;
59            }
60            true
61        })
62        .collect()
63}