Skip to main content

cdk_common/
terminal.rs

1//! Terminal-safe rendering of potentially untrusted strings.
2
3use std::fmt::Write as _;
4
5/// Escapes control and bidirectional formatting characters in `input`.
6///
7/// The returned string is safe to include in terminal output and single-line
8/// logs. Printable characters are preserved, while C0/C1 controls, DEL, and
9/// Unicode bidirectional formatting characters are rendered visibly.
10pub fn escape_control(input: &str) -> String {
11    let mut escaped = String::with_capacity(input.len());
12    for ch in input.chars() {
13        match ch {
14            '\0' => escaped.push_str("\\0"),
15            '\u{07}' => escaped.push_str("\\a"),
16            '\u{08}' => escaped.push_str("\\b"),
17            '\t' => escaped.push_str("\\t"),
18            '\n' => escaped.push_str("\\n"),
19            '\u{0B}' => escaped.push_str("\\v"),
20            '\u{0C}' => escaped.push_str("\\f"),
21            '\r' => escaped.push_str("\\r"),
22            '\u{1B}' => escaped.push_str("\\e"),
23            ch if ch.is_control() || is_bidi_control_character(ch) => {
24                write!(escaped, "\\u{{{:02x}}}", ch as u32)
25                    .expect("writing to a String cannot fail");
26            }
27            ch => escaped.push(ch),
28        }
29    }
30    escaped
31}
32
33/// Returns whether `ch` has Unicode's `Bidi_Control` property.
34pub const fn is_bidi_control_character(ch: char) -> bool {
35    matches!(
36        ch,
37        '\u{061c}'
38            | '\u{200e}'
39            | '\u{200f}'
40            | '\u{202a}'..='\u{202e}'
41            | '\u{2066}'..='\u{2069}'
42    )
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn printable_text_passes_through() {
51        assert_eq!(escape_control("lnbc2500u1pjexample"), "lnbc2500u1pjexample");
52    }
53
54    #[test]
55    fn printable_unicode_is_preserved() {
56        assert_eq!(escape_control("₿ ünïcodé 🚀"), "₿ ünïcodé 🚀");
57    }
58
59    #[test]
60    fn escapes_cr_lf_and_tabs() {
61        assert_eq!(escape_control("a\nb\rc\td"), "a\\nb\\rc\\td");
62    }
63
64    #[test]
65    fn escapes_esc_and_csi_sequence() {
66        assert_eq!(escape_control("\u{1b}[2Jevil"), "\\e[2Jevil");
67    }
68
69    #[test]
70    fn escapes_osc_sequence_with_bel_terminator() {
71        assert_eq!(escape_control("\u{1b}]0;pwned\u{07}"), "\\e]0;pwned\\a");
72    }
73
74    #[test]
75    fn escapes_c1_controls() {
76        assert_eq!(escape_control("\u{85}\u{9b}"), "\\u{85}\\u{9b}");
77    }
78
79    #[test]
80    fn escapes_del_and_null() {
81        assert_eq!(escape_control("\u{7f}\0"), "\\u{7f}\\0");
82    }
83
84    #[test]
85    fn escapes_unicode_bidi_controls() {
86        let input = "\u{061c}\u{200e}\u{200f}\u{202a}\u{202e}\u{2066}\u{2069}";
87        let output = escape_control(input);
88
89        assert_eq!(
90            output,
91            "\\u{61c}\\u{200e}\\u{200f}\\u{202a}\\u{202e}\\u{2066}\\u{2069}"
92        );
93        assert!(!output.chars().any(is_bidi_control_character));
94    }
95}