1use anyhow::Result;
6use thiserror::Error;
7
8#[derive(Debug, Error)]
9pub enum EscapeError {
10 #[error("Incomplete escape sequence")]
11 IncompleteEscape,
12 #[error("Invalid character in escape sequence")]
13 InvalidCharacter(char),
14 #[error("Invalid character in hex escape sequence")]
15 InvalidHexCharacter(char),
16}
17
18const HEX: &[u8; 16] = b"0123456789ABCDEF";
19
20pub fn escape(val: &[u8]) -> String {
21 let mut data = String::new();
22 for ch in val {
23 match ch {
24 b'\t' => data.push_str("\\t"),
25 b'\r' => data.push_str("\\r"),
26 b'\n' => data.push_str("\\n"),
27 b'\'' => data.push_str("\\'"),
28 b'\"' => data.push_str("\\\""),
29 b'\\' => data.push_str("\\\\"),
30 0x20..=0x7e => data.push(*ch as char),
31 _ => {
32 data.push_str("\\x");
33 data.push(HEX[(ch >> 4) as usize] as char);
34 data.push(HEX[(ch & 15) as usize] as char);
35 }
36 }
37 }
38 data
39}
40
41pub fn unhex(ch: char) -> Result<u8> {
42 match ch {
43 '0'..='9' => Ok(ch as u8 - b'0'),
44 'A'..='F' => Ok(ch as u8 - b'A' + 10),
45 'a'..='f' => Ok(ch as u8 - b'a' + 10),
46 _ => Err(EscapeError::InvalidHexCharacter(ch).into()),
47 }
48}
49
50pub fn unescape(val: &str) -> Result<Vec<u8>> {
51 let mut data = Vec::new();
52 let mut it = val.chars();
53 while let Some(ch) = it.next() {
54 if !ch.is_ascii() {
55 return Err(EscapeError::InvalidCharacter(ch).into());
56 }
57 if ch == '\\' {
58 let ch = it.next().ok_or(EscapeError::IncompleteEscape)?;
59 let decoded = match ch {
60 't' => b'\t',
61 'r' => b'\r',
62 'n' => b'\n',
63 '"' => b'\"',
64 '\'' => b'\'',
65 '\\' => b'\\',
66 'x' => {
67 let mut v = 0;
68 v = (v << 4) | unhex(it.next().ok_or(EscapeError::IncompleteEscape)?)?;
69 v = (v << 4) | unhex(it.next().ok_or(EscapeError::IncompleteEscape)?)?;
70 v
71 }
72 _ => return Err(EscapeError::InvalidCharacter(ch).into()),
73 };
74 data.push(decoded);
75 } else {
76 data.push(ch as u8);
77 }
78 }
79 Ok(data)
80}
81
82pub fn as_hex(v: &[u8]) -> String {
83 let mut s = String::with_capacity(v.len() * 3);
84 for (i, &byte) in v.iter().enumerate() {
85 if i > 0 {
86 s.push(':');
87 }
88 s.push(HEX[(byte >> 4) as usize] as char);
89 s.push(HEX[(byte & 15) as usize] as char);
90 }
91 s
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_escape() -> Result<()> {
100 let bytes = b"The quick brown fox\n\tjumped over the \"lazy\" dog.";
101 assert_eq!(
102 escape(bytes),
103 r#"The quick brown fox\n\tjumped over the \"lazy\" dog."#
104 );
105
106 let bytes = b"\t\r\n\"\'\\\x00\x7F\xFF";
107 assert_eq!(escape(bytes), r#"\t\r\n\"\'\\\x00\x7F\xFF"#);
108 Ok(())
109 }
110
111 #[test]
112 fn test_unescape() -> Result<()> {
113 let s = r#"The quick brown fox\n\tjumped over the \"lazy\" dog."#;
114 assert_eq!(
115 unescape(s)?,
116 b"The quick brown fox\n\tjumped over the \"lazy\" dog."
117 );
118
119 let s = r#"\t\r\n\"\'\\\x00\x7F\xFF"#;
120 assert_eq!(unescape(s)?, b"\t\r\n\"\'\\\x00\x7F\xFF");
121 Ok(())
122 }
123
124 #[test]
125 fn test_unescape_err() -> Result<()> {
126 let pounds = "\u{00A3}";
127 assert!(unescape(pounds).is_err());
128
129 let invalid_escape = "\\z";
130 assert!(unescape(invalid_escape).is_err());
131
132 let incomplete_escape = "\\";
133 assert!(unescape(incomplete_escape).is_err());
134
135 let bad_hex = "\\xzz";
136 assert!(unescape(bad_hex).is_err());
137 Ok(())
138 }
139}