Skip to main content

parse_net/mail/
mod.rs

1//! The email lexical layer (RFC 5322, RFC 2045, RFC 2047, RFC 2231): header
2//! lines and folding, encoded words, MIME parameters with continuations,
3//! addresses, and message identifiers. The grammar smtp-net needs to write
4//! mail and gate-net needs to validate login addresses (D-008); not a mail
5//! parser. Charsets decoded: UTF-8, US-ASCII, ISO-8859-1.
6//!
7//! # Examples
8//!
9//! ```
10//! use parse_net::{mail, SliceSink, Limits};
11//!
12//! let mut buf = [0u8; 256];
13//! let mut out = SliceSink::new(&mut buf);
14//! mail::encode_words("Caf\u{e9} au lait", 64, &mut out).expect("room");
15//! assert_eq!(out.written(), b"=?UTF-8?Q?Caf=C3=A9_au_lait?=");
16//!
17//! let a = mail::parse_addr_spec(b"\"john doe\"@example.com")?;
18//! assert!(a.is_ascii());
19//! assert!(mail::parse_addr_spec(b"john..doe@example.com").is_err());
20//!
21//! let (p, _) = mail::parse_mime_params(b"; filename*0*=UTF-8''caf%C3%A9; filename*1=\".txt\"", &Limits::DEFAULT)?;
22//! let mut name = [0u8; 32];
23//! assert_eq!(p.decode_into("filename", &mut name)?, Some("caf\u{e9}.txt"));
24//! # Ok::<(), parse_net::ParseError>(())
25//! ```
26
27mod 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
48/// The longest physical line, CRLF excluded (RFC 5322 section 2.1.1).
49const HARD_LIMIT: usize = 998;
50/// The line length writers keep to where they can (RFC 5322 section 2.1.1).
51const SOFT_LIMIT: usize = 78;
52
53/// Skips CFWS at `v[i..]` (WSP, `CRLF WSP`, and comments, depth-limited);
54/// returns the index after it.
55pub(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
73/// A byte no header value may hold: every control but HTAB.
74const fn is_refused(b: u8) -> bool {
75    b != b'\t' && crate::scan::is_control(b)
76}
77
78/// Checks a header field value for writing: no control byte but HTAB, and at
79/// most 998 bytes (the physical line limit of RFC 5322 section 2.1.1, which the
80/// caller's `Name: ` also counts toward).
81///
82/// # Errors
83///
84/// [`ErrorKind::BareCr`], [`ErrorKind::LineFeed`], [`ErrorKind::Nul`],
85/// [`ErrorKind::Control`]; [`ErrorKind::LineTooLong`].
86///
87/// # Examples
88///
89/// ```
90/// use parse_net::{mail, ErrorKind};
91///
92/// assert!(mail::check_header_value(b"Hello there").is_ok());
93/// assert_eq!(mail::check_header_value(b"a\r\nBcc: x").unwrap_err().kind(), ErrorKind::BareCr);
94/// assert_eq!(mail::check_header_value(&[b'a'; 999]).unwrap_err().kind(), ErrorKind::LineTooLong);
95/// ```
96pub 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/// Whether `name` is an email header field name: one or more `ftext`
107/// (`%d33-57 / %d59-126`, RFC 5322 section 3.6.8).
108///
109/// # Examples
110///
111/// ```
112/// assert!(parse_net::mail::is_field_name(b"X-Mailer"));
113/// assert!(!parse_net::mail::is_field_name(b"Bad:Name") && !parse_net::mail::is_field_name(b""));
114/// ```
115#[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
120/// Validates a folded header field body and returns its pieces: the text
121/// between `CRLF WSP` folds, each CRLF removed and the WSP kept (RFC 5322
122/// section 2.2.3). Concatenated, the pieces are the unfolded value.
123///
124/// # Errors
125///
126/// [`ErrorKind::BareCr`] (a CR not followed by LF and WSP),
127/// [`ErrorKind::LineFeed`] (an LF without CR), [`ErrorKind::Nul`].
128///
129/// # Examples
130///
131/// ```
132/// use parse_net::mail;
133///
134/// let u = mail::unfold(b"a long\r\n subject")?;
135/// assert_eq!(u.iter().collect::<Vec<_>>(), [&b"a long"[..], b" subject"]);
136/// assert!(mail::unfold(b"a\r\nb").is_err());
137/// # Ok::<(), parse_net::ParseError>(())
138/// ```
139pub 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/// A validated folded field body.
161///
162/// # Examples
163///
164/// ```
165/// let u = parse_net::mail::unfold(b"x\r\n\ty")?;
166/// let joined: Vec<u8> = u.iter().flatten().copied().collect();
167/// assert_eq!(joined, b"x\ty");
168/// # Ok::<(), parse_net::ParseError>(())
169/// ```
170#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
171pub struct Unfolded<'a> {
172    value: &'a [u8],
173}
174
175impl<'a> Unfolded<'a> {
176    /// The pieces between folds, in order.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// let u = parse_net::mail::unfold(b"no folds here")?;
182    /// assert_eq!(u.iter().collect::<Vec<_>>(), [&b"no folds here"[..]]);
183    /// # Ok::<(), parse_net::ParseError>(())
184    /// ```
185    #[must_use]
186    pub fn iter(&self) -> UnfoldIter<'a> {
187        UnfoldIter {
188            rest: Some(self.value),
189        }
190    }
191}
192
193/// The pieces of an [`Unfolded`] value.
194///
195/// # Examples
196///
197/// ```
198/// let mut it = parse_net::mail::unfold(b"a\r\n b\r\n c")?.iter();
199/// assert_eq!(it.next(), Some(&b"a"[..]));
200/// assert_eq!(it.next(), Some(&b" b"[..]));
201/// assert_eq!(it.next(), Some(&b" c"[..]));
202/// assert_eq!(it.next(), None);
203/// # Ok::<(), parse_net::ParseError>(())
204/// ```
205#[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
228/// Writes `Name: value` with CRLF folds before whitespace so lines stay
229/// within 78 columns where the words allow, never creating a line of only
230/// whitespace and never one over 998 bytes. A space is added after the colon
231/// when `value` does not start with whitespace. No CRLF is written at the
232/// end.
233///
234/// # Errors
235///
236/// [`WriteError::NotToken`] for a name outside `ftext`;
237/// [`WriteError::Forbidden`] for a control byte other than HTAB in `value`;
238/// [`WriteError::OutOfRange`] for a word (with the whitespace that ends the
239/// value, after the last word) that cannot fit in 998 bytes;
240/// [`WriteError::Full`] (nothing written when the sink reports its room).
241///
242/// # Examples
243///
244/// ```
245/// use parse_net::{mail, SliceSink};
246///
247/// let mut buf = [0u8; 256];
248/// let mut out = SliceSink::new(&mut buf);
249/// let words = "word ".repeat(20);
250/// mail::fold("Subject", words.trim_end().as_bytes(), &mut out)?;
251/// let lines: Vec<&[u8]> = out.written().split(|&b| b == b'\n').collect();
252/// assert_eq!(lines.len(), 2);
253/// assert!(lines.iter().all(|l| l.len() <= 79));
254/// # Ok::<(), parse_net::WriteError>(())
255/// ```
256pub 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            // Trailing whitespace stays on the line it ends: a fold before
292            // it would leave a line of only whitespace. It counts toward
293            // the 998 bytes like any word.
294            if col + ws.len() > HARD_LIMIT {
295                return Err(WriteError::OutOfRange);
296            }
297            out.put(ws)?;
298            return Ok(());
299        }
300        // The writer's own space after the colon may fold too, but only
301        // when the word could not stay on the line at all.
302        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            // A run too long to start the next line leaves all but its last
307            // byte here; a fold needs only one.
308            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        // Found by the 0.7 fuzz run: whitespace after the last word was
418        // written past 998 bytes. `Subject: ` and 989 bytes fill the line.
419        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        // Earlier words fold away; the last line still counts its tail.
429        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}