1use std::fmt::Write as _;
4
5pub 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
33pub 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}