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 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
52/// The longest physical line, CRLF excluded (RFC 5322 section 2.1.1).
53const HARD_LIMIT: usize = 998;
54/// The line length writers keep to where they can (RFC 5322 section 2.1.1).
55const SOFT_LIMIT: usize = 78;
56
57/// Skips CFWS at `v[i..]` (WSP, `CRLF WSP`, and comments, depth-limited);
58/// returns the index after it.
59pub(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
77/// A byte no header value may hold: every control but HTAB.
78const fn is_refused(b: u8) -> bool {
79    b != b'\t' && crate::scan::is_control(b)
80}
81
82/// Checks a header field value for writing: no control byte but HTAB, and at
83/// most 998 bytes (the physical line limit of RFC 5322 section 2.1.1, which the
84/// caller's `Name: ` also counts toward).
85///
86/// # Errors
87///
88/// [`ErrorKind::BareCr`], [`ErrorKind::LineFeed`], [`ErrorKind::Nul`],
89/// [`ErrorKind::Control`]; [`ErrorKind::LineTooLong`].
90///
91/// # Examples
92///
93/// ```
94/// use parse_net::{mail, ErrorKind};
95///
96/// assert!(mail::check_header_value(b"Hello there").is_ok());
97/// assert_eq!(mail::check_header_value(b"a\r\nBcc: x").unwrap_err().kind(), ErrorKind::BareCr);
98/// assert_eq!(mail::check_header_value(&[b'a'; 999]).unwrap_err().kind(), ErrorKind::LineTooLong);
99/// ```
100pub 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/// Whether `name` is an email header field name: one or more `ftext`
111/// (`%d33-57 / %d59-126`, RFC 5322 section 3.6.8).
112///
113/// # Examples
114///
115/// ```
116/// assert!(parse_net::mail::is_field_name(b"X-Mailer"));
117/// assert!(!parse_net::mail::is_field_name(b"Bad:Name") && !parse_net::mail::is_field_name(b""));
118/// ```
119#[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
124/// Validates a folded header field body and returns its pieces: the text
125/// between `CRLF WSP` folds, each CRLF removed and the WSP kept (RFC 5322
126/// section 2.2.3). Concatenated, the pieces are the unfolded value.
127///
128/// # Errors
129///
130/// [`ErrorKind::BareCr`] (a CR not followed by LF and WSP),
131/// [`ErrorKind::LineFeed`] (an LF without CR), [`ErrorKind::Nul`].
132///
133/// # Examples
134///
135/// ```
136/// use parse_net::mail;
137///
138/// let u = mail::unfold(b"a long\r\n subject")?;
139/// assert_eq!(u.iter().collect::<Vec<_>>(), [&b"a long"[..], b" subject"]);
140/// assert!(mail::unfold(b"a\r\nb").is_err());
141/// # Ok::<(), parse_net::ParseError>(())
142/// ```
143pub 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/// A validated folded field body.
165///
166/// # Examples
167///
168/// ```
169/// let u = parse_net::mail::unfold(b"x\r\n\ty")?;
170/// let joined: Vec<u8> = u.iter().flatten().copied().collect();
171/// assert_eq!(joined, b"x\ty");
172/// # Ok::<(), parse_net::ParseError>(())
173/// ```
174#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
175pub struct Unfolded<'a> {
176    value: &'a [u8],
177}
178
179impl<'a> Unfolded<'a> {
180    /// The pieces between folds, in order.
181    ///
182    /// # Examples
183    ///
184    /// ```
185    /// let u = parse_net::mail::unfold(b"no folds here")?;
186    /// assert_eq!(u.iter().collect::<Vec<_>>(), [&b"no folds here"[..]]);
187    /// # Ok::<(), parse_net::ParseError>(())
188    /// ```
189    #[must_use]
190    pub fn iter(&self) -> UnfoldIter<'a> {
191        UnfoldIter {
192            rest: Some(self.value),
193        }
194    }
195}
196
197/// The pieces of an [`Unfolded`] value.
198///
199/// # Examples
200///
201/// ```
202/// let mut it = parse_net::mail::unfold(b"a\r\n b\r\n c")?.iter();
203/// assert_eq!(it.next(), Some(&b"a"[..]));
204/// assert_eq!(it.next(), Some(&b" b"[..]));
205/// assert_eq!(it.next(), Some(&b" c"[..]));
206/// assert_eq!(it.next(), None);
207/// # Ok::<(), parse_net::ParseError>(())
208/// ```
209#[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
232/// Writes `Name: value` with CRLF folds before whitespace so lines stay
233/// within 78 columns where the words allow, never creating a line of only
234/// whitespace and never one over 998 bytes. A space is added after the colon
235/// when `value` does not start with whitespace. No CRLF is written at the
236/// end.
237///
238/// # Errors
239///
240/// [`WriteError::NotToken`] for a name outside `ftext`;
241/// [`WriteError::Forbidden`] for a control byte other than HTAB in `value`;
242/// [`WriteError::OutOfRange`] for a word (with the whitespace that ends the
243/// value, after the last word) that cannot fit in 998 bytes;
244/// [`WriteError::Full`] (nothing written when the sink reports its room).
245///
246/// # Examples
247///
248/// ```
249/// use parse_net::{mail, SliceSink};
250///
251/// let mut buf = [0u8; 256];
252/// let mut out = SliceSink::new(&mut buf);
253/// let words = "word ".repeat(20);
254/// mail::fold("Subject", words.trim_end().as_bytes(), &mut out)?;
255/// let lines: Vec<&[u8]> = out.written().split(|&b| b == b'\n').collect();
256/// assert_eq!(lines.len(), 2);
257/// assert!(lines.iter().all(|l| l.len() <= 79));
258/// # Ok::<(), parse_net::WriteError>(())
259/// ```
260pub 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            // Trailing whitespace stays on the line it ends: a fold before
296            // it would leave a line of only whitespace. It counts toward
297            // the 998 bytes like any word.
298            if col + ws.len() > HARD_LIMIT {
299                return Err(WriteError::OutOfRange);
300            }
301            out.put(ws)?;
302            return Ok(());
303        }
304        // The writer's own space after the colon may fold too, but only
305        // when the word could not stay on the line at all.
306        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            // A run too long to start the next line leaves all but its last
311            // byte here; a fold needs only one.
312            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        // Found by the 0.7 fuzz run: whitespace after the last word was
422        // written past 998 bytes. `Subject: ` and 989 bytes fill the line.
423        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        // Earlier words fold away; the last line still counts its tail.
433        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}