1use crate::{
4 class,
5 error::{ErrorKind, ParseError, Result, WriteError, err},
6 limits::Limits,
7 utf8::Utf8,
8 write::{CountSink, Sink, reserve},
9};
10
11const WORD_LIMIT: usize = 75;
13const OVERHEAD: usize = 12;
15
16const fn q_plain(b: u8) -> bool {
19 b.is_ascii_alphanumeric() || matches!(b, b'!' | b'*' | b'+' | b'-' | b'/')
20}
21
22#[allow(clippy::indexing_slicing)]
26const Q_LEN: [u8; 256] = {
27 let mut t = [3u8; 256];
28 let mut b = 0usize;
29 while b < 256 {
30 if q_plain(b as u8) || b == b' ' as usize {
31 t[b] = 1;
32 }
33 b += 1;
34 }
35 t
36};
37
38#[inline]
40fn q_byte_len(b: u8) -> usize {
41 usize::from(Q_LEN.get(usize::from(b)).copied().unwrap_or(3))
42}
43
44const fn utf8_width(b: u8) -> usize {
46 match b {
47 0x00..=0x7f => 1,
48 0xc0..=0xdf => 2,
49 0xe0..=0xef => 3,
50 _ => 4,
51 }
52}
53
54struct Payload {
57 buf: [u8; WORD_LIMIT - OVERHEAD],
58 len: usize,
59}
60
61impl Payload {
62 fn push(&mut self, ch: &[u8]) {
65 const HEX: &[u8; 16] = b"0123456789ABCDEF";
66 for &b in ch {
67 let len = self.len;
68 if q_byte_len(b) == 1 {
69 if let Some(d) = self.buf.get_mut(len) {
70 *d = if b == b' ' { b'_' } else { b };
71 }
72 self.len = len + 1;
73 } else {
74 let hi = HEX.get(usize::from(b >> 4)).copied().unwrap_or(b'0');
75 let lo = HEX.get(usize::from(b & 0xf)).copied().unwrap_or(b'0');
76 if let Some([x, y, z]) = self.buf.get_mut(len..len + 3) {
77 (*x, *y, *z) = (b'=', hi, lo);
78 }
79 self.len = len + 3;
80 }
81 }
82 }
83
84 fn as_bytes(&self) -> &[u8] {
85 self.buf.get(..self.len).unwrap_or(&[])
86 }
87}
88
89fn put_words(
90 text: &str,
91 room_first_line: usize,
92 out: &mut impl Sink,
93) -> core::result::Result<(), WriteError> {
94 let bytes = text.as_bytes();
95 if bytes.iter().all(|b| (0x20..=0x7e).contains(b)) && !text.contains("=?") {
96 out.put(bytes)?;
97 return Ok(());
98 }
99 let mut limit = room_first_line.min(WORD_LIMIT);
100 let mut word = Payload {
101 buf: [0; WORD_LIMIT - OVERHEAD],
102 len: 0,
103 };
104 let mut open = false;
105 let mut i = 0usize;
106 while let Some(&lead) = bytes.get(i) {
107 let ch = bytes.get(i..i + utf8_width(lead)).unwrap_or(&[]);
108 i += ch.len().max(1);
109 let mut n = 0usize;
110 for &b in ch {
111 n += q_byte_len(b);
112 }
113 if open && OVERHEAD + word.len + n > limit {
114 out.put(word.as_bytes())?;
115 out.put(b"?=\r\n ")?;
116 open = false;
117 limit = WORD_LIMIT;
118 }
119 if !open {
120 if OVERHEAD + n > limit {
121 out.put(b"\r\n ")?;
124 limit = WORD_LIMIT;
125 }
126 out.put(b"=?UTF-8?Q?")?;
127 open = true;
128 word.len = 0;
129 }
130 word.push(ch);
131 }
132 if open {
133 out.put(word.as_bytes())?;
134 out.put(b"?=")?;
135 }
136 Ok(())
137}
138
139pub fn encode_words(
162 text: &str,
163 room_first_line: usize,
164 out: &mut impl Sink,
165) -> core::result::Result<(), WriteError> {
166 if let Some((at, _)) = text.char_indices().find(|&(_, c)| c.is_control()) {
167 return Err(WriteError::forbidden(at));
168 }
169 let mut count = CountSink::new();
170 put_words(text, room_first_line, &mut count)?;
171 reserve(out, count.count())?;
172 put_words(text, room_first_line, out)
173}
174
175#[derive(Clone, Copy, PartialEq, Eq)]
177enum Charset {
178 Utf8,
179 Ascii,
180 Latin1,
181}
182
183struct Encoded<'a> {
185 charset: Charset,
186 base64: bool,
188 text: &'a [u8],
189 end: usize,
191}
192
193fn shape(v: &[u8], i: usize) -> Result<Option<Encoded<'_>>> {
196 let rest = v.get(i + 2..).unwrap_or(&[]);
197 let Some(q1) = rest.iter().position(|&b| b == b'?') else {
198 return Ok(None);
199 };
200 let charset = rest.get(..q1).unwrap_or(&[]);
201 if charset.is_empty()
202 || !charset
203 .iter()
204 .all(|&b| class::is_mime_token_char(b) && b != b'?')
205 {
206 return Ok(None);
207 }
208 let after = rest.get(q1 + 1..).unwrap_or(&[]);
209 let (Some(&enc), Some(b'?')) = (after.first(), after.get(1)) else {
210 return Ok(None);
211 };
212 let text_start = i + 2 + q1 + 3;
213 let text_rest = v.get(text_start..).unwrap_or(&[]);
214 let Some(end) = text_rest.windows(2).position(|w| w == b"?=") else {
215 return Ok(None);
216 };
217 let text = text_rest.get(..end).unwrap_or(&[]);
218 if text.iter().any(|&b| b == b' ' || b == b'\t' || b == b'?') {
219 return Ok(None);
220 }
221 let name = charset.split(|&b| b == b'*').next().unwrap_or(&[]);
223 let cs = if name.eq_ignore_ascii_case(b"utf-8") {
224 Charset::Utf8
225 } else if name.eq_ignore_ascii_case(b"us-ascii") {
226 Charset::Ascii
227 } else if name.eq_ignore_ascii_case(b"iso-8859-1") {
228 Charset::Latin1
229 } else {
230 return err(ErrorKind::UnsupportedCharset, i + 2);
231 };
232 let b = match enc {
233 b'Q' | b'q' => false,
234 b'B' | b'b' => true,
235 _ => return err(ErrorKind::InvalidEncodedWord, i + 2 + q1 + 1),
236 };
237 Ok(Some(Encoded {
238 charset: cs,
239 base64: b,
240 text,
241 end: text_start + end + 2,
242 }))
243}
244
245fn decode_q(cs: Charset, text: &[u8], at: usize, emit: &mut impl FnMut(&[u8])) -> Result<()> {
247 let mut utf8 = Utf8::new();
248 let (mut i, mut prev) = (0usize, 0u8);
249 while let Some(&b) = text.get(i) {
250 let byte = match b {
251 b'_' => b' ',
252 b'=' => {
253 let hi = text.get(i + 1).copied().and_then(class::hex_value);
254 let lo = text.get(i + 2).copied().and_then(class::hex_value);
255 let (Some(hi), Some(lo)) = (hi, lo) else {
256 return err(ErrorKind::InvalidEncodedWord, at + i);
257 };
258 i += 2;
259 (hi << 4) | lo
260 }
261 0x21..=0x7e => b,
262 _ => return err(ErrorKind::InvalidEncodedWord, at + i),
263 };
264 i += 1;
265 let c1 = match cs {
270 Charset::Utf8 => prev == 0xc2 && (0x80..=0x9f).contains(&byte),
271 Charset::Latin1 => (0x80..=0x9f).contains(&byte),
272 Charset::Ascii => false,
273 };
274 if byte < 0x20 || byte == 0x7f || c1 {
275 return err(ErrorKind::ControlInDecoded, at + i - 1);
276 }
277 prev = byte;
278 match cs {
279 Charset::Utf8 => {
280 if !utf8.push(byte) {
281 return err(ErrorKind::InvalidUtf8, at + i - 1);
282 }
283 emit(&[byte]);
284 }
285 Charset::Ascii if byte >= 0x80 => {
286 return err(ErrorKind::InvalidEncodedWord, at + i - 1);
287 }
288 Charset::Ascii => emit(&[byte]),
289 Charset::Latin1 if byte >= 0x80 => emit(&[0xc0 | (byte >> 6), 0x80 | (byte & 0x3f)]),
290 Charset::Latin1 => emit(&[byte]),
291 }
292 }
293 if !utf8.is_complete() {
294 return err(ErrorKind::InvalidUtf8, at + text.len());
296 }
297 Ok(())
298}
299
300fn walk(v: &[u8], limits: &Limits, emit: &mut impl FnMut(&[u8])) -> Result<()> {
302 let (mut i, mut words) = (0usize, 0usize);
303 let (mut boundary, mut quoted, mut after_word) = (true, false, false);
304 let mut held: Option<(usize, usize)> = None;
305 while let Some(&b) = v.get(i) {
306 if quoted {
307 emit(&[b]);
308 if b == b'\\' {
309 if let Some(&n) = v.get(i + 1) {
310 emit(&[n]);
311 i += 1;
312 }
313 } else if b == b'"' {
314 quoted = false;
315 }
316 i += 1;
317 continue;
318 }
319 if b == b' ' || b == b'\t' {
320 let start = i;
321 while matches!(v.get(i), Some(b' ' | b'\t')) {
322 i += 1;
323 }
324 if after_word {
325 held = Some((start, i));
326 } else {
327 emit(v.get(start..i).unwrap_or(&[]));
328 }
329 boundary = true;
330 continue;
331 }
332 if boundary && v.get(i..i + 2) == Some(b"=?") {
333 if let Some(Encoded {
334 charset: cs,
335 base64: b64,
336 text,
337 end,
338 }) = shape(v, i)?
339 {
340 if end == v.len() || matches!(v.get(end), Some(b' ' | b'\t')) {
341 words += 1;
342 if words > usize::from(limits.encoded_words) {
343 return err(ErrorKind::TooManyMembers, i);
344 }
345 if b64 {
346 return err(ErrorKind::InvalidEncodedWord, i);
348 }
349 held = None;
352 let text_at = end - 2 - text.len();
353 decode_q(cs, text, text_at, emit)?;
354 after_word = true;
355 boundary = false;
356 i = end;
357 continue;
358 }
359 }
360 }
361 if let Some((s, e)) = held.take() {
362 emit(v.get(s..e).unwrap_or(&[]));
363 }
364 after_word = false;
365 boundary = false;
366 if b == b'"' {
367 quoted = true;
368 }
369 emit(&[b]);
370 i += 1;
371 }
372 if let Some((s, e)) = held {
373 emit(v.get(s..e).unwrap_or(&[]));
374 }
375 Ok(())
376}
377
378pub fn decode_words<'b>(value: &[u8], limits: &Limits, out: &'b mut [u8]) -> Result<&'b str> {
409 let mut len = 0usize;
410 walk(value, limits, &mut |b: &[u8]| len += b.len())?;
411 let Some(dst) = out.get_mut(..len) else {
412 return err(ErrorKind::BufferTooSmall, 0);
413 };
414 let mut n = 0usize;
415 walk(value, limits, &mut |b: &[u8]| {
416 if let Some(d) = dst.get_mut(n..n + b.len()) {
417 d.copy_from_slice(b);
418 }
419 n += b.len();
420 })?;
421 if let Err(e) = core::str::from_utf8(dst) {
422 let at = e.valid_up_to();
423 dst.fill(0);
425 return Err(ParseError::new(ErrorKind::InvalidUtf8, at));
426 }
427 core::str::from_utf8(dst).map_err(|e| ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use std::{string::String, vec};
434
435 fn enc(text: &str, room: usize) -> String {
436 let mut buf = vec![0u8; 2048];
437 let mut out = crate::SliceSink::new(&mut buf);
438 encode_words(text, room, &mut out).unwrap();
439 String::from_utf8(out.written().to_vec()).unwrap()
440 }
441
442 fn dec(v: &[u8]) -> Result<String> {
443 let mut buf = vec![0u8; 2048];
444 decode_words(v, &Limits::DEFAULT, &mut buf).map(String::from)
445 }
446
447 #[test]
448 fn test_encode_words_edge_cases() {
449 assert_eq!(enc("plain ascii", 10), "plain ascii");
450 assert_eq!(enc("a =?b", 70), "=?UTF-8?Q?a_=3D=3Fb?=");
451 let long = "\u{e9}".repeat(40);
452 let e = enc(&long, 75);
453 for word in e.split("\r\n ") {
454 assert!(word.len() <= 75 && word.starts_with("=?UTF-8?Q?") && word.ends_with("?="));
455 assert!(dec(word.as_bytes()).is_ok());
457 }
458 assert_eq!(dec(e.replace("\r\n", "").as_bytes()).unwrap(), long);
459 let e = enc(&"\u{20ac}".repeat(7), 75);
461 assert_eq!(e.len(), 12 + 63);
462 assert!(!e.starts_with("\r\n "));
463 assert!(enc("\u{e9}", 5).starts_with("\r\n =?UTF-8?Q?"));
464 let mut buf = [0u8; 8];
465 let mut out = crate::SliceSink::new(&mut buf);
466 assert_eq!(
467 encode_words("a\nb", 70, &mut out),
468 Err(WriteError::Forbidden { at: 1 })
469 );
470 assert_eq!(
471 encode_words("a\u{85}", 70, &mut out),
472 Err(WriteError::Forbidden { at: 1 })
473 );
474 assert_eq!(
475 encode_words("\u{e9}\u{e9}", 70, &mut out),
476 Err(WriteError::Full)
477 );
478 assert!(out.is_empty());
479 }
480
481 #[test]
482 fn test_decode_words_edge_cases() {
483 assert_eq!(dec(b"=?utf-8?q?a_b?=").unwrap(), "a b");
484 assert_eq!(dec(b"=?UTF-8?Q?a?= \t =?UTF-8?Q?b?=").unwrap(), "ab");
485 assert_eq!(dec(b"=?UTF-8?Q?a?= x =?UTF-8?Q?b?= ").unwrap(), "a x b ");
486 assert_eq!(dec(b"\"=?UTF-8?Q?a?=\"").unwrap(), "\"=?UTF-8?Q?a?=\"");
487 assert_eq!(dec(b"x=?UTF-8?Q?a?=").unwrap(), "x=?UTF-8?Q?a?=");
488 assert_eq!(dec(b"=?UTF-8?Q?a?=x").unwrap(), "=?UTF-8?Q?a?=x");
489 assert_eq!(dec(b"=?UTF-8*en?Q?a?=").unwrap(), "a");
490 assert_eq!(dec(b"=? not a word").unwrap(), "=? not a word");
491 assert_eq!(dec(b"=?US-ASCII?Q?ok?=").unwrap(), "ok");
492 assert_eq!(
493 dec(b"=?KOI8-R?Q?a?=").unwrap_err().kind(),
494 ErrorKind::UnsupportedCharset
495 );
496 assert_eq!(
497 dec(b"=?UTF-8?X?a?=").unwrap_err().kind(),
498 ErrorKind::InvalidEncodedWord
499 );
500 assert_eq!(
501 dec(b"=?UTF-8?Q?=4?=").unwrap_err().kind(),
502 ErrorKind::InvalidEncodedWord
503 );
504 assert_eq!(
505 dec(b"=?UTF-8?B?YQ==?=").unwrap_err().kind(),
506 ErrorKind::InvalidEncodedWord
507 );
508 assert_eq!(
509 dec(b"=?UTF-8?Q?=0D=0A?=").unwrap_err().kind(),
510 ErrorKind::ControlInDecoded
511 );
512 assert_eq!(
513 dec(b"=?UTF-8?Q?=C3?=").unwrap_err().kind(),
514 ErrorKind::InvalidUtf8
515 );
516 assert_eq!(
517 dec(b"=?US-ASCII?Q?=E9?=").unwrap_err().kind(),
518 ErrorKind::InvalidEncodedWord
519 );
520 assert_eq!(dec(b"raw \xe9").unwrap_err().kind(), ErrorKind::InvalidUtf8);
521 let l = Limits::DEFAULT.max_encoded_words(2);
522 let mut buf = [0u8; 64];
523 assert!(decode_words(b"=?UTF-8?Q?a?= =?UTF-8?Q?b?=", &l, &mut buf).is_ok());
524 assert_eq!(
525 decode_words(b"=?UTF-8?Q?a?= =?UTF-8?Q?b?= =?UTF-8?Q?c?=", &l, &mut buf)
526 .unwrap_err()
527 .kind(),
528 ErrorKind::TooManyMembers
529 );
530 let mut small = [0u8; 2];
531 assert_eq!(
532 decode_words(b"abc", &Limits::DEFAULT, &mut small)
533 .unwrap_err()
534 .kind(),
535 ErrorKind::BufferTooSmall
536 );
537 assert_eq!(small, [0, 0]);
538 }
539}