Skip to main content

mail_parser/core/
message.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use std::{borrow::Cow, convert::TryInto};
8
9use crate::{
10    Address, AttachmentIterator, BodyPartIterator, DateTime, GetHeader, Header, HeaderForm,
11    HeaderName, HeaderValue, Message, MessageParser, MessagePart, PartType, Received,
12    decoders::html::{html_to_text, text_to_html},
13    parsers::{
14        MessageStream,
15        fields::thread::thread_name,
16        preview::{preview_html, preview_text},
17    },
18};
19
20impl<'x> Message<'x> {
21    /// Returns the root message part
22    pub fn root_part(&self) -> &MessagePart<'x> {
23        &self.parts[0]
24    }
25
26    /// Returns a parsed header.
27    pub fn header(&self, header: impl Into<HeaderName<'x>>) -> Option<&HeaderValue<'x>> {
28        self.parts[0].headers.header(header).map(|h| &h.value)
29    }
30
31    /// Removed a parsed header and returns its value.
32    pub fn remove_header(&mut self, header: impl Into<HeaderName<'x>>) -> Option<HeaderValue<'x>> {
33        let header = header.into();
34        let headers = &mut self.parts[0].headers;
35        headers
36            .iter()
37            .position(|h| h.name == header)
38            .map(|pos| headers.swap_remove(pos).value)
39    }
40
41    /// Returns the raw header.
42    pub fn header_raw(&self, header: impl Into<HeaderName<'x>>) -> Option<&str> {
43        self.parts[0].headers.header(header).and_then(|h| {
44            std::str::from_utf8(&self.raw_message[h.offset_start as usize..h.offset_end as usize])
45                .ok()
46        })
47    }
48
49    // Parse a header as a specific type.
50    pub fn header_as(
51        &self,
52        header: impl Into<HeaderName<'x>>,
53        form: HeaderForm,
54    ) -> Vec<HeaderValue<'_>> {
55        let header = header.into();
56        let mut results = Vec::new();
57        for header_ in &self.parts[0].headers {
58            if header_.name == header {
59                results.push(
60                    self.raw_message
61                        .get(header_.offset_start as usize..header_.offset_end as usize)
62                        .map_or(HeaderValue::Empty, |bytes| match form {
63                            HeaderForm::Raw => HeaderValue::Text(
64                                std::str::from_utf8(bytes).unwrap_or_default().trim().into(),
65                            ),
66                            HeaderForm::Text => MessageStream::new(bytes).parse_unstructured(),
67                            HeaderForm::Addresses => MessageStream::new(bytes).parse_address(),
68                            HeaderForm::GroupedAddresses => {
69                                MessageStream::new(bytes).parse_address()
70                            }
71                            HeaderForm::MessageIds => MessageStream::new(bytes).parse_id(),
72                            HeaderForm::Date => MessageStream::new(bytes).parse_date(),
73                            HeaderForm::URLs => MessageStream::new(bytes).parse_address(),
74                        }),
75                );
76            }
77        }
78
79        results
80    }
81
82    /// Returns an iterator over the RFC headers of this message.
83    pub fn headers(&self) -> &[Header<'x>] {
84        &self.parts[0].headers
85    }
86
87    /// Returns an iterator over the matching RFC headers of this message.
88    pub fn header_values(
89        &self,
90        name: impl Into<HeaderName<'x>>,
91    ) -> impl Iterator<Item = &HeaderValue<'x>> + Sync + Send {
92        let name = name.into();
93        self.parts[0].headers.iter().filter_map(move |header| {
94            if header.name == name {
95                Some(&header.value)
96            } else {
97                None
98            }
99        })
100    }
101
102    /// Returns all headers in raw format
103    pub fn headers_raw(&self) -> impl Iterator<Item = (&str, &str)> + Sync + Send {
104        self.parts[0].headers.iter().filter_map(move |header| {
105            Some((
106                header.name.as_str(),
107                std::str::from_utf8(
108                    &self.raw_message[header.offset_start as usize..header.offset_end as usize],
109                )
110                .ok()?,
111            ))
112        })
113    }
114
115    /// Returns the raw message
116    pub fn raw_message(&self) -> &[u8] {
117        let part = &self.parts[0];
118        self.raw_message
119            .get(part.offset_header as usize..part.offset_end as usize)
120            .unwrap_or_default()
121    }
122
123    /// Returns the BCC header field
124    pub fn bcc(&self) -> Option<&Address<'x>> {
125        self.parts[0]
126            .headers
127            .header_value(&HeaderName::Bcc)
128            .and_then(|a| a.as_address())
129    }
130
131    /// Returns the CC header field
132    pub fn cc(&self) -> Option<&Address<'x>> {
133        self.parts[0]
134            .headers
135            .header_value(&HeaderName::Cc)
136            .and_then(|a| a.as_address())
137    }
138
139    /// Returns all Comments header fields
140    pub fn comments(&self) -> &HeaderValue<'x> {
141        self.parts[0]
142            .headers
143            .header_value(&HeaderName::Comments)
144            .unwrap_or(&HeaderValue::Empty)
145    }
146
147    /// Returns the Date header field
148    pub fn date(&self) -> Option<&DateTime> {
149        self.parts[0]
150            .headers
151            .header_value(&HeaderName::Date)
152            .and_then(|header| header.as_datetime())
153    }
154
155    /// Returns the From header field
156    pub fn from(&self) -> Option<&Address<'x>> {
157        self.parts[0]
158            .headers
159            .header_value(&HeaderName::From)
160            .and_then(|a| a.as_address())
161    }
162
163    /// Returns all In-Reply-To header fields
164    pub fn in_reply_to(&self) -> &HeaderValue<'x> {
165        self.parts[0]
166            .headers
167            .header_value(&HeaderName::InReplyTo)
168            .unwrap_or(&HeaderValue::Empty)
169    }
170
171    /// Returns all Keywords header fields
172    pub fn keywords(&self) -> &HeaderValue<'x> {
173        self.parts[0]
174            .headers
175            .header_value(&HeaderName::Keywords)
176            .unwrap_or(&HeaderValue::Empty)
177    }
178
179    /// Returns the List-Archive header field
180    pub fn list_archive(&self) -> &HeaderValue<'x> {
181        self.parts[0]
182            .headers
183            .header_value(&HeaderName::ListArchive)
184            .unwrap_or(&HeaderValue::Empty)
185    }
186
187    /// Returns the List-Help header field
188    pub fn list_help(&self) -> &HeaderValue<'x> {
189        self.parts[0]
190            .headers
191            .header_value(&HeaderName::ListHelp)
192            .unwrap_or(&HeaderValue::Empty)
193    }
194
195    /// Returns the List-ID header field
196    pub fn list_id(&self) -> &HeaderValue<'x> {
197        self.parts[0]
198            .headers
199            .header_value(&HeaderName::ListId)
200            .unwrap_or(&HeaderValue::Empty)
201    }
202
203    /// Returns the List-Owner header field
204    pub fn list_owner(&self) -> &HeaderValue<'x> {
205        self.parts[0]
206            .headers
207            .header_value(&HeaderName::ListOwner)
208            .unwrap_or(&HeaderValue::Empty)
209    }
210
211    /// Returns the List-Post header field
212    pub fn list_post(&self) -> &HeaderValue<'x> {
213        self.parts[0]
214            .headers
215            .header_value(&HeaderName::ListPost)
216            .unwrap_or(&HeaderValue::Empty)
217    }
218
219    /// Returns the List-Subscribe header field
220    pub fn list_subscribe(&self) -> &HeaderValue<'x> {
221        self.parts[0]
222            .headers
223            .header_value(&HeaderName::ListSubscribe)
224            .unwrap_or(&HeaderValue::Empty)
225    }
226
227    /// Returns the List-Unsubscribe header field
228    pub fn list_unsubscribe(&self) -> &HeaderValue<'x> {
229        self.parts[0]
230            .headers
231            .header_value(&HeaderName::ListUnsubscribe)
232            .unwrap_or(&HeaderValue::Empty)
233    }
234
235    /// Returns the Message-ID header field
236    pub fn message_id(&self) -> Option<&str> {
237        self.parts[0]
238            .headers
239            .header_value(&HeaderName::MessageId)
240            .and_then(|header| header.as_text())
241    }
242
243    /// Returns the MIME-Version header field
244    pub fn mime_version(&self) -> &HeaderValue<'x> {
245        self.parts[0]
246            .headers
247            .header_value(&HeaderName::MimeVersion)
248            .unwrap_or(&HeaderValue::Empty)
249    }
250
251    /// Returns the last Received header field
252    pub fn received(&self) -> Option<&Received<'x>> {
253        self.parts[0]
254            .headers
255            .header_value(&HeaderName::Received)
256            .and_then(|header| header.as_received())
257    }
258
259    /// Returns an iterator over all Received header fields, in the order they
260    /// appear in the message.
261    pub fn received_all(&self) -> impl Iterator<Item = &Received<'x>> + Sync + Send {
262        self.header_values(HeaderName::Received)
263            .filter_map(|header| header.as_received())
264    }
265
266    /// Returns all References header fields
267    pub fn references(&self) -> &HeaderValue<'x> {
268        self.parts[0]
269            .headers
270            .header_value(&HeaderName::References)
271            .unwrap_or(&HeaderValue::Empty)
272    }
273
274    /// Returns the Reply-To header field
275    pub fn reply_to(&self) -> Option<&Address<'x>> {
276        self.parts[0]
277            .headers
278            .header_value(&HeaderName::ReplyTo)
279            .and_then(|a| a.as_address())
280    }
281
282    /// Returns the Resent-BCC header field
283    pub fn resent_bcc(&self) -> Option<&Address<'x>> {
284        self.parts[0]
285            .headers
286            .header_value(&HeaderName::ResentBcc)
287            .and_then(|a| a.as_address())
288    }
289
290    /// Returns the Resent-CC header field
291    pub fn resent_cc(&self) -> Option<&Address<'x>> {
292        self.parts[0]
293            .headers
294            .header_value(&HeaderName::ResentTo)
295            .and_then(|a| a.as_address())
296    }
297
298    /// Returns all Resent-Date header fields
299    pub fn resent_date(&self) -> &HeaderValue<'x> {
300        self.parts[0]
301            .headers
302            .header_value(&HeaderName::ResentDate)
303            .unwrap_or(&HeaderValue::Empty)
304    }
305
306    /// Returns the Resent-From header field
307    pub fn resent_from(&self) -> Option<&Address<'x>> {
308        self.parts[0]
309            .headers
310            .header_value(&HeaderName::ResentFrom)
311            .and_then(|a| a.as_address())
312    }
313
314    /// Returns all Resent-Message-ID header fields
315    pub fn resent_message_id(&self) -> &HeaderValue<'x> {
316        self.parts[0]
317            .headers
318            .header_value(&HeaderName::ResentMessageId)
319            .unwrap_or(&HeaderValue::Empty)
320    }
321
322    /// Returns the Sender header field
323    pub fn resent_sender(&self) -> Option<&Address<'x>> {
324        self.parts[0]
325            .headers
326            .header_value(&HeaderName::ResentSender)
327            .and_then(|a| a.as_address())
328    }
329
330    /// Returns the Resent-To header field
331    pub fn resent_to(&self) -> Option<&Address<'x>> {
332        self.parts[0]
333            .headers
334            .header_value(&HeaderName::ResentTo)
335            .and_then(|a| a.as_address())
336    }
337
338    /// Returns all Return-Path header fields
339    pub fn return_path(&self) -> &HeaderValue<'x> {
340        self.parts[0]
341            .headers
342            .header_value(&HeaderName::ReturnPath)
343            .unwrap_or(&HeaderValue::Empty)
344    }
345
346    /// Returns the return address from either the Return-Path
347    /// or From header fields
348    pub fn return_address(&self) -> Option<&str> {
349        match self.parts[0].headers.header_value(&HeaderName::ReturnPath) {
350            Some(HeaderValue::Text(text)) => Some(text.as_ref()),
351            Some(HeaderValue::TextList(text_list)) => text_list.last().map(|t| t.as_ref()),
352            _ => match self.parts[0].headers.header_value(&HeaderName::From) {
353                Some(HeaderValue::Address(addr)) => addr.first()?.address.as_deref(),
354                _ => None,
355            },
356        }
357    }
358
359    /// Returns the Sender header field
360    pub fn sender(&self) -> Option<&Address<'x>> {
361        self.parts[0]
362            .headers
363            .header_value(&HeaderName::Sender)
364            .and_then(|a| a.as_address())
365    }
366
367    /// Returns the Subject header field
368    pub fn subject(&self) -> Option<&str> {
369        self.parts[0]
370            .headers
371            .header_value(&HeaderName::Subject)
372            .and_then(|header| header.as_text())
373    }
374
375    /// Returns the message thread name or 'base subject' as defined in
376    /// [RFC 5957 - Internet Message Access Protocol - SORT and THREAD Extensions (Section 2.1)](https://datatracker.ietf.org/doc/html/rfc5256#section-2.1)
377    pub fn thread_name(&self) -> Option<&str> {
378        thread_name(self.subject()?).into()
379    }
380
381    /// Returns the To header field
382    pub fn to(&self) -> Option<&Address<'x>> {
383        self.parts[0]
384            .headers
385            .header_value(&HeaderName::To)
386            .and_then(|a| a.as_address())
387    }
388
389    /// Returns a preview of the message body
390    pub fn body_preview(&self, preview_len: usize) -> Option<Cow<'x, str>> {
391        if !self.text_body.is_empty() {
392            preview_text(self.body_text(0)?, preview_len).into()
393        } else if !self.html_body.is_empty() {
394            preview_html(self.body_html(0)?, preview_len).into()
395        } else {
396            None
397        }
398    }
399
400    /// Returns a message body part as text/plain
401    pub fn body_html(&'x self, pos: usize) -> Option<Cow<'x, str>> {
402        let part = self.parts.get(*self.html_body.get(pos)? as usize)?;
403        match &part.body {
404            PartType::Html(html) => Some(html.as_ref().into()),
405            PartType::Text(text) => Some(text_to_html(text.as_ref()).into()),
406            _ => None,
407        }
408    }
409
410    /// Returns a message body part as text/plain
411    pub fn body_text(&'x self, pos: usize) -> Option<Cow<'x, str>> {
412        let part = self.parts.get(*self.text_body.get(pos)? as usize)?;
413        match &part.body {
414            PartType::Text(text) => Some(text.as_ref().into()),
415            PartType::Html(html) => Some(html_to_text(html.as_ref()).into()),
416            _ => None,
417        }
418    }
419
420    /// Returns a message part by position
421    pub fn part(&self, pos: u32) -> Option<&MessagePart<'x>> {
422        self.parts.get(pos as usize)
423    }
424
425    /// Returns an inline HTML body part by position
426    pub fn html_part(&self, pos: u32) -> Option<&MessagePart<'x>> {
427        self.parts.get(*self.html_body.get(pos as usize)? as usize)
428    }
429
430    /// Returns an inline text body part by position
431    pub fn text_part(&self, pos: u32) -> Option<&MessagePart<'x>> {
432        self.parts.get(*self.text_body.get(pos as usize)? as usize)
433    }
434
435    /// Returns an attacment by position
436    pub fn attachment(&self, pos: u32) -> Option<&MessagePart<'x>> {
437        self.parts
438            .get(*self.attachments.get(pos as usize)? as usize)
439    }
440
441    /// Returns the number of plain text body parts
442    pub fn text_body_count(&self) -> usize {
443        self.text_body.len()
444    }
445
446    /// Returns the number of HTML body parts
447    pub fn html_body_count(&self) -> usize {
448        self.html_body.len()
449    }
450
451    /// Returns the number of attachments
452    pub fn attachment_count(&self) -> usize {
453        self.attachments.len()
454    }
455
456    /// Returns an Interator over the text body parts
457    pub fn text_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
458        BodyPartIterator::new(self, &self.text_body)
459    }
460
461    /// Returns an Interator over the HTML body parts
462    pub fn html_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
463        BodyPartIterator::new(self, &self.html_body)
464    }
465
466    /// Returns an Interator over the attachments
467    pub fn attachments(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
468        AttachmentIterator::new(self)
469    }
470
471    /// Returns an owned version of the message
472    pub fn into_owned(self) -> Message<'static> {
473        Message {
474            html_body: self.html_body,
475            text_body: self.text_body,
476            attachments: self.attachments,
477            parts: self.parts.into_iter().map(|p| p.into_owned()).collect(),
478            raw_message: self.raw_message.into_owned().into(),
479        }
480    }
481}
482
483impl<'x> TryInto<Message<'x>> for &'x [u8] {
484    type Error = ();
485
486    fn try_into(self) -> Result<Message<'x>, Self::Error> {
487        MessageParser::default().parse(self).ok_or(())
488    }
489}