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(v, i, limits)?.0,
68 _ => return Ok(i),
69 }
70 }
71}
72
73pub fn check_header_value(value: &[u8]) -> Result<()> {
92 if let Some(at) = value.iter().position(|&b| matches!(b, b'\r' | b'\n' | 0)) {
93 return err(control_kind(value.get(at).copied().unwrap_or(0)), at);
94 }
95 if value.len() > HARD_LIMIT {
96 return err(ErrorKind::LineTooLong, HARD_LIMIT);
97 }
98 Ok(())
99}
100
101#[must_use]
111pub fn is_field_name(name: &[u8]) -> bool {
112 !name.is_empty() && name.iter().all(|&b| matches!(b, 33..=57 | 59..=126))
113}
114
115pub fn unfold(value: &[u8]) -> Result<Unfolded<'_>> {
135 let mut i = 0usize;
136 while let Some(&b) = value.get(i) {
137 match b {
138 b'\r' => {
139 if value.get(i + 1) == Some(&b'\n')
140 && matches!(value.get(i + 2), Some(b' ' | b'\t'))
141 {
142 i += 3;
143 continue;
144 }
145 return err(ErrorKind::BareCr, i);
146 }
147 b'\n' => return err(ErrorKind::LineFeed, i),
148 0 => return err(ErrorKind::Nul, i),
149 _ => i += 1,
150 }
151 }
152 Ok(Unfolded { value })
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
166pub struct Unfolded<'a> {
167 value: &'a [u8],
168}
169
170impl<'a> Unfolded<'a> {
171 #[must_use]
181 pub fn iter(&self) -> UnfoldIter<'a> {
182 UnfoldIter {
183 rest: Some(self.value),
184 }
185 }
186}
187
188#[derive(Clone, Debug)]
201pub struct UnfoldIter<'a> {
202 rest: Option<&'a [u8]>,
203}
204
205impl<'a> Iterator for UnfoldIter<'a> {
206 type Item = &'a [u8];
207
208 fn next(&mut self) -> Option<&'a [u8]> {
209 let rest = self.rest?;
210 match rest.iter().position(|&b| b == b'\r') {
211 Some(at) => {
212 self.rest = rest.get(at + 2..);
213 rest.get(..at)
214 }
215 None => {
216 self.rest = None;
217 Some(rest)
218 }
219 }
220 }
221}
222
223pub fn fold(name: &str, value: &[u8], out: &mut impl Sink) -> core::result::Result<(), WriteError> {
252 if !is_field_name(name.as_bytes()) {
253 return Err(WriteError::NotToken);
254 }
255 if let Some(at) = value.iter().position(|&b| matches!(b, b'\r' | b'\n' | 0)) {
256 return Err(WriteError::forbidden(at));
257 }
258 let mut count = CountSink::new();
259 put_fold(name.as_bytes(), value, &mut count)?;
260 reserve(out, count.count())?;
261 put_fold(name.as_bytes(), value, out)
262}
263
264fn put_fold(
265 name: &[u8],
266 value: &[u8],
267 out: &mut impl Sink,
268) -> core::result::Result<(), WriteError> {
269 out.put(name)?;
270 out.put(b":")?;
271 let mut col = name.len() + 1;
272 if value.first().is_some_and(|&b| b != b' ' && b != b'\t') {
273 out.put(b" ")?;
274 col += 1;
275 }
276 let mut words_on_line = 0usize;
277 let mut rest = value;
278 loop {
279 let ws_len = rest
280 .iter()
281 .take_while(|&&b| b == b' ' || b == b'\t')
282 .count();
283 let (ws, tail) = rest.split_at(ws_len);
284 let word_len = tail
285 .iter()
286 .take_while(|&&b| b != b' ' && b != b'\t')
287 .count();
288 let (word, tail) = tail.split_at(word_len);
289 if word.is_empty() {
290 if col + ws.len() > HARD_LIMIT {
294 return Err(WriteError::OutOfRange);
295 }
296 out.put(ws)?;
297 return Ok(());
298 }
299 if words_on_line > 0 && col + ws.len() + word.len() > SOFT_LIMIT {
300 out.put(b"\r\n")?;
301 col = 0;
302 words_on_line = 0;
303 }
304 if col + ws.len() + word.len() > HARD_LIMIT {
305 return Err(WriteError::OutOfRange);
306 }
307 out.put(ws)?;
308 out.put(word)?;
309 col += ws.len() + word.len();
310 words_on_line += 1;
311 rest = tail;
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use std::{vec, vec::Vec};
319
320 #[test]
321 fn test_check_header_value_limits() {
322 assert!(check_header_value(&[b'a'; 998]).is_ok());
323 assert_eq!(
324 check_header_value(&[b'a'; 999]).unwrap_err().kind(),
325 ErrorKind::LineTooLong
326 );
327 assert_eq!(
328 check_header_value(b"a\nb").unwrap_err().kind(),
329 ErrorKind::LineFeed
330 );
331 assert_eq!(
332 check_header_value(b"a\x00b").unwrap_err().kind(),
333 ErrorKind::Nul
334 );
335 assert!(check_header_value(b"").is_ok());
336 }
337
338 #[test]
339 fn test_field_names() {
340 assert!(is_field_name(b"Subject") && is_field_name(b"!#$%&'*+-.^_`|~9"));
341 assert!(!is_field_name(b"A B") && !is_field_name(b"A:") && !is_field_name(b"\x7f"));
342 }
343
344 #[test]
345 fn test_unfold_edge_cases() {
346 let u = unfold(b"a\r\n\tb\r\n c").unwrap();
347 assert_eq!(u.iter().collect::<Vec<_>>(), vec![&b"a"[..], b"\tb", b" c"]);
348 assert_eq!(
349 unfold(b"").unwrap().iter().collect::<Vec<_>>(),
350 vec![&b""[..]]
351 );
352 assert_eq!(unfold(b"a\r").unwrap_err().kind(), ErrorKind::BareCr);
353 assert_eq!(unfold(b"a\r\n").unwrap_err().kind(), ErrorKind::BareCr);
354 assert_eq!(unfold(b"a\nb").unwrap_err().kind(), ErrorKind::LineFeed);
355 }
356
357 fn folded(name: &str, value: &[u8]) -> Vec<u8> {
358 let mut buf = vec![0u8; 4096];
359 let mut out = crate::SliceSink::new(&mut buf);
360 fold(name, value, &mut out).unwrap();
361 out.written().to_vec()
362 }
363
364 #[test]
365 fn test_fold_lines_and_round_trip() {
366 let value = "the quick brown fox jumps over the lazy dog ".repeat(5);
367 let value = value.trim_end().as_bytes();
368 let f = folded("Subject", value);
369 for line in f.split(|&b| b == b'\n') {
370 let line = line.strip_suffix(b"\r").unwrap_or(line);
371 assert!(line.len() <= 78, "{}", line.len());
372 assert!(!line.iter().all(|&b| b == b' '));
373 }
374 let body = f.strip_prefix(b"Subject: ").unwrap();
375 let back: Vec<u8> = unfold(body).unwrap().iter().flatten().copied().collect();
376 assert_eq!(back, value);
377 assert_eq!(folded("X", b" lead"), b"X: lead");
378 assert_eq!(folded("X", b"tab\tsep"), b"X: tab\tsep");
379 assert_eq!(folded("X", b""), b"X:");
380 assert_eq!(folded("X", b"a "), b"X: a ");
381 }
382
383 #[test]
384 fn test_fold_long_words() {
385 let word = vec![b'w'; 989];
386 let f = folded("Subject", &word);
387 assert_eq!(f.len(), 998);
388 let mut buf = vec![0u8; 4096];
389 let mut out = crate::SliceSink::new(&mut buf);
390 assert_eq!(
391 fold("Subject", &[b'w'; 1000], &mut out),
392 Err(WriteError::OutOfRange)
393 );
394 let mut two = vec![b'a'; 10];
395 two.push(b' ');
396 two.extend_from_slice(&[b'w'; 997]);
397 assert_eq!(fold("S", &two, &mut out), Ok(()));
398 let mut two = vec![b'a'; 10];
399 two.push(b' ');
400 two.extend_from_slice(&[b'w'; 998]);
401 assert_eq!(fold("S", &two, &mut out), Err(WriteError::OutOfRange));
402 let mut tail = vec![b'w'; 989];
405 tail.push(b'\t');
406 assert_eq!(
407 fold("Subject", &tail, &mut out),
408 Err(WriteError::OutOfRange)
409 );
410 let mut fits = vec![b'w'; 988];
411 fits.push(b'\t');
412 assert_eq!(folded("Subject", &fits).len(), 998);
413 let mut many = b"a b ".to_vec();
415 many.extend_from_slice(&[b'w'; 997]);
416 many.extend_from_slice(b" ");
417 assert_eq!(fold("S", &many, &mut out), Err(WriteError::OutOfRange));
418 }
419
420 #[test]
421 fn test_fold_refusals_write_nothing() {
422 let mut buf = [0u8; 8];
423 let mut out = crate::SliceSink::new(&mut buf);
424 assert_eq!(fold("A B", b"x", &mut out), Err(WriteError::NotToken));
425 assert_eq!(
426 fold("A", b"x\r\nBcc: y", &mut out),
427 Err(WriteError::Forbidden { at: 1 })
428 );
429 assert_eq!(
430 fold("A", b"too long for eight", &mut out),
431 Err(WriteError::Full)
432 );
433 assert!(out.is_empty());
434 }
435}