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