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