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> {
251 if !is_field_name(name.as_bytes()) {
252 return Err(WriteError::NotToken);
253 }
254 if let Some(at) = value.iter().position(|&b| matches!(b, b'\r' | b'\n' | 0)) {
255 return Err(WriteError::forbidden(at));
256 }
257 let mut count = CountSink::new();
258 put_fold(name.as_bytes(), value, &mut count)?;
259 reserve(out, count.count())?;
260 put_fold(name.as_bytes(), value, out)
261}
262
263fn put_fold(
264 name: &[u8],
265 value: &[u8],
266 out: &mut impl Sink,
267) -> core::result::Result<(), WriteError> {
268 out.put(name)?;
269 out.put(b":")?;
270 let mut col = name.len() + 1;
271 if value.first().is_some_and(|&b| b != b' ' && b != b'\t') {
272 out.put(b" ")?;
273 col += 1;
274 }
275 let mut words_on_line = 0usize;
276 let mut rest = value;
277 loop {
278 let ws_len = rest
279 .iter()
280 .take_while(|&&b| b == b' ' || b == b'\t')
281 .count();
282 let (ws, tail) = rest.split_at(ws_len);
283 let word_len = tail
284 .iter()
285 .take_while(|&&b| b != b' ' && b != b'\t')
286 .count();
287 let (word, tail) = tail.split_at(word_len);
288 if word.is_empty() {
289 out.put(ws)?;
291 return Ok(());
292 }
293 if words_on_line > 0 && col + ws.len() + word.len() > SOFT_LIMIT {
294 out.put(b"\r\n")?;
295 col = 0;
296 words_on_line = 0;
297 }
298 if col + ws.len() + word.len() > HARD_LIMIT {
299 return Err(WriteError::OutOfRange);
300 }
301 out.put(ws)?;
302 out.put(word)?;
303 col += ws.len() + word.len();
304 words_on_line += 1;
305 rest = tail;
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use std::{vec, vec::Vec};
313
314 #[test]
315 fn test_check_header_value_limits() {
316 assert!(check_header_value(&[b'a'; 998]).is_ok());
317 assert_eq!(
318 check_header_value(&[b'a'; 999]).unwrap_err().kind(),
319 ErrorKind::LineTooLong
320 );
321 assert_eq!(
322 check_header_value(b"a\nb").unwrap_err().kind(),
323 ErrorKind::LineFeed
324 );
325 assert_eq!(
326 check_header_value(b"a\x00b").unwrap_err().kind(),
327 ErrorKind::Nul
328 );
329 assert!(check_header_value(b"").is_ok());
330 }
331
332 #[test]
333 fn test_field_names() {
334 assert!(is_field_name(b"Subject") && is_field_name(b"!#$%&'*+-.^_`|~9"));
335 assert!(!is_field_name(b"A B") && !is_field_name(b"A:") && !is_field_name(b"\x7f"));
336 }
337
338 #[test]
339 fn test_unfold_edge_cases() {
340 let u = unfold(b"a\r\n\tb\r\n c").unwrap();
341 assert_eq!(u.iter().collect::<Vec<_>>(), vec![&b"a"[..], b"\tb", b" c"]);
342 assert_eq!(
343 unfold(b"").unwrap().iter().collect::<Vec<_>>(),
344 vec![&b""[..]]
345 );
346 assert_eq!(unfold(b"a\r").unwrap_err().kind(), ErrorKind::BareCr);
347 assert_eq!(unfold(b"a\r\n").unwrap_err().kind(), ErrorKind::BareCr);
348 assert_eq!(unfold(b"a\nb").unwrap_err().kind(), ErrorKind::LineFeed);
349 }
350
351 fn folded(name: &str, value: &[u8]) -> Vec<u8> {
352 let mut buf = vec![0u8; 4096];
353 let mut out = crate::SliceSink::new(&mut buf);
354 fold(name, value, &mut out).unwrap();
355 out.written().to_vec()
356 }
357
358 #[test]
359 fn test_fold_lines_and_round_trip() {
360 let value = "the quick brown fox jumps over the lazy dog ".repeat(5);
361 let value = value.trim_end().as_bytes();
362 let f = folded("Subject", value);
363 for line in f.split(|&b| b == b'\n') {
364 let line = line.strip_suffix(b"\r").unwrap_or(line);
365 assert!(line.len() <= 78, "{}", line.len());
366 assert!(!line.iter().all(|&b| b == b' '));
367 }
368 let body = f.strip_prefix(b"Subject: ").unwrap();
369 let back: Vec<u8> = unfold(body).unwrap().iter().flatten().copied().collect();
370 assert_eq!(back, value);
371 assert_eq!(folded("X", b" lead"), b"X: lead");
372 assert_eq!(folded("X", b"tab\tsep"), b"X: tab\tsep");
373 assert_eq!(folded("X", b""), b"X:");
374 assert_eq!(folded("X", b"a "), b"X: a ");
375 }
376
377 #[test]
378 fn test_fold_long_words() {
379 let word = vec![b'w'; 989];
380 let f = folded("Subject", &word);
381 assert_eq!(f.len(), 998);
382 let mut buf = vec![0u8; 4096];
383 let mut out = crate::SliceSink::new(&mut buf);
384 assert_eq!(
385 fold("Subject", &[b'w'; 1000], &mut out),
386 Err(WriteError::OutOfRange)
387 );
388 let mut two = vec![b'a'; 10];
389 two.push(b' ');
390 two.extend_from_slice(&[b'w'; 997]);
391 assert_eq!(fold("S", &two, &mut out), Ok(()));
392 let mut two = vec![b'a'; 10];
393 two.push(b' ');
394 two.extend_from_slice(&[b'w'; 998]);
395 assert_eq!(fold("S", &two, &mut out), Err(WriteError::OutOfRange));
396 }
397
398 #[test]
399 fn test_fold_refusals_write_nothing() {
400 let mut buf = [0u8; 8];
401 let mut out = crate::SliceSink::new(&mut buf);
402 assert_eq!(fold("A B", b"x", &mut out), Err(WriteError::NotToken));
403 assert_eq!(
404 fold("A", b"x\r\nBcc: y", &mut out),
405 Err(WriteError::Forbidden { at: 1 })
406 );
407 assert_eq!(
408 fold("A", b"too long for eight", &mut out),
409 Err(WriteError::Full)
410 );
411 assert!(out.is_empty());
412 }
413}