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 all BCC header fields, permitted by [4.5.3 Obsolete Destination Address Fields](https://www.rfc-editor.org/info/rfc5322/#section-4.5.3)
132    pub fn all_bcc(&self) -> impl Iterator<Item = &Address<'x>> {
133        self.header_values(HeaderName::Bcc)
134            .filter_map(|a| a.as_address())
135    }
136
137    /// Returns the CC header field
138    pub fn cc(&self) -> Option<&Address<'x>> {
139        self.parts[0]
140            .headers
141            .header_value(&HeaderName::Cc)
142            .and_then(|a| a.as_address())
143    }
144
145    /// Returns all CC header fields, permitted by [4.5.3 Obsolete Destination Address Fields](https://www.rfc-editor.org/info/rfc5322/#section-4.5.3)
146    pub fn all_cc(&self) -> impl Iterator<Item = &Address<'x>> {
147        self.header_values(HeaderName::Cc)
148            .filter_map(|a| a.as_address())
149    }
150
151    /// Returns all Comments header fields
152    pub fn comments(&self) -> &HeaderValue<'x> {
153        self.parts[0]
154            .headers
155            .header_value(&HeaderName::Comments)
156            .unwrap_or(&HeaderValue::Empty)
157    }
158
159    /// Returns the Date header field
160    pub fn date(&self) -> Option<&DateTime> {
161        self.parts[0]
162            .headers
163            .header_value(&HeaderName::Date)
164            .and_then(|header| header.as_datetime())
165    }
166
167    /// Returns the From header field
168    pub fn from(&self) -> Option<&Address<'x>> {
169        self.parts[0]
170            .headers
171            .header_value(&HeaderName::From)
172            .and_then(|a| a.as_address())
173    }
174
175    /// Returns all In-Reply-To header fields
176    pub fn in_reply_to(&self) -> &HeaderValue<'x> {
177        self.parts[0]
178            .headers
179            .header_value(&HeaderName::InReplyTo)
180            .unwrap_or(&HeaderValue::Empty)
181    }
182
183    /// Returns all Keywords header fields
184    pub fn keywords(&self) -> &HeaderValue<'x> {
185        self.parts[0]
186            .headers
187            .header_value(&HeaderName::Keywords)
188            .unwrap_or(&HeaderValue::Empty)
189    }
190
191    /// Returns the List-Archive header field
192    pub fn list_archive(&self) -> &HeaderValue<'x> {
193        self.parts[0]
194            .headers
195            .header_value(&HeaderName::ListArchive)
196            .unwrap_or(&HeaderValue::Empty)
197    }
198
199    /// Returns the List-Help header field
200    pub fn list_help(&self) -> &HeaderValue<'x> {
201        self.parts[0]
202            .headers
203            .header_value(&HeaderName::ListHelp)
204            .unwrap_or(&HeaderValue::Empty)
205    }
206
207    /// Returns the List-ID header field
208    pub fn list_id(&self) -> &HeaderValue<'x> {
209        self.parts[0]
210            .headers
211            .header_value(&HeaderName::ListId)
212            .unwrap_or(&HeaderValue::Empty)
213    }
214
215    /// Returns the List-Owner header field
216    pub fn list_owner(&self) -> &HeaderValue<'x> {
217        self.parts[0]
218            .headers
219            .header_value(&HeaderName::ListOwner)
220            .unwrap_or(&HeaderValue::Empty)
221    }
222
223    /// Returns the List-Post header field
224    pub fn list_post(&self) -> &HeaderValue<'x> {
225        self.parts[0]
226            .headers
227            .header_value(&HeaderName::ListPost)
228            .unwrap_or(&HeaderValue::Empty)
229    }
230
231    /// Returns the List-Subscribe header field
232    pub fn list_subscribe(&self) -> &HeaderValue<'x> {
233        self.parts[0]
234            .headers
235            .header_value(&HeaderName::ListSubscribe)
236            .unwrap_or(&HeaderValue::Empty)
237    }
238
239    /// Returns the List-Unsubscribe header field
240    pub fn list_unsubscribe(&self) -> &HeaderValue<'x> {
241        self.parts[0]
242            .headers
243            .header_value(&HeaderName::ListUnsubscribe)
244            .unwrap_or(&HeaderValue::Empty)
245    }
246
247    /// Returns the Message-ID header field
248    pub fn message_id(&self) -> Option<&str> {
249        self.parts[0]
250            .headers
251            .header_value(&HeaderName::MessageId)
252            .and_then(|header| header.as_text())
253    }
254
255    /// Returns the MIME-Version header field
256    pub fn mime_version(&self) -> &HeaderValue<'x> {
257        self.parts[0]
258            .headers
259            .header_value(&HeaderName::MimeVersion)
260            .unwrap_or(&HeaderValue::Empty)
261    }
262
263    /// Returns the last Received header field
264    pub fn received(&self) -> Option<&Received<'x>> {
265        self.parts[0]
266            .headers
267            .header_value(&HeaderName::Received)
268            .and_then(|header| header.as_received())
269    }
270
271    /// Returns an iterator over all Received header fields, in the order they
272    /// appear in the message.
273    pub fn received_all(&self) -> impl Iterator<Item = &Received<'x>> + Sync + Send {
274        self.header_values(HeaderName::Received)
275            .filter_map(|header| header.as_received())
276    }
277
278    /// Returns all References header fields
279    pub fn references(&self) -> &HeaderValue<'x> {
280        self.parts[0]
281            .headers
282            .header_value(&HeaderName::References)
283            .unwrap_or(&HeaderValue::Empty)
284    }
285
286    /// Returns the Reply-To header field
287    pub fn reply_to(&self) -> Option<&Address<'x>> {
288        self.parts[0]
289            .headers
290            .header_value(&HeaderName::ReplyTo)
291            .and_then(|a| a.as_address())
292    }
293
294    /// Returns the Resent-BCC header field
295    pub fn resent_bcc(&self) -> Option<&Address<'x>> {
296        self.parts[0]
297            .headers
298            .header_value(&HeaderName::ResentBcc)
299            .and_then(|a| a.as_address())
300    }
301
302    /// Returns the Resent-CC header field
303    pub fn resent_cc(&self) -> Option<&Address<'x>> {
304        self.parts[0]
305            .headers
306            .header_value(&HeaderName::ResentTo)
307            .and_then(|a| a.as_address())
308    }
309
310    /// Returns all Resent-Date header fields
311    pub fn resent_date(&self) -> &HeaderValue<'x> {
312        self.parts[0]
313            .headers
314            .header_value(&HeaderName::ResentDate)
315            .unwrap_or(&HeaderValue::Empty)
316    }
317
318    /// Returns the Resent-From header field
319    pub fn resent_from(&self) -> Option<&Address<'x>> {
320        self.parts[0]
321            .headers
322            .header_value(&HeaderName::ResentFrom)
323            .and_then(|a| a.as_address())
324    }
325
326    /// Returns all Resent-Message-ID header fields
327    pub fn resent_message_id(&self) -> &HeaderValue<'x> {
328        self.parts[0]
329            .headers
330            .header_value(&HeaderName::ResentMessageId)
331            .unwrap_or(&HeaderValue::Empty)
332    }
333
334    /// Returns the Sender header field
335    pub fn resent_sender(&self) -> Option<&Address<'x>> {
336        self.parts[0]
337            .headers
338            .header_value(&HeaderName::ResentSender)
339            .and_then(|a| a.as_address())
340    }
341
342    /// Returns the Resent-To header field
343    pub fn resent_to(&self) -> Option<&Address<'x>> {
344        self.parts[0]
345            .headers
346            .header_value(&HeaderName::ResentTo)
347            .and_then(|a| a.as_address())
348    }
349
350    /// Returns all Return-Path header fields
351    pub fn return_path(&self) -> &HeaderValue<'x> {
352        self.parts[0]
353            .headers
354            .header_value(&HeaderName::ReturnPath)
355            .unwrap_or(&HeaderValue::Empty)
356    }
357
358    /// Returns the return address from either the Return-Path
359    /// or From header fields
360    pub fn return_address(&self) -> Option<&str> {
361        match self.parts[0].headers.header_value(&HeaderName::ReturnPath) {
362            Some(HeaderValue::Text(text)) => Some(text.as_ref()),
363            Some(HeaderValue::TextList(text_list)) => text_list.last().map(|t| t.as_ref()),
364            _ => match self.parts[0].headers.header_value(&HeaderName::From) {
365                Some(HeaderValue::Address(addr)) => addr.first()?.address.as_deref(),
366                _ => None,
367            },
368        }
369    }
370
371    /// Returns the Sender header field
372    pub fn sender(&self) -> Option<&Address<'x>> {
373        self.parts[0]
374            .headers
375            .header_value(&HeaderName::Sender)
376            .and_then(|a| a.as_address())
377    }
378
379    /// Returns the Subject header field
380    pub fn subject(&self) -> Option<&str> {
381        self.parts[0]
382            .headers
383            .header_value(&HeaderName::Subject)
384            .and_then(|header| header.as_text())
385    }
386
387    /// Returns the message thread name or 'base subject' as defined in
388    /// [RFC 5957 - Internet Message Access Protocol - SORT and THREAD Extensions (Section 2.1)](https://datatracker.ietf.org/doc/html/rfc5256#section-2.1)
389    pub fn thread_name(&self) -> Option<&str> {
390        thread_name(self.subject()?).into()
391    }
392
393    /// Returns the To header field
394    pub fn to(&self) -> Option<&Address<'x>> {
395        self.parts[0]
396            .headers
397            .header_value(&HeaderName::To)
398            .and_then(|a| a.as_address())
399    }
400
401    /// Returns all To header fields, permitted by [4.5.3 Obsolete Destination Address Fields](https://www.rfc-editor.org/info/rfc5322/#section-4.5.3)
402    pub fn all_to(&self) -> impl Iterator<Item = &Address<'x>> {
403        self.header_values(HeaderName::To)
404            .filter_map(|a| a.as_address())
405    }
406
407    /// Returns a preview of the message body
408    pub fn body_preview(&self, preview_len: usize) -> Option<Cow<'x, str>> {
409        if !self.text_body.is_empty() {
410            preview_text(self.body_text(0)?, preview_len).into()
411        } else if !self.html_body.is_empty() {
412            preview_html(self.body_html(0)?, preview_len).into()
413        } else {
414            None
415        }
416    }
417
418    /// Returns a message body part as text/plain
419    pub fn body_html(&'x self, pos: usize) -> Option<Cow<'x, str>> {
420        let part = self.parts.get(*self.html_body.get(pos)? as usize)?;
421        match &part.body {
422            PartType::Html(html) => Some(html.as_ref().into()),
423            PartType::Text(text) => Some(text_to_html(text.as_ref()).into()),
424            _ => None,
425        }
426    }
427
428    /// Returns a message body part as text/plain
429    pub fn body_text(&'x self, pos: usize) -> Option<Cow<'x, str>> {
430        let part = self.parts.get(*self.text_body.get(pos)? as usize)?;
431        match &part.body {
432            PartType::Text(text) => Some(text.as_ref().into()),
433            PartType::Html(html) => Some(html_to_text(html.as_ref()).into()),
434            _ => None,
435        }
436    }
437
438    /// Returns a message part by position
439    pub fn part(&self, pos: u32) -> Option<&MessagePart<'x>> {
440        self.parts.get(pos as usize)
441    }
442
443    /// Returns an inline HTML body part by position
444    pub fn html_part(&self, pos: u32) -> Option<&MessagePart<'x>> {
445        self.parts.get(*self.html_body.get(pos as usize)? as usize)
446    }
447
448    /// Returns an inline text body part by position
449    pub fn text_part(&self, pos: u32) -> Option<&MessagePart<'x>> {
450        self.parts.get(*self.text_body.get(pos as usize)? as usize)
451    }
452
453    /// Returns an attacment by position
454    pub fn attachment(&self, pos: u32) -> Option<&MessagePart<'x>> {
455        self.parts
456            .get(*self.attachments.get(pos as usize)? as usize)
457    }
458
459    /// Returns the number of plain text body parts
460    pub fn text_body_count(&self) -> usize {
461        self.text_body.len()
462    }
463
464    /// Returns the number of HTML body parts
465    pub fn html_body_count(&self) -> usize {
466        self.html_body.len()
467    }
468
469    /// Returns the number of attachments
470    pub fn attachment_count(&self) -> usize {
471        self.attachments.len()
472    }
473
474    /// Returns an Interator over the text body parts
475    pub fn text_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
476        BodyPartIterator::new(self, &self.text_body)
477    }
478
479    /// Returns an Interator over the HTML body parts
480    pub fn html_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
481        BodyPartIterator::new(self, &self.html_body)
482    }
483
484    /// Returns an Interator over the attachments
485    pub fn attachments(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
486        AttachmentIterator::new(self)
487    }
488
489    /// Returns an owned version of the message
490    pub fn into_owned(self) -> Message<'static> {
491        Message {
492            html_body: self.html_body,
493            text_body: self.text_body,
494            attachments: self.attachments,
495            parts: self.parts.into_iter().map(|p| p.into_owned()).collect(),
496            raw_message: self.raw_message.into_owned().into(),
497        }
498    }
499}
500
501impl<'x> TryInto<Message<'x>> for &'x [u8] {
502    type Error = ();
503
504    fn try_into(self) -> Result<Message<'x>, Self::Error> {
505        MessageParser::default().parse(self).ok_or(())
506    }
507}