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 b64;
29mod params;
30mod words;
31
32pub use addr::{
33    AddrSpec, Domain, LocalPart, Mailbox, MsgId, parse_addr_spec, parse_mailbox, parse_msg_id,
34    write_msg_id,
35};
36pub use params::{
37    Continuation, MimeParamIter, MimeParams, MimeValue, parse_mime_params, write_mime_param,
38};
39pub use words::{decode_words, encode_words, encode_words_b};
40
41pub use crate::MailDate;
42
43use crate::{
44    error::{ErrorKind, Result, WriteError, err},
45    scan::control_kind,
46    write::{CountSink, Sink, reserve},
47};
48
49/// The longest physical line, CRLF excluded (RFC 5322 section 2.1.1).
50const HARD_LIMIT: usize = 998;
51/// The line length writers keep to where they can (RFC 5322 section 2.1.1).
52const SOFT_LIMIT: usize = 78;
53
54/// Skips CFWS at `v[i..]` (WSP, `CRLF WSP`, and comments, depth-limited);
55/// returns the index after it.
56pub(super) fn cfws(v: &[u8], mut i: usize, limits: &crate::Limits) -> Result<usize> {
57    loop {
58        match v.get(i) {
59            Some(b' ' | b'\t') => i += 1,
60            Some(b'\r') => {
61                if v.get(i + 1) == Some(&b'\n') && matches!(v.get(i + 2), Some(b' ' | b'\t')) {
62                    i += 3;
63                } else {
64                    return err(ErrorKind::BareCr, i);
65                }
66            }
67            Some(b'\n') => return err(ErrorKind::LineFeed, i),
68            Some(b'(') => i = crate::text::comment_end_folded(v, i, limits)?.0,
69            _ => return Ok(i),
70        }
71    }
72}
73
74/// A byte no header value may hold: every control but HTAB.
75const fn is_refused(b: u8) -> bool {
76    b != b'\t' && crate::scan::is_control(b)
77}
78
79/// Checks a header field value for writing: no control byte but HTAB, and at
80/// most 998 bytes (the physical line limit of RFC 5322 section 2.1.1, which the
81/// caller's `Name: ` also counts toward).
82///
83/// # Errors
84///
85/// [`ErrorKind::BareCr`], [`ErrorKind::LineFeed`], [`ErrorKind::Nul`],
86/// [`ErrorKind::Control`]; [`ErrorKind::LineTooLong`].
87///
88/// # Examples
89///
90/// ```
91/// use parse_net::{mail, ErrorKind};
92///
93/// assert!(mail::check_header_value(b"Hello there").is_ok());
94/// assert_eq!(mail::check_header_value(b"a\r\nBcc: x").unwrap_err().kind(), ErrorKind::BareCr);
95/// assert_eq!(mail::check_header_value(&[b'a'; 999]).unwrap_err().kind(), ErrorKind::LineTooLong);
96/// ```
97pub fn check_header_value(value: &[u8]) -> Result<()> {
98    if let Some(at) = value.iter().position(|&b| is_refused(b)) {
99        return err(control_kind(value.get(at).copied().unwrap_or(0)), at);
100    }
101    if value.len() > HARD_LIMIT {
102        return err(ErrorKind::LineTooLong, HARD_LIMIT);
103    }
104    Ok(())
105}
106
107/// Whether `name` is an email header field name: one or more `ftext`
108/// (`%d33-57 / %d59-126`, RFC 5322 section 3.6.8).
109///
110/// # Examples
111///
112/// ```
113/// assert!(parse_net::mail::is_field_name(b"X-Mailer"));
114/// assert!(!parse_net::mail::is_field_name(b"Bad:Name") && !parse_net::mail::is_field_name(b""));
115/// ```
116#[must_use]
117pub fn is_field_name(name: &[u8]) -> bool {
118    !name.is_empty() && name.iter().all(|&b| matches!(b, 33..=57 | 59..=126))
119}
120
121/// Validates a folded header field body and returns its pieces: the text
122/// between `CRLF WSP` folds, each CRLF removed and the WSP kept (RFC 5322
123/// section 2.2.3). Concatenated, the pieces are the unfolded value.
124///
125/// # Errors
126///
127/// [`ErrorKind::BareCr`] (a CR not followed by LF and WSP),
128/// [`ErrorKind::LineFeed`] (an LF without CR), [`ErrorKind::Nul`].
129///
130/// # Examples
131///
132/// ```
133/// use parse_net::mail;
134///
135/// let u = mail::unfold(b"a long\r\n subject")?;
136/// assert_eq!(u.iter().collect::<Vec<_>>(), [&b"a long"[..], b" subject"]);
137/// assert!(mail::unfold(b"a\r\nb").is_err());
138/// # Ok::<(), parse_net::ParseError>(())
139/// ```
140pub fn unfold(value: &[u8]) -> Result<Unfolded<'_>> {
141    let mut i = 0usize;
142    while let Some(&b) = value.get(i) {
143        match b {
144            b'\r' => {
145                if value.get(i + 1) == Some(&b'\n')
146                    && matches!(value.get(i + 2), Some(b' ' | b'\t'))
147                {
148                    i += 3;
149                    continue;
150                }
151                return err(ErrorKind::BareCr, i);
152            }
153            b'\n' => return err(ErrorKind::LineFeed, i),
154            0 => return err(ErrorKind::Nul, i),
155            _ => i += 1,
156        }
157    }
158    Ok(Unfolded { value })
159}
160
161/// A validated folded field body.
162///
163/// # Examples
164///
165/// ```
166/// let u = parse_net::mail::unfold(b"x\r\n\ty")?;
167/// let joined: Vec<u8> = u.iter().flatten().copied().collect();
168/// assert_eq!(joined, b"x\ty");
169/// # Ok::<(), parse_net::ParseError>(())
170/// ```
171#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
172pub struct Unfolded<'a> {
173    value: &'a [u8],
174}
175
176impl<'a> Unfolded<'a> {
177    /// The pieces between folds, in order.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// let u = parse_net::mail::unfold(b"no folds here")?;
183    /// assert_eq!(u.iter().collect::<Vec<_>>(), [&b"no folds here"[..]]);
184    /// # Ok::<(), parse_net::ParseError>(())
185    /// ```
186    #[must_use]
187    pub fn iter(&self) -> UnfoldIter<'a> {
188        UnfoldIter {
189            rest: Some(self.value),
190        }
191    }
192}
193
194/// The pieces of an [`Unfolded`] value.
195///
196/// # Examples
197///
198/// ```
199/// let mut it = parse_net::mail::unfold(b"a\r\n b\r\n c")?.iter();
200/// assert_eq!(it.next(), Some(&b"a"[..]));
201/// assert_eq!(it.next(), Some(&b" b"[..]));
202/// assert_eq!(it.next(), Some(&b" c"[..]));
203/// assert_eq!(it.next(), None);
204/// # Ok::<(), parse_net::ParseError>(())
205/// ```
206#[derive(Clone, Debug)]
207pub struct UnfoldIter<'a> {
208    rest: Option<&'a [u8]>,
209}
210
211impl<'a> Iterator for UnfoldIter<'a> {
212    type Item = &'a [u8];
213
214    fn next(&mut self) -> Option<&'a [u8]> {
215        let rest = self.rest?;
216        match rest.iter().position(|&b| b == b'\r') {
217            Some(at) => {
218                self.rest = rest.get(at + 2..);
219                rest.get(..at)
220            }
221            None => {
222                self.rest = None;
223                Some(rest)
224            }
225        }
226    }
227}
228
229/// Writes `Name: value` with CRLF folds before whitespace so lines stay
230/// within 78 columns where the words allow, never creating a line of only
231/// whitespace and never one over 998 bytes. A space is added after the colon
232/// when `value` does not start with whitespace. No CRLF is written at the
233/// end.
234///
235/// # Errors
236///
237/// [`WriteError::NotToken`] for a name outside `ftext`;
238/// [`WriteError::Forbidden`] for a control byte other than HTAB in `value`;
239/// [`WriteError::OutOfRange`] for a word (with the whitespace that ends the
240/// value, after the last word) that cannot fit in 998 bytes;
241/// [`WriteError::Full`] (nothing written when the sink reports its room).
242///
243/// # Examples
244///
245/// ```
246/// use parse_net::{mail, SliceSink};
247///
248/// let mut buf = [0u8; 256];
249/// let mut out = SliceSink::new(&mut buf);
250/// let words = "word ".repeat(20);
251/// mail::fold("Subject", words.trim_end().as_bytes(), &mut out)?;
252/// let lines: Vec<&[u8]> = out.written().split(|&b| b == b'\n').collect();
253/// assert_eq!(lines.len(), 2);
254/// assert!(lines.iter().all(|l| l.len() <= 79));
255/// # Ok::<(), parse_net::WriteError>(())
256/// ```
257pub fn fold(name: &str, value: &[u8], out: &mut impl Sink) -> core::result::Result<(), WriteError> {
258    if !is_field_name(name.as_bytes()) {
259        return Err(WriteError::NotToken);
260    }
261    if let Some(at) = value.iter().position(|&b| is_refused(b)) {
262        return Err(WriteError::forbidden(at));
263    }
264    let mut count = CountSink::new();
265    put_fold(name.as_bytes(), value, &mut count)?;
266    reserve(out, count.count())?;
267    put_fold(name.as_bytes(), value, out)
268}
269
270fn put_fold(
271    name: &[u8],
272    value: &[u8],
273    out: &mut impl Sink,
274) -> core::result::Result<(), WriteError> {
275    out.put(name)?;
276    out.put(b":")?;
277    let mut col = name.len() + 1;
278    let mut first = true;
279    let mut rest = value;
280    loop {
281        let ws_len = rest
282            .iter()
283            .take_while(|&&b| b == b' ' || b == b'\t')
284            .count();
285        let (ws, tail) = rest.split_at(ws_len);
286        let word_len = tail
287            .iter()
288            .take_while(|&&b| b != b' ' && b != b'\t')
289            .count();
290        let (word, tail) = tail.split_at(word_len);
291        if word.is_empty() {
292            // Trailing whitespace stays on the line it ends: a fold before
293            // it would leave a line of only whitespace. It counts toward
294            // the 998 bytes like any word.
295            if col + ws.len() > HARD_LIMIT {
296                return Err(WriteError::OutOfRange);
297            }
298            out.put(ws)?;
299            return Ok(());
300        }
301        // The writer's own space after the colon may fold too, but only
302        // when the word could not stay on the line at all.
303        let own_space = first && ws.is_empty();
304        let mut gap: &[u8] = if own_space { b" " } else { ws };
305        let width = col + gap.len() + word.len();
306        if width > HARD_LIMIT || (width > SOFT_LIMIT && !own_space) {
307            // A run too long to start the next line leaves all but its last
308            // byte here; a fold needs only one.
309            if gap.len() + word.len() > HARD_LIMIT {
310                let (keep, last) = gap.split_at(gap.len() - 1);
311                if col + keep.len() > HARD_LIMIT {
312                    return Err(WriteError::OutOfRange);
313                }
314                out.put(keep)?;
315                gap = last;
316            }
317            out.put(b"\r\n")?;
318            col = 0;
319        }
320        if col + gap.len() + word.len() > HARD_LIMIT {
321            return Err(WriteError::OutOfRange);
322        }
323        out.put(gap)?;
324        out.put(word)?;
325        col += gap.len() + word.len();
326        first = false;
327        rest = tail;
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334    use std::{vec, vec::Vec};
335
336    #[test]
337    fn test_check_header_value_limits() {
338        assert!(check_header_value(&[b'a'; 998]).is_ok());
339        assert_eq!(
340            check_header_value(&[b'a'; 999]).unwrap_err().kind(),
341            ErrorKind::LineTooLong
342        );
343        assert_eq!(
344            check_header_value(b"a\nb").unwrap_err().kind(),
345            ErrorKind::LineFeed
346        );
347        assert_eq!(
348            check_header_value(b"a\x00b").unwrap_err().kind(),
349            ErrorKind::Nul
350        );
351        assert!(check_header_value(b"").is_ok());
352    }
353
354    #[test]
355    fn test_field_names() {
356        assert!(is_field_name(b"Subject") && is_field_name(b"!#$%&'*+-.^_`|~9"));
357        assert!(!is_field_name(b"A B") && !is_field_name(b"A:") && !is_field_name(b"\x7f"));
358    }
359
360    #[test]
361    fn test_unfold_edge_cases() {
362        let u = unfold(b"a\r\n\tb\r\n c").unwrap();
363        assert_eq!(u.iter().collect::<Vec<_>>(), vec![&b"a"[..], b"\tb", b" c"]);
364        assert_eq!(
365            unfold(b"").unwrap().iter().collect::<Vec<_>>(),
366            vec![&b""[..]]
367        );
368        assert_eq!(unfold(b"a\r").unwrap_err().kind(), ErrorKind::BareCr);
369        assert_eq!(unfold(b"a\r\n").unwrap_err().kind(), ErrorKind::BareCr);
370        assert_eq!(unfold(b"a\nb").unwrap_err().kind(), ErrorKind::LineFeed);
371    }
372
373    fn folded(name: &str, value: &[u8]) -> Vec<u8> {
374        let mut buf = vec![0u8; 4096];
375        let mut out = crate::SliceSink::new(&mut buf);
376        fold(name, value, &mut out).unwrap();
377        out.written().to_vec()
378    }
379
380    #[test]
381    fn test_fold_lines_and_round_trip() {
382        let value = "the quick brown fox jumps over the lazy dog ".repeat(5);
383        let value = value.trim_end().as_bytes();
384        let f = folded("Subject", value);
385        for line in f.split(|&b| b == b'\n') {
386            let line = line.strip_suffix(b"\r").unwrap_or(line);
387            assert!(line.len() <= 78, "{}", line.len());
388            assert!(!line.iter().all(|&b| b == b' '));
389        }
390        let body = f.strip_prefix(b"Subject: ").unwrap();
391        let back: Vec<u8> = unfold(body).unwrap().iter().flatten().copied().collect();
392        assert_eq!(back, value);
393        assert_eq!(folded("X", b" lead"), b"X: lead");
394        assert_eq!(folded("X", b"tab\tsep"), b"X: tab\tsep");
395        assert_eq!(folded("X", b""), b"X:");
396        assert_eq!(folded("X", b"a  "), b"X: a  ");
397    }
398
399    #[test]
400    fn test_fold_long_words() {
401        let word = vec![b'w'; 989];
402        let f = folded("Subject", &word);
403        assert_eq!(f.len(), 998);
404        let mut buf = vec![0u8; 4096];
405        let mut out = crate::SliceSink::new(&mut buf);
406        assert_eq!(
407            fold("Subject", &[b'w'; 1000], &mut out),
408            Err(WriteError::OutOfRange)
409        );
410        let mut two = vec![b'a'; 10];
411        two.push(b' ');
412        two.extend_from_slice(&[b'w'; 997]);
413        assert_eq!(fold("S", &two, &mut out), Ok(()));
414        let mut two = vec![b'a'; 10];
415        two.push(b' ');
416        two.extend_from_slice(&[b'w'; 998]);
417        assert_eq!(fold("S", &two, &mut out), Err(WriteError::OutOfRange));
418        // Found by the 0.7 fuzz run: whitespace after the last word was
419        // written past 998 bytes. `Subject: ` and 989 bytes fill the line.
420        let mut tail = vec![b'w'; 989];
421        tail.push(b'\t');
422        assert_eq!(
423            fold("Subject", &tail, &mut out),
424            Err(WriteError::OutOfRange)
425        );
426        let mut fits = vec![b'w'; 988];
427        fits.push(b'\t');
428        assert_eq!(folded("Subject", &fits).len(), 998);
429        // Earlier words fold away; the last line still counts its tail.
430        let mut many = b"a b ".to_vec();
431        many.extend_from_slice(&[b'w'; 997]);
432        many.extend_from_slice(b"  ");
433        assert_eq!(fold("S", &many, &mut out), Err(WriteError::OutOfRange));
434    }
435
436    #[test]
437    fn test_fold_refusals_write_nothing() {
438        let mut buf = [0u8; 8];
439        let mut out = crate::SliceSink::new(&mut buf);
440        assert_eq!(fold("A B", b"x", &mut out), Err(WriteError::NotToken));
441        assert_eq!(
442            fold("A", b"x\r\nBcc: y", &mut out),
443            Err(WriteError::Forbidden { at: 1 })
444        );
445        assert_eq!(
446            fold("A", b"too long for eight", &mut out),
447            Err(WriteError::Full)
448        );
449        assert!(out.is_empty());
450    }
451}