Skip to main content

mail_parser/parser/
mod.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7mod builder;
8mod classify;
9mod delimiter;
10mod header_block;
11
12#[cfg(test)]
13mod tests;
14
15pub(crate) use builder::Frame;
16
17use crate::{
18    HeaderForm, HeaderName, Message, MessageBuffers,
19    header_name::{FIRST_VENDOR, HeaderId, KNOWN_NAMES, OTHER_ID},
20    scan::Kernel,
21    store::MessageData,
22};
23use builder::Builder;
24use std::borrow::Cow;
25
26const FIRST_VENDOR_ID: usize = FIRST_VENDOR as usize;
27const CONTENT_TYPE: u16 = HeaderId::ContentType as u16;
28const CONTENT_DISPOSITION: u16 = HeaderId::ContentDisposition as u16;
29const CONTENT_TRANSFER_ENCODING: u16 = HeaderId::ContentTransferEncoding as u16;
30const DEFAULT_MAX_DEPTH: u32 = 64;
31const DEFAULT_MAX_PARTS: u32 = 10_000;
32const DEFAULT_MAX_ENCODED_NESTING: u32 = 3;
33
34const TEXT: [HeaderId; 5] = [
35    HeaderId::Subject,
36    HeaderId::Comments,
37    HeaderId::ContentDescription,
38    HeaderId::ContentLocation,
39    HeaderId::ContentTransferEncoding,
40];
41
42const ADDRESSES: [HeaderId; 18] = [
43    HeaderId::From,
44    HeaderId::To,
45    HeaderId::Cc,
46    HeaderId::Bcc,
47    HeaderId::ReplyTo,
48    HeaderId::Sender,
49    HeaderId::ResentTo,
50    HeaderId::ResentFrom,
51    HeaderId::ResentBcc,
52    HeaderId::ResentCc,
53    HeaderId::ResentSender,
54    HeaderId::ListArchive,
55    HeaderId::ListHelp,
56    HeaderId::ListId,
57    HeaderId::ListOwner,
58    HeaderId::ListPost,
59    HeaderId::ListSubscribe,
60    HeaderId::ListUnsubscribe,
61];
62
63const DATES: [HeaderId; 2] = [HeaderId::Date, HeaderId::ResentDate];
64
65const MESSAGE_IDS: [HeaderId; 6] = [
66    HeaderId::MessageId,
67    HeaderId::References,
68    HeaderId::InReplyTo,
69    HeaderId::ReturnPath,
70    HeaderId::ContentId,
71    HeaderId::ResentMessageId,
72];
73
74const COMMA_LISTS: [HeaderId; 2] = [HeaderId::Keywords, HeaderId::ContentLanguage];
75
76const CONTENT_TYPES: [HeaderId; 2] = [HeaderId::ContentType, HeaderId::ContentDisposition];
77
78const fn assign(
79    mut forms: [HeaderForm; KNOWN_NAMES],
80    ids: &[HeaderId],
81    form: HeaderForm,
82) -> [HeaderForm; KNOWN_NAMES] {
83    let mut index = 0;
84    while index < ids.len() {
85        forms[ids[index] as usize] = form;
86        index += 1;
87    }
88    forms
89}
90
91const DEFAULT_FORMS: [HeaderForm; KNOWN_NAMES] = {
92    let forms = [HeaderForm::Raw; KNOWN_NAMES];
93    let forms = assign(forms, &TEXT, HeaderForm::Text);
94    let forms = assign(forms, &ADDRESSES, HeaderForm::Addresses);
95    let forms = assign(forms, &DATES, HeaderForm::Date);
96    let forms = assign(forms, &MESSAGE_IDS, HeaderForm::MessageIds);
97    let forms = assign(forms, &COMMA_LISTS, HeaderForm::CommaList);
98    let forms = assign(forms, &[HeaderId::Received], HeaderForm::Received);
99    assign(forms, &CONTENT_TYPES, HeaderForm::ContentType)
100};
101
102/// RFC 5322 message parser and its configuration.
103///
104/// Build a parser once and reuse it; it is `Send + Sync`. The default
105/// configuration parses the header fields that 0.11 parsed into structured
106/// values the same way and keeps every other field raw.
107///
108/// ```
109/// use mail_parser::{HeaderForm, HeaderName, MessageParser};
110///
111/// let parser = MessageParser::new()
112///     .header(HeaderName::Received, HeaderForm::Raw)
113///     .header("X-Sender", HeaderForm::Addresses)
114///     .max_parts(1_000);
115/// let message = parser
116///     .parse(b"X-Sender: <sender@example.com>\r\n\r\nbody")
117///     .expect("message");
118/// let sender = message.headers().value("x-sender").and_then(|value| value.as_address());
119/// let address = sender.and_then(|list| list.first()).and_then(|mailbox| mailbox.address());
120/// assert_eq!(address, Some("sender@example.com"));
121/// ```
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct MessageParser {
124    forms: [HeaderForm; KNOWN_NAMES],
125    configured_vendor: Vec<u16>,
126    custom: Vec<(Box<str>, HeaderForm)>,
127    unknown: HeaderForm,
128    max_depth: u32,
129    max_parts: u32,
130    max_encoded_nesting: u32,
131    kernel: Kernel,
132}
133
134impl Default for MessageParser {
135    fn default() -> Self {
136        MessageParser::new()
137    }
138}
139
140impl MessageParser {
141    /// A parser with the default header table: every header 0.11 parsed
142    /// into a structured value is parsed the same way, every other header is
143    /// kept raw. Limits: nesting depth 64, 10,000 parts, 3 levels of
144    /// transfer-encoded nested messages.
145    pub fn new() -> Self {
146        MessageParser {
147            forms: DEFAULT_FORMS,
148            configured_vendor: Vec::new(),
149            custom: Vec::new(),
150            unknown: HeaderForm::Raw,
151            max_depth: DEFAULT_MAX_DEPTH,
152            max_parts: DEFAULT_MAX_PARTS,
153            max_encoded_nesting: DEFAULT_MAX_ENCODED_NESTING,
154            kernel: Kernel::best(),
155        }
156    }
157
158    /// Parses the header `name` as `form`, in place of its default form.
159    /// Content-Type, Content-Disposition and Content-Transfer-Encoding
160    /// always keep their MIME form. Names compare ASCII case-insensitively,
161    /// and a [`HeaderName::Other`] spelled like a known name configures that
162    /// name; any other name is added to a short list checked for every
163    /// unknown name.
164    pub fn header(mut self, name: impl Into<HeaderName<'static>>, form: HeaderForm) -> Self {
165        let name = name.into();
166        match (name.resolved_id(), name) {
167            (CONTENT_TYPE | CONTENT_DISPOSITION | CONTENT_TRANSFER_ENCODING, _) => {}
168            (OTHER_ID, HeaderName::Other(name)) if name.is_empty() => {}
169            (OTHER_ID, HeaderName::Other(name)) => {
170                match self
171                    .custom
172                    .iter_mut()
173                    .find(|(custom, _)| custom.eq_ignore_ascii_case(&name))
174                {
175                    Some((_, custom_form)) => *custom_form = form,
176                    None => self.custom.push((name.into_owned().into_boxed_str(), form)),
177                }
178            }
179            (id, _) => {
180                if let Some(slot) = self.forms.get_mut(usize::from(id)) {
181                    *slot = form;
182                    if id >= FIRST_VENDOR && !self.configured_vendor.contains(&id) {
183                        self.configured_vendor.push(id);
184                    }
185                }
186            }
187        }
188        self
189    }
190
191    /// The form used for header names that are neither known nor
192    /// registered with [`MessageParser::header`]; [`HeaderForm::Raw`] by
193    /// default.
194    ///
195    /// It also applies to the vendor names that are known since 1.0 but were
196    /// unknown to 0.11 (from [`HeaderName::XOriginalTo`] on: X-Mailer,
197    /// User-Agent, X-Priority, the X-MS-Exchange, X-Google and X-GitHub
198    /// fields, ...), except those registered with `header`, whatever the
199    /// order of the calls.
200    pub fn unknown_headers(mut self, form: HeaderForm) -> Self {
201        self.unknown = form;
202        let vendor_forms = self.forms.get_mut(FIRST_VENDOR_ID..).unwrap_or_default();
203        for (slot, id) in vendor_forms.iter_mut().zip(FIRST_VENDOR..) {
204            if !self.configured_vendor.contains(&id) {
205                *slot = form;
206            }
207        }
208        self
209    }
210
211    /// Maximum number of nested containers (multiparts and messages), the
212    /// root message included: with the default of 64, a part can be nested
213    /// in at most 63 containers below the root message.
214    pub fn max_depth(mut self, depth: usize) -> Self {
215        self.max_depth = u32::try_from(depth).unwrap_or(u32::MAX).max(1);
216        self
217    }
218
219    /// Maximum number of parts; the content past the limit stays unparsed
220    /// in the part flagged [`PartFlags::LIMIT_REACHED`](crate::PartFlags::LIMIT_REACHED).
221    pub fn max_parts(mut self, parts: usize) -> Self {
222        self.max_parts = u32::try_from(parts).unwrap_or(u32::MAX).max(1);
223        self
224    }
225
226    /// Maximum nesting of transfer-encoded `message/rfc822` parts that are
227    /// decoded and parsed; 0 leaves them as binary parts.
228    pub fn max_encoded_nesting(mut self, levels: usize) -> Self {
229        self.max_encoded_nesting = u32::try_from(levels).unwrap_or(u32::MAX);
230        self
231    }
232
233    #[cfg(test)]
234    pub(crate) fn kernel(mut self, kernel: Kernel) -> Self {
235        self.kernel = kernel;
236        self
237    }
238
239    pub(crate) fn known_form(&self, id: u16) -> HeaderForm {
240        self.forms.get(id as usize).copied().unwrap_or(self.unknown)
241    }
242
243    pub(crate) fn other_form(&self, name: &[u8]) -> HeaderForm {
244        self.custom
245            .iter()
246            .find(|(custom, _)| custom.as_bytes().eq_ignore_ascii_case(name))
247            .map_or(self.unknown, |(_, form)| *form)
248    }
249
250    /// Parses a message that borrows `raw`. Returns `None` when the input
251    /// has no header and no blank line, or is 4 GiB or larger.
252    pub fn parse<'x>(&self, raw: &'x (impl AsRef<[u8]> + ?Sized)) -> Option<Message<'x>> {
253        let mut message = Message::new(Cow::Borrowed(raw.as_ref()), MessageData::default());
254        self.fill(&mut message, false).then_some(message)
255    }
256
257    /// Parses a message and takes ownership of its bytes, giving a
258    /// `Message<'static>` that can be moved to another task.
259    pub fn parse_owned(&self, raw: Vec<u8>) -> Option<Message<'static>> {
260        let mut message = Message::new(Cow::Owned(raw), MessageData::default());
261        self.fill(&mut message, false).then_some(message)
262    }
263
264    /// Parses only the root header block, for callers that do not need the
265    /// MIME structure (DKIM verification, header indexing).
266    ///
267    /// The message has a single part, the root part, which covers the whole
268    /// body and keeps the kind its Content-Type gives (a multipart root has
269    /// no children). No part is in a body list or in the attachments:
270    /// [`crate::MessageRef::has_attachments`] is false, and a root that is
271    /// not a multipart is reported by [`crate::MessageRef::other_parts`].
272    pub fn parse_headers<'x>(&self, raw: &'x (impl AsRef<[u8]> + ?Sized)) -> Option<Message<'x>> {
273        let mut message = Message::new(Cow::Borrowed(raw.as_ref()), MessageData::default());
274        self.fill(&mut message, true).then_some(message)
275    }
276
277    /// Parses a message into the storage held by `buffers`, which a
278    /// previous message gave back with [`Message::into_buffers`]; once the
279    /// storage fits the messages, parsing allocates nothing. See
280    /// [`MessageBuffers`] for a loop.
281    ///
282    /// The message takes the storage out of `buffers`, which stay usable:
283    /// when the input does not parse, the storage is dropped and the next
284    /// parse allocates.
285    pub fn parse_with<'x>(
286        &self,
287        raw: &'x (impl AsRef<[u8]> + ?Sized),
288        buffers: &mut MessageBuffers,
289    ) -> Option<Message<'x>> {
290        let mut message = Message::new(
291            Cow::Borrowed(raw.as_ref()),
292            std::mem::take(&mut buffers.data),
293        );
294        message.split_mut().1.clear();
295        self.fill(&mut message, false).then_some(message)
296    }
297
298    fn fill(&self, message: &mut Message<'_>, headers_only: bool) -> bool {
299        let (raw, data) = message.split_mut();
300        u32::try_from(raw.len()).is_ok() && Builder::new(self, data, headers_only).run(raw)
301    }
302
303    pub(crate) fn scan_kernel(&self) -> Kernel {
304        self.kernel
305    }
306
307    pub(crate) fn limits(&self) -> (u32, u32, u32) {
308        (self.max_depth, self.max_parts, self.max_encoded_nesting)
309    }
310}