1use std::borrow::Cow;
4
5pub fn escape_byte_string<B>(bytes: B) -> String
7where
8 B: AsRef<[u8]>,
9{
10 let bytes = bytes.as_ref();
11
12 bytes
13 .iter()
14 .map(|byte| match byte {
15 0x00..=0x08 => format!("\\x{:02x}", byte),
16 0x09 => String::from("\\t"),
17 0x0A => String::from("\\n"),
18 0x0B => format!("\\x{:02x}", byte),
19 0x0C => format!("\\x{:02x}", byte),
20 0x0D => String::from("\\r"),
21 0x0e..=0x1f => format!("\\x{:02x}", byte),
22 0x20..=0x21 => format!("{}", *byte as char),
23 0x22 => String::from("\\\""),
24 0x23..=0x5B => format!("{}", *byte as char),
25 0x5C => String::from("\\\\"),
26 0x5D..=0x7E => format!("{}", *byte as char),
27 0x7f => format!("\\x{:02x}", byte),
28 0x80..=0xff => format!("\\x{:02x}", byte),
29 })
30 .collect::<Vec<String>>()
31 .join("")
32}
33
34pub mod indicators {
35 pub fn is_char(byte: u8) -> bool {
39 matches!(byte, 0x01..=0x7f)
40 }
41
42 pub fn is_ctl(byte: u8) -> bool {
46 matches!(byte, 0x00..=0x1f | 0x7f)
47 }
48
49 pub(crate) fn is_any_text_char_except_quoted_specials(byte: u8) -> bool {
50 is_text_char(byte) && !is_quoted_specials(byte)
51 }
52
53 pub fn is_quoted_specials(byte: u8) -> bool {
55 byte == b'"' || byte == b'\\'
56 }
57
58 pub fn is_astring_char(i: u8) -> bool {
60 is_atom_char(i) || is_resp_specials(i)
61 }
62
63 pub fn is_atom_char(b: u8) -> bool {
65 is_char(b) && !is_atom_specials(b)
66 }
67
68 pub fn is_atom_specials(i: u8) -> bool {
70 match i {
71 b'(' | b')' | b'{' | b' ' => true,
72 c if is_ctl(c) => true,
73 c if is_list_wildcards(c) => true,
74 c if is_quoted_specials(c) => true,
75 c if is_resp_specials(c) => true,
76 _ => false,
77 }
78 }
79
80 pub fn is_list_wildcards(i: u8) -> bool {
82 i == b'%' || i == b'*'
83 }
84
85 #[inline]
86 pub fn is_resp_specials(i: u8) -> bool {
88 i == b']'
89 }
90
91 #[inline]
92 pub fn is_char8(i: u8) -> bool {
96 i != 0
97 }
98
99 pub fn is_text_char(c: u8) -> bool {
103 matches!(c, 0x01..=0x09 | 0x0b..=0x0c | 0x0e..=0x7f)
104 }
105
106 pub fn is_list_char(i: u8) -> bool {
108 is_atom_char(i) || is_list_wildcards(i) || is_resp_specials(i)
109 }
110}
111
112pub fn escape_quoted(unescaped: &str) -> Cow<str> {
113 let mut escaped = Cow::Borrowed(unescaped);
114
115 if escaped.contains('\\') {
116 escaped = Cow::Owned(escaped.replace('\\', "\\\\"));
117 }
118
119 if escaped.contains('\"') {
120 escaped = Cow::Owned(escaped.replace('"', "\\\""));
121 }
122
123 escaped
124}
125
126pub fn unescape_quoted(escaped: &str) -> Cow<str> {
127 let mut unescaped = Cow::Borrowed(escaped);
128
129 if unescaped.contains("\\\\") {
130 unescaped = Cow::Owned(unescaped.replace("\\\\", "\\"));
131 }
132
133 if unescaped.contains("\\\"") {
134 unescaped = Cow::Owned(unescaped.replace("\\\"", "\""));
135 }
136
137 unescaped
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_escape_quoted() {
146 let tests = [
147 ("", ""),
148 ("\\", "\\\\"),
149 ("\"", "\\\""),
150 ("alice", "alice"),
151 ("\\alice\\", "\\\\alice\\\\"),
152 ("alice\"", "alice\\\""),
153 (r#"\alice\ ""#, r#"\\alice\\ \""#),
154 ];
155
156 for (test, expected) in tests {
157 let got = escape_quoted(test);
158 assert_eq!(expected, got);
159 }
160 }
161
162 #[test]
163 fn test_unescape_quoted() {
164 let tests = [
165 ("", ""),
166 ("\\\\", "\\"),
167 ("\\\"", "\""),
168 ("alice", "alice"),
169 ("\\\\alice\\\\", "\\alice\\"),
170 ("alice\\\"", "alice\""),
171 (r#"\\alice\\ \""#, r#"\alice\ ""#),
172 ];
173
174 for (test, expected) in tests {
175 let got = unescape_quoted(test);
176 assert_eq!(expected, got);
177 }
178 }
179
180 #[test]
181 fn test_that_unescape_is_inverse_of_escape() {
182 let input = "\\\"\\¹²³abc_*:;059^$%§!\"";
183
184 assert_eq!(input, unescape_quoted(escape_quoted(input).as_ref()));
185 }
186
187 #[test]
188 fn test_escape_byte_string() {
189 for byte in 0u8..=255 {
190 let got = escape_byte_string([byte]);
191
192 if byte.is_ascii_alphanumeric() {
193 assert_eq!((byte as char).to_string(), got.to_string());
194 } else if byte.is_ascii_whitespace() {
195 if byte == b'\t' {
196 assert_eq!(String::from("\\t"), got);
197 } else if byte == b'\n' {
198 assert_eq!(String::from("\\n"), got);
199 }
200 } else if byte.is_ascii_punctuation() {
201 if byte == b'\\' {
202 assert_eq!(String::from("\\\\"), got);
203 } else if byte == b'"' {
204 assert_eq!(String::from("\\\""), got);
205 } else {
206 assert_eq!((byte as char).to_string(), got);
207 }
208 } else {
209 assert_eq!(format!("\\x{:02x}", byte), got);
210 }
211 }
212
213 let tests = [(b"Hallo \"\\\x00", String::from(r#"Hallo \"\\\x00"#))];
214
215 for (test, expected) in tests {
216 let got = escape_byte_string(test);
217 assert_eq!(expected, got);
218 }
219 }
220}