1use super::words::{self, Text};
5use crate::{
6 class,
7 error::{ErrorKind, ParseError, Result, err},
8 limits::Limits,
9 scan::{control_kind, is_control},
10 text::{comment_end_folded, parse_quoted},
11};
12
13fn is_word_byte(b: u8) -> bool {
16 WORD.get(usize::from(b)).copied().unwrap_or(false)
17}
18
19#[allow(clippy::indexing_slicing)]
22const WORD: [bool; 256] = {
23 let mut t = [false; 256];
24 let mut b = 0usize;
25 while b < 256 {
26 t[b] = class::is_atext(b as u8) || b == b'.' as usize || b >= 0x80;
27 b += 1;
28 }
29 t
30};
31
32#[derive(Clone, Copy, PartialEq, Eq)]
34enum Last {
35 None,
36 Text,
37 Encoded,
38}
39
40fn walk(v: &[u8], limits: &Limits, emit: &mut impl FnMut(&[u8])) -> Result<()> {
42 let mut text = Text::new();
43 let (mut i, mut words) = (0usize, 0usize);
44 let mut last = Last::None;
45 let mut commented = false;
48 while let Some(&b) = v.get(i) {
49 let n = words::ws_len(v, i);
50 if n > 0 {
51 i += n;
52 continue;
53 }
54 if b == b'(' {
55 i = comment_end_folded(v, i, limits)?.0;
56 commented = true;
57 continue;
58 }
59 if b == b'"' {
60 let (q, used) = parse_quoted(v.get(i..).unwrap_or(&[])).map_err(|e| e.shifted(i))?;
61 if last != Last::None {
62 emit(b" ");
63 }
64 let raw = q.raw();
67 if raw.is_ascii() {
68 let mut rest = raw;
71 while let Some(p) = rest.iter().position(|&c| c == b'\\') {
72 emit(rest.get(..p).unwrap_or(&[]));
73 emit(rest.get(p + 1..p + 2).unwrap_or(&[]));
74 rest = rest.get(p + 2..).unwrap_or(&[]);
75 }
76 emit(rest);
77 } else {
78 let mut k = 0usize;
79 while let Some(&c) = raw.get(k) {
80 if c == b'\\' {
81 k += 1;
82 }
83 let c = raw.get(k).copied().unwrap_or(c);
84 text.check(c, i + 1 + k)?;
85 emit(&[c]);
86 k += 1;
87 }
88 if !text.is_complete() {
89 return err(ErrorKind::InvalidUtf8, i + used - 1);
90 }
91 }
92 last = Last::Text;
93 commented = false;
94 i += used;
95 continue;
96 }
97 if !is_word_byte(b) {
98 let kind = if is_control(b) {
99 control_kind(b)
100 } else {
101 ErrorKind::InvalidByte
102 };
103 return Err(ParseError::new(kind, i));
104 }
105 let rest = v.get(i..).unwrap_or(&[]);
106 let end = i + rest
107 .iter()
108 .position(|&c| !is_word_byte(c))
109 .unwrap_or(rest.len());
110 let alone = || {
116 let before = i == 0 || matches!(v.get(i - 1), Some(b' ' | b'\t'));
117 before && (end == v.len() || words::ws_len(v, end) > 0)
118 };
119 let encoded = if rest.starts_with(b"=?") && alone() {
120 words::shape(v.get(..end).unwrap_or(&[]), i)?
121 } else {
122 None
123 };
124 if let Some(e) = encoded {
125 words += 1;
126 if words > usize::from(limits.encoded_words) {
127 return err(ErrorKind::TooManyMembers, i);
128 }
129 if last == Last::Text || (last == Last::Encoded && commented) {
130 emit(b" ");
131 }
132 words::decode_word(&e, i, emit)?;
133 last = Last::Encoded;
134 } else {
135 if last != Last::None {
136 emit(b" ");
137 }
138 let atom = v.get(i..end).unwrap_or(&[]);
139 if !atom.is_ascii() {
141 for (k, &c) in atom.iter().enumerate() {
142 text.check(c, i + k)?;
143 }
144 if !text.is_complete() {
145 return err(ErrorKind::InvalidUtf8, end);
146 }
147 }
148 emit(atom);
149 last = Last::Text;
150 }
151 commented = false;
152 i = end;
153 }
154 if last == Last::None {
155 return err(ErrorKind::Empty, 0);
156 }
157 Ok(())
158}
159
160pub fn decode_phrase<'b>(value: &[u8], limits: &Limits, out: &'b mut [u8]) -> Result<&'b str> {
205 let mut len = 0usize;
206 walk(value, limits, &mut |b: &[u8]| len += b.len())?;
207 let Some(dst) = out.get_mut(..len) else {
208 return err(ErrorKind::BufferTooSmall, 0);
209 };
210 let mut n = 0usize;
211 walk(value, limits, &mut |b: &[u8]| {
212 if let Some(d) = dst.get_mut(n..n + b.len()) {
213 d.copy_from_slice(b);
214 }
215 n += b.len();
216 })?;
217 core::str::from_utf8(dst).map_err(|e| ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use std::{string::String, vec, vec::Vec};
225
226 fn dec(v: &[u8]) -> Result<String> {
227 let mut buf = vec![0u8; 1024];
228 decode_phrase(v, &Limits::DEFAULT, &mut buf).map(String::from)
229 }
230
231 fn fails(v: &[u8]) -> (ErrorKind, usize) {
232 let e = dec(v).unwrap_err();
233 (e.kind(), e.offset())
234 }
235
236 #[test]
237 fn test_decode_phrase_words() {
238 assert_eq!(dec(b"Jane Doe").unwrap(), "Jane Doe");
239 assert_eq!(dec(b" Jane \t Doe ").unwrap(), "Jane Doe");
240 assert_eq!(dec(b"Jane\r\n Doe").unwrap(), "Jane Doe");
241 assert_eq!(dec(b"John Q. Public").unwrap(), "John Q. Public");
242 assert_eq!(dec(b"\"Doe, John\"").unwrap(), "Doe, John");
243 assert_eq!(dec(b"\"a \\\"q\\\" \\\\ b\"").unwrap(), "a \"q\" \\ b");
244 assert_eq!(dec(b"Jane\"Q\"Doe").unwrap(), "Jane Q Doe");
245 assert_eq!(dec(b"\" spaced \" x").unwrap(), " spaced x");
246 assert_eq!(dec(b"\"\"").unwrap(), "");
247 assert_eq!(dec(b"John (the (nested) one) Doe").unwrap(), "John Doe");
248 assert_eq!(dec(b"(lead)John(trail)").unwrap(), "John");
249 assert_eq!(
250 dec("J\u{f6}rg M\u{fc}ller".as_bytes()).unwrap(),
251 "J\u{f6}rg M\u{fc}ller"
252 );
253 assert_eq!(
254 dec("\"\u{e9}t\u{e9}\"".as_bytes()).unwrap(),
255 "\u{e9}t\u{e9}"
256 );
257 assert_eq!(dec(b"\"tab\there\"").unwrap(), "tab\there");
258 }
259
260 #[test]
261 fn test_decode_phrase_encoded_words() {
262 assert_eq!(
263 dec(b"=?UTF-8?Q?Caf=C3=A9?= Owner").unwrap(),
264 "Caf\u{e9} Owner"
265 );
266 assert_eq!(
267 dec(b"Owner =?utf-8?q?Caf=C3=A9?=").unwrap(),
268 "Owner Caf\u{e9}"
269 );
270 assert_eq!(dec(b"=?UTF-8?Q?a?= =?UTF-8?Q?b?=").unwrap(), "ab");
271 assert_eq!(dec(b"=?UTF-8?Q?a?=\r\n =?UTF-8?Q?b?=").unwrap(), "ab");
272 assert_eq!(dec(b"=?UTF-8?Q?a?= (c) =?UTF-8?Q?b?=").unwrap(), "a b");
273 assert_eq!(dec(b"=?UTF-8?Q?a_b?=").unwrap(), "a b");
274 assert_eq!(
275 dec(b"=?ISO-8859-1?Q?Andr=E9?= Pirard").unwrap(),
276 "Andr\u{e9} Pirard"
277 );
278 assert_eq!(
279 dec(b"=?UTF-8?B?5Lya6K2w44Gu44GK55+l44KJ44Gb?=").unwrap(),
280 "\u{4f1a}\u{8b70}\u{306e}\u{304a}\u{77e5}\u{3089}\u{305b}"
281 );
282 assert_eq!(dec(b"\"=?UTF-8?Q?a?=\"").unwrap(), "=?UTF-8?Q?a?=");
284 assert_eq!(dec(b"=?UTF-8?Q?a?=(c)").unwrap(), "=?UTF-8?Q?a?=");
285 assert_eq!(dec(b"=?UTF-8?Q?a?=\"b\"").unwrap(), "=?UTF-8?Q?a?= b");
286 assert_eq!(dec(b"(c)=?UTF-8?Q?a?=").unwrap(), "=?UTF-8?Q?a?=");
288 assert_eq!(dec(b"\"x\"=?UTF-8?Q?a?=").unwrap(), "x =?UTF-8?Q?a?=");
289 assert_eq!(dec(b"(c) =?UTF-8?Q?a?=").unwrap(), "a");
290 assert_eq!(dec(b"\"x\" =?UTF-8?Q?a?=").unwrap(), "x a");
291 assert_eq!(
292 dec(b"=?UTF-8?Q?a?= (c)=?UTF-8?Q?b?=").unwrap(),
293 "a =?UTF-8?Q?b?="
294 );
295 assert_eq!(dec(b"=?UTF-8?B??=").unwrap(), "");
297 assert_eq!(dec(b"x =?UTF-8?Q??= y").unwrap(), "x y");
298 assert_eq!(dec(b"x \"\" y").unwrap(), "x y");
299 assert_eq!(dec(b"=?x").unwrap(), "=?x");
301 assert_eq!(dec(b"a=?UTF-8?Q?b?=").unwrap(), "a=?UTF-8?Q?b?=");
302 }
303
304 #[test]
305 fn test_decode_phrase_errors() {
306 assert_eq!(fails(b""), (ErrorKind::Empty, 0));
307 assert_eq!(fails(b" "), (ErrorKind::Empty, 0));
308 assert_eq!(fails(b"(only a comment)"), (ErrorKind::Empty, 0));
309 assert_eq!(fails(b"Doe, John"), (ErrorKind::InvalidByte, 3));
310 assert_eq!(fails(b"a <b>"), (ErrorKind::InvalidByte, 2));
311 assert_eq!(fails(b"a@b"), (ErrorKind::InvalidByte, 1));
312 assert_eq!(fails(b"a\x01b").0, ErrorKind::Control);
313 assert_eq!(fails(b"a\rb").0, ErrorKind::BareCr);
314 assert_eq!(fails(b"a\nb").0, ErrorKind::LineFeed);
315 assert_eq!(fails(b"a\0b").0, ErrorKind::Nul);
316 assert_eq!(fails(b"\"open"), (ErrorKind::UnterminatedQuote, 5));
317 assert_eq!(fails(b"a (open").0, ErrorKind::UnbalancedComment);
318 assert_eq!(fails(b"a \xff"), (ErrorKind::InvalidUtf8, 2));
319 assert_eq!(fails(b"a \xc3 b"), (ErrorKind::InvalidUtf8, 3));
320 assert_eq!(fails(b"\"\xc3\" b"), (ErrorKind::InvalidUtf8, 2));
321 assert_eq!(fails(b"a\xc2\x85b"), (ErrorKind::Control, 2));
322 assert_eq!(fails(b"\"\\\xc2\\\x85\"").0, ErrorKind::Control);
323 assert_eq!(fails(b"=?KOI8-R?Q?a?=").0, ErrorKind::UnsupportedCharset);
324 assert_eq!(fails(b"x =?UTF-8?Q?=0D?=").0, ErrorKind::ControlInDecoded);
325 assert_eq!(fails(b"=?UTF-8?B?YQ=?=").0, ErrorKind::InvalidEncodedWord);
326 let l = Limits::DEFAULT.max_encoded_words(1);
327 let mut buf = [0u8; 16];
328 assert_eq!(
329 decode_phrase(b"=?UTF-8?Q?a?= =?UTF-8?Q?b?=", &l, &mut buf)
330 .unwrap_err()
331 .kind(),
332 ErrorKind::TooManyMembers
333 );
334 let mut small = [b'#'; 4];
336 assert_eq!(
337 decode_phrase(b"Jane Doe", &Limits::DEFAULT, &mut small)
338 .unwrap_err()
339 .kind(),
340 ErrorKind::BufferTooSmall
341 );
342 assert_eq!(small, [b'#'; 4]);
343 }
344
345 fn reference(words: &[(bool, String)]) -> String {
349 let mut out = String::new();
350 for (k, (_, w)) in words.iter().enumerate() {
351 if k > 0 {
352 out.push(' ');
353 }
354 out.push_str(w);
355 }
356 out
357 }
358
359 proptest::proptest! {
360 #[test]
361 fn decode_phrase_matches_reference(
362 words in proptest::collection::vec(
363 (proptest::bool::ANY, "[ -~\u{e9}\u{4f1a}]{0,8}", "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.\u{e9}-]{1,8}"),
364 1..6,
365 ),
366 gaps in proptest::collection::vec(0usize..4, 6),
367 ) {
368 let mut raw = Vec::new();
369 let mut expect = Vec::new();
370 for (k, (quoted, q, atom)) in words.iter().enumerate() {
371 if k > 0 {
372 match gaps.get(k).copied().unwrap_or(0) {
373 0 => raw.push(b' '),
374 1 => raw.extend_from_slice(b"\t "),
375 2 => raw.extend_from_slice(b" (note) "),
376 _ => raw.extend_from_slice(b"\r\n "),
377 }
378 }
379 if *quoted {
380 raw.push(b'"');
381 for c in q.bytes() {
382 if c == b'"' || c == b'\\' {
383 raw.push(b'\\');
384 }
385 raw.push(c);
386 }
387 raw.push(b'"');
388 expect.push((true, q.clone()));
389 } else {
390 raw.extend_from_slice(atom.as_bytes());
391 expect.push((false, atom.clone()));
392 }
393 }
394 proptest::prop_assume!(!expect.iter().any(|(q, w)| !q && w.starts_with("=?")));
397 proptest::prop_assert_eq!(dec(&raw).unwrap(), reference(&expect));
398 }
399
400 #[test]
401 fn decode_phrase_never_panics(v in proptest::collection::vec(proptest::num::u8::ANY, 0..64)) {
402 let mut buf = [0u8; 256];
403 if let Ok(s) = decode_phrase(&v, &Limits::DEFAULT, &mut buf) {
404 proptest::prop_assert!(!s.chars().any(|c| c.is_control() && c != '\t'));
405 }
406 }
407 }
408}