rustpython_common/encodings/
unicode_escape.rs1use super::*;
4use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf};
5
6pub const ENCODING_NAME: &str = "unicodeescape";
7
8fn push_hex(out: &mut Vec<u8>, prefix: u8, cp: u32, digits: usize) {
9 out.push(b'\\');
10 out.push(prefix);
11 for shift in (0..digits).rev() {
12 out.push(b"0123456789abcdef"[((cp >> (shift * 4)) & 0xf) as usize]);
13 }
14}
15
16#[must_use]
19pub fn encode_bytes(s: &Wtf8) -> Vec<u8> {
20 let mut out = Vec::new();
21 for cp in s.code_points() {
22 match cp.to_u32() {
23 0x5c => out.extend_from_slice(br"\\"),
24 0x09 => out.extend_from_slice(br"\t"),
25 0x0a => out.extend_from_slice(br"\n"),
26 0x0d => out.extend_from_slice(br"\r"),
27 0x20..=0x7e => out.push(cp.to_u32() as u8),
28 c @ 0x00..=0xff => push_hex(&mut out, b'x', c, 2),
29 c @ 0x100..=0xffff => push_hex(&mut out, b'u', c, 4),
30 c => push_hex(&mut out, b'U', c, 8),
31 }
32 }
33 out
34}
35
36pub fn encode<Ctx, E>(ctx: Ctx, _errors: &E) -> Result<Vec<u8>, Ctx::Error>
37where
38 Ctx: EncodeContext,
39 E: EncodeErrorHandler<Ctx>,
40{
41 Ok(encode_bytes(ctx.remaining_data()))
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct EscapeNote {
47 pub message: String,
48}
49
50fn invalid_escape_warning(prefix: &str, sequence: &str, octal: bool) -> String {
51 let kind = if octal {
52 "an invalid octal escape sequence"
53 } else {
54 "an invalid escape sequence"
55 };
56 format!("{prefix}\"\\{sequence}\" is {kind}. Such sequences will not work in the future. ")
57}
58
59pub fn decode<Ctx, E>(
61 mut ctx: Ctx,
62 errors: &E,
63 final_decode: bool,
64) -> Result<(Wtf8Buf, usize, Option<EscapeNote>), Ctx::Error>
65where
66 Ctx: DecodeContext,
67 E: DecodeErrorHandler<Ctx>,
68{
69 let mut out = Wtf8Buf::new();
70 let mut warning = None;
71 loop {
72 let rest = ctx.remaining_data();
73 if rest.is_empty() {
74 break;
75 }
76 let ch = rest[0];
77 if ch != b'\\' {
78 if let Some(cp) = CodePoint::from_u32(ch as u32) {
79 out.push(cp);
80 }
81 ctx.advance(1);
82 continue;
83 }
84 let escape_start = ctx.position();
85 if rest.len() == 1 {
86 if !final_decode {
87 break;
88 }
89 let end = ctx.full_data().len();
90 let replace =
91 ctx.handle_error(errors, escape_start..end, Some("\\ at end of string"))?;
92 out.push_wtf8(replace.as_ref());
93 continue;
94 }
95 let intro = rest[1];
96 match intro {
97 b'\n' => ctx.advance(2),
98 b'\\' => {
99 out.push_char('\\');
100 ctx.advance(2);
101 }
102 b'\'' => {
103 out.push_char('\'');
104 ctx.advance(2);
105 }
106 b'"' => {
107 out.push_char('"');
108 ctx.advance(2);
109 }
110 b'b' => {
111 out.push_char('\x08');
112 ctx.advance(2);
113 }
114 b'f' => {
115 out.push_char('\x0c');
116 ctx.advance(2);
117 }
118 b't' => {
119 out.push_char('\t');
120 ctx.advance(2);
121 }
122 b'n' => {
123 out.push_char('\n');
124 ctx.advance(2);
125 }
126 b'r' => {
127 out.push_char('\r');
128 ctx.advance(2);
129 }
130 b'v' => {
131 out.push_char('\x0b');
132 ctx.advance(2);
133 }
134 b'a' => {
135 out.push_char('\x07');
136 ctx.advance(2);
137 }
138 b'0'..=b'7' => {
139 ctx.advance(2);
140 let mut value = (intro - b'0') as u32;
141 let octal_start = escape_start + 1;
142 for _ in 0..2 {
143 let rest = ctx.remaining_data();
144 if rest.first().is_some_and(|b| matches!(b, b'0'..=b'7')) {
145 value = (value << 3) + (rest[0] - b'0') as u32;
146 ctx.advance(1);
147 }
148 }
149 if value > 0o377 && warning.is_none() {
150 let seq = &ctx.full_data()[octal_start..ctx.position()];
151 warning = Some(EscapeNote {
152 message: invalid_escape_warning("", &String::from_utf8_lossy(seq), true),
153 });
154 }
155 if let Some(cp) = CodePoint::from_u32(value) {
156 out.push(cp);
157 }
158 }
159 b'x' | b'u' | b'U' => {
160 let (digits, message) = match intro {
161 b'x' => (2usize, "truncated \\xXX escape"),
162 b'u' => (4usize, "truncated \\uXXXX escape"),
163 _ => (8usize, "truncated \\UXXXXXXXX escape"),
164 };
165 if !final_decode && rest.len() < 2 + digits {
166 break;
167 }
168 ctx.advance(2);
169 let rest = ctx.remaining_data();
170 if rest.len() >= digits && rest[..digits].iter().all(u8::is_ascii_hexdigit) {
171 let value =
172 u32::from_str_radix(core::str::from_utf8(&rest[..digits]).unwrap(), 16)
173 .unwrap();
174 if let Some(cp) = CodePoint::from_u32(value) {
175 out.push(cp);
176 ctx.advance(digits);
177 continue;
178 }
179 let start = escape_start;
180 let end = ctx.position() + digits;
181 let replace =
182 ctx.handle_error(errors, start..end, Some("illegal Unicode character"))?;
183 out.push_wtf8(replace.as_ref());
184 continue;
185 }
186 let mut hex_end = 0;
187 while hex_end < rest.len() && rest[hex_end].is_ascii_hexdigit() {
188 hex_end += 1;
189 }
190 let start = escape_start;
191 let end = ctx.position() + hex_end;
192 let replace = ctx.handle_error(errors, start..end, Some(message))?;
193 out.push_wtf8(replace.as_ref());
194 }
195 b'N' => {
196 if rest.len() == 2 && !final_decode {
197 break;
198 }
199 if rest.len() >= 3 && rest[2] == b'{' {
200 let name_start = 3;
201 let mut look = name_start;
202 while look < rest.len() && rest[look] != b'}' {
203 look += 1;
204 }
205 if look >= rest.len() {
206 if !final_decode {
207 break;
208 }
209 let start = escape_start;
210 let end = ctx.full_data().len();
211 let replace = ctx.handle_error(
212 errors,
213 start..end,
214 Some("malformed \\N character escape"),
215 )?;
216 out.push_wtf8(replace.as_ref());
217 continue;
218 }
219 if look == name_start {
220 let start = escape_start;
221 let end = ctx.position() + name_start;
222 let replace = ctx.handle_error(
223 errors,
224 start..end,
225 Some("malformed \\N character escape"),
226 )?;
227 out.push_wtf8(replace.as_ref());
228 continue;
229 }
230 let name = core::str::from_utf8(&rest[name_start..look]).ok();
231 match name.and_then(rustpython_unicode::lookup_character) {
232 Some(ch) => {
233 out.push_char(ch);
234 ctx.advance(look + 1);
235 continue;
236 }
237 None => {
238 let start = escape_start;
239 let end = ctx.position() + look + 1;
240 let replace = ctx.handle_error(
241 errors,
242 start..end,
243 Some("unknown Unicode character name"),
244 )?;
245 out.push_wtf8(replace.as_ref());
246 continue;
247 }
248 }
249 }
250 ctx.advance(2);
251 let start = escape_start;
252 let end = ctx.position();
253 let replace =
254 ctx.handle_error(errors, start..end, Some("malformed \\N character escape"))?;
255 out.push_wtf8(replace.as_ref());
256 }
257 other => {
258 if warning.is_none() {
259 warning = Some(EscapeNote {
260 message: invalid_escape_warning("", &char::from(other).to_string(), false),
261 });
262 }
263 out.push_char('\\');
264 if let Some(cp) = CodePoint::from_u32(other as u32) {
265 out.push(cp);
266 }
267 ctx.advance(2);
268 }
269 }
270 }
271 Ok((out, ctx.position(), warning))
272}