1mod addr;
28mod b64;
29mod params;
30mod words;
31
32pub use addr::{
33 AddrSpec, Domain, LocalPart, Mailbox, MsgId, parse_addr_spec, parse_mailbox, parse_msg_id,
34 write_msg_id,
35};
36pub use params::{
37 Continuation, MimeParamIter, MimeParams, MimeValue, parse_mime_params, write_mime_param,
38};
39pub use words::{decode_words, encode_words, encode_words_b};
40
41pub use crate::MailDate;
42
43use crate::{
44 error::{ErrorKind, Result, WriteError, err},
45 scan::control_kind,
46 write::{CountSink, Sink, reserve},
47};
48
49const HARD_LIMIT: usize = 998;
51const SOFT_LIMIT: usize = 78;
53
54pub(super) fn cfws(v: &[u8], mut i: usize, limits: &crate::Limits) -> Result<usize> {
57 loop {
58 match v.get(i) {
59 Some(b' ' | b'\t') => i += 1,
60 Some(b'\r') => {
61 if v.get(i + 1) == Some(&b'\n') && matches!(v.get(i + 2), Some(b' ' | b'\t')) {
62 i += 3;
63 } else {
64 return err(ErrorKind::BareCr, i);
65 }
66 }
67 Some(b'\n') => return err(ErrorKind::LineFeed, i),
68 Some(b'(') => i = crate::text::comment_end_folded(v, i, limits)?.0,
69 _ => return Ok(i),
70 }
71 }
72}
73
74const fn is_refused(b: u8) -> bool {
76 b != b'\t' && crate::scan::is_control(b)
77}
78
79pub fn check_header_value(value: &[u8]) -> Result<()> {
98 if let Some(at) = value.iter().position(|&b| is_refused(b)) {
99 return err(control_kind(value.get(at).copied().unwrap_or(0)), at);
100 }
101 if value.len() > HARD_LIMIT {
102 return err(ErrorKind::LineTooLong, HARD_LIMIT);
103 }
104 Ok(())
105}
106
107#[must_use]
117pub fn is_field_name(name: &[u8]) -> bool {
118 !name.is_empty() && name.iter().all(|&b| matches!(b, 33..=57 | 59..=126))
119}
120
121pub fn unfold(value: &[u8]) -> Result<Unfolded<'_>> {
141 let mut i = 0usize;
142 while let Some(&b) = value.get(i) {
143 match b {
144 b'\r' => {
145 if value.get(i + 1) == Some(&b'\n')
146 && matches!(value.get(i + 2), Some(b' ' | b'\t'))
147 {
148 i += 3;
149 continue;
150 }
151 return err(ErrorKind::BareCr, i);
152 }
153 b'\n' => return err(ErrorKind::LineFeed, i),
154 0 => return err(ErrorKind::Nul, i),
155 _ => i += 1,
156 }
157 }
158 Ok(Unfolded { value })
159}
160
161#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
172pub struct Unfolded<'a> {
173 value: &'a [u8],
174}
175
176impl<'a> Unfolded<'a> {
177 #[must_use]
187 pub fn iter(&self) -> UnfoldIter<'a> {
188 UnfoldIter {
189 rest: Some(self.value),
190 }
191 }
192}
193
194#[derive(Clone, Debug)]
207pub struct UnfoldIter<'a> {
208 rest: Option<&'a [u8]>,
209}
210
211impl<'a> Iterator for UnfoldIter<'a> {
212 type Item = &'a [u8];
213
214 fn next(&mut self) -> Option<&'a [u8]> {
215 let rest = self.rest?;
216 match rest.iter().position(|&b| b == b'\r') {
217 Some(at) => {
218 self.rest = rest.get(at + 2..);
219 rest.get(..at)
220 }
221 None => {
222 self.rest = None;
223 Some(rest)
224 }
225 }
226 }
227}
228
229pub fn fold(name: &str, value: &[u8], out: &mut impl Sink) -> core::result::Result<(), WriteError> {
258 if !is_field_name(name.as_bytes()) {
259 return Err(WriteError::NotToken);
260 }
261 if let Some(at) = value.iter().position(|&b| is_refused(b)) {
262 return Err(WriteError::forbidden(at));
263 }
264 let mut count = CountSink::new();
265 put_fold(name.as_bytes(), value, &mut count)?;
266 reserve(out, count.count())?;
267 put_fold(name.as_bytes(), value, out)
268}
269
270fn put_fold(
271 name: &[u8],
272 value: &[u8],
273 out: &mut impl Sink,
274) -> core::result::Result<(), WriteError> {
275 out.put(name)?;
276 out.put(b":")?;
277 let mut col = name.len() + 1;
278 let mut first = true;
279 let mut rest = value;
280 loop {
281 let ws_len = rest
282 .iter()
283 .take_while(|&&b| b == b' ' || b == b'\t')
284 .count();
285 let (ws, tail) = rest.split_at(ws_len);
286 let word_len = tail
287 .iter()
288 .take_while(|&&b| b != b' ' && b != b'\t')
289 .count();
290 let (word, tail) = tail.split_at(word_len);
291 if word.is_empty() {
292 if col + ws.len() > HARD_LIMIT {
296 return Err(WriteError::OutOfRange);
297 }
298 out.put(ws)?;
299 return Ok(());
300 }
301 let own_space = first && ws.is_empty();
304 let mut gap: &[u8] = if own_space { b" " } else { ws };
305 let width = col + gap.len() + word.len();
306 if width > HARD_LIMIT || (width > SOFT_LIMIT && !own_space) {
307 if gap.len() + word.len() > HARD_LIMIT {
310 let (keep, last) = gap.split_at(gap.len() - 1);
311 if col + keep.len() > HARD_LIMIT {
312 return Err(WriteError::OutOfRange);
313 }
314 out.put(keep)?;
315 gap = last;
316 }
317 out.put(b"\r\n")?;
318 col = 0;
319 }
320 if col + gap.len() + word.len() > HARD_LIMIT {
321 return Err(WriteError::OutOfRange);
322 }
323 out.put(gap)?;
324 out.put(word)?;
325 col += gap.len() + word.len();
326 first = false;
327 rest = tail;
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use std::{vec, vec::Vec};
335
336 #[test]
337 fn test_check_header_value_limits() {
338 assert!(check_header_value(&[b'a'; 998]).is_ok());
339 assert_eq!(
340 check_header_value(&[b'a'; 999]).unwrap_err().kind(),
341 ErrorKind::LineTooLong
342 );
343 assert_eq!(
344 check_header_value(b"a\nb").unwrap_err().kind(),
345 ErrorKind::LineFeed
346 );
347 assert_eq!(
348 check_header_value(b"a\x00b").unwrap_err().kind(),
349 ErrorKind::Nul
350 );
351 assert!(check_header_value(b"").is_ok());
352 }
353
354 #[test]
355 fn test_field_names() {
356 assert!(is_field_name(b"Subject") && is_field_name(b"!#$%&'*+-.^_`|~9"));
357 assert!(!is_field_name(b"A B") && !is_field_name(b"A:") && !is_field_name(b"\x7f"));
358 }
359
360 #[test]
361 fn test_unfold_edge_cases() {
362 let u = unfold(b"a\r\n\tb\r\n c").unwrap();
363 assert_eq!(u.iter().collect::<Vec<_>>(), vec![&b"a"[..], b"\tb", b" c"]);
364 assert_eq!(
365 unfold(b"").unwrap().iter().collect::<Vec<_>>(),
366 vec![&b""[..]]
367 );
368 assert_eq!(unfold(b"a\r").unwrap_err().kind(), ErrorKind::BareCr);
369 assert_eq!(unfold(b"a\r\n").unwrap_err().kind(), ErrorKind::BareCr);
370 assert_eq!(unfold(b"a\nb").unwrap_err().kind(), ErrorKind::LineFeed);
371 }
372
373 fn folded(name: &str, value: &[u8]) -> Vec<u8> {
374 let mut buf = vec![0u8; 4096];
375 let mut out = crate::SliceSink::new(&mut buf);
376 fold(name, value, &mut out).unwrap();
377 out.written().to_vec()
378 }
379
380 #[test]
381 fn test_fold_lines_and_round_trip() {
382 let value = "the quick brown fox jumps over the lazy dog ".repeat(5);
383 let value = value.trim_end().as_bytes();
384 let f = folded("Subject", value);
385 for line in f.split(|&b| b == b'\n') {
386 let line = line.strip_suffix(b"\r").unwrap_or(line);
387 assert!(line.len() <= 78, "{}", line.len());
388 assert!(!line.iter().all(|&b| b == b' '));
389 }
390 let body = f.strip_prefix(b"Subject: ").unwrap();
391 let back: Vec<u8> = unfold(body).unwrap().iter().flatten().copied().collect();
392 assert_eq!(back, value);
393 assert_eq!(folded("X", b" lead"), b"X: lead");
394 assert_eq!(folded("X", b"tab\tsep"), b"X: tab\tsep");
395 assert_eq!(folded("X", b""), b"X:");
396 assert_eq!(folded("X", b"a "), b"X: a ");
397 }
398
399 #[test]
400 fn test_fold_long_words() {
401 let word = vec![b'w'; 989];
402 let f = folded("Subject", &word);
403 assert_eq!(f.len(), 998);
404 let mut buf = vec![0u8; 4096];
405 let mut out = crate::SliceSink::new(&mut buf);
406 assert_eq!(
407 fold("Subject", &[b'w'; 1000], &mut out),
408 Err(WriteError::OutOfRange)
409 );
410 let mut two = vec![b'a'; 10];
411 two.push(b' ');
412 two.extend_from_slice(&[b'w'; 997]);
413 assert_eq!(fold("S", &two, &mut out), Ok(()));
414 let mut two = vec![b'a'; 10];
415 two.push(b' ');
416 two.extend_from_slice(&[b'w'; 998]);
417 assert_eq!(fold("S", &two, &mut out), Err(WriteError::OutOfRange));
418 let mut tail = vec![b'w'; 989];
421 tail.push(b'\t');
422 assert_eq!(
423 fold("Subject", &tail, &mut out),
424 Err(WriteError::OutOfRange)
425 );
426 let mut fits = vec![b'w'; 988];
427 fits.push(b'\t');
428 assert_eq!(folded("Subject", &fits).len(), 998);
429 let mut many = b"a b ".to_vec();
431 many.extend_from_slice(&[b'w'; 997]);
432 many.extend_from_slice(b" ");
433 assert_eq!(fold("S", &many, &mut out), Err(WriteError::OutOfRange));
434 }
435
436 #[test]
437 fn test_fold_refusals_write_nothing() {
438 let mut buf = [0u8; 8];
439 let mut out = crate::SliceSink::new(&mut buf);
440 assert_eq!(fold("A B", b"x", &mut out), Err(WriteError::NotToken));
441 assert_eq!(
442 fold("A", b"x\r\nBcc: y", &mut out),
443 Err(WriteError::Forbidden { at: 1 })
444 );
445 assert_eq!(
446 fold("A", b"too long for eight", &mut out),
447 Err(WriteError::Full)
448 );
449 assert!(out.is_empty());
450 }
451}