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    decoders::html::{html_to_text, text_to_html},
11    parsers::{
12        fields::thread::thread_name,
13        preview::{preview_html, preview_text},
14        MessageStream,
15    },
16    Address, AttachmentIterator, BodyPartIterator, DateTime, GetHeader, Header, HeaderForm,
17    HeaderName, HeaderValue, Message, MessageParser, MessagePart, PartType, Received,
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 first 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 all References header fields
260    pub fn references(&self) -> &HeaderValue<'x> {
261        self.parts[0]
262            .headers
263            .header_value(&HeaderName::References)
264            .unwrap_or(&HeaderValue::Empty)
265    }
266
267    /// Returns the Reply-To header field
268    pub fn reply_to(&self) -> Option<&Address<'x>> {
269        self.parts[0]
270            .headers
271            .header_value(&HeaderName::ReplyTo)
272            .and_then(|a| a.as_address())
273    }
274
275    /// Returns the Resent-BCC header field
276    pub fn resent_bcc(&self) -> Option<&Address<'x>> {
277        self.parts[0]
278            .headers
279            .header_value(&HeaderName::ResentBcc)
280            .and_then(|a| a.as_address())
281    }
282
283    /// Returns the Resent-CC header field
284    pub fn resent_cc(&self) -> Option<&Address<'x>> {
285        self.parts[0]
286            .headers
287            .header_value(&HeaderName::ResentTo)
288            .and_then(|a| a.as_address())
289    }
290
291    /// Returns all Resent-Date header fields
292    pub fn resent_date(&self) -> &HeaderValue<'x> {
293        self.parts[0]
294            .headers
295            .header_value(&HeaderName::ResentDate)
296            .unwrap_or(&HeaderValue::Empty)
297    }
298
299    /// Returns the Resent-From header field
300    pub fn resent_from(&self) -> Option<&Address<'x>> {
301        self.parts[0]
302            .headers
303            .header_value(&HeaderName::ResentFrom)
304            .and_then(|a| a.as_address())
305    }
306
307    /// Returns all Resent-Message-ID header fields
308    pub fn resent_message_id(&self) -> &HeaderValue<'x> {
309        self.parts[0]
310            .headers
311            .header_value(&HeaderName::ResentMessageId)
312            .unwrap_or(&HeaderValue::Empty)
313    }
314
315    /// Returns the Sender header field
316    pub fn resent_sender(&self) -> Option<&Address<'x>> {
317        self.parts[0]
318            .headers
319            .header_value(&HeaderName::ResentSender)
320            .and_then(|a| a.as_address())
321    }
322
323    /// Returns the Resent-To header field
324    pub fn resent_to(&self) -> Option<&Address<'x>> {
325        self.parts[0]
326            .headers
327            .header_value(&HeaderName::ResentTo)
328            .and_then(|a| a.as_address())
329    }
330
331    /// Returns all Return-Path header fields
332    pub fn return_path(&self) -> &HeaderValue<'x> {
333        self.parts[0]
334            .headers
335            .header_value(&HeaderName::ReturnPath)
336            .unwrap_or(&HeaderValue::Empty)
337    }
338
339    /// Returns the return address from either the Return-Path
340    /// or From header fields
341    pub fn return_address(&self) -> Option<&str> {
342        match self.parts[0].headers.header_value(&HeaderName::ReturnPath) {
343            Some(HeaderValue::Text(text)) => Some(text.as_ref()),
344            Some(HeaderValue::TextList(text_list)) => text_list.last().map(|t| t.as_ref()),
345            _ => match self.parts[0].headers.header_value(&HeaderName::From) {
346                Some(HeaderValue::Address(addr)) => addr.first()?.address.as_deref(),
347                _ => None,
348            },
349        }
350    }
351
352    /// Returns the Sender header field
353    pub fn sender(&self) -> Option<&Address<'x>> {
354        self.parts[0]
355            .headers
356            .header_value(&HeaderName::Sender)
357            .and_then(|a| a.as_address())
358    }
359
360    /// Returns the Subject header field
361    pub fn subject(&self) -> Option<&str> {
362        self.parts[0]
363            .headers
364            .header_value(&HeaderName::Subject)
365            .and_then(|header| header.as_text())
366    }
367
368    /// Returns the message thread name or 'base subject' as defined in
369    /// [RFC 5957 - Internet Message Access Protocol - SORT and THREAD Extensions (Section 2.1)](https://datatracker.ietf.org/doc/html/rfc5256#section-2.1)
370    pub fn thread_name(&self) -> Option<&str> {
371        thread_name(self.subject()?).into()
372    }
373
374    /// Returns the To header field
375    pub fn to(&self) -> Option<&Address<'x>> {
376        self.parts[0]
377            .headers
378            .header_value(&HeaderName::To)
379            .and_then(|a| a.as_address())
380    }
381
382    /// Returns a preview of the message body
383    pub fn body_preview(&self, preview_len: usize) -> Option<Cow<'x, str>> {
384        if !self.text_body.is_empty() {
385            preview_text(self.body_text(0)?, preview_len).into()
386        } else if !self.html_body.is_empty() {
387            preview_html(self.body_html(0)?, preview_len).into()
388        } else {
389            None
390        }
391    }
392
393    /// Returns a message body part as text/plain
394    pub fn body_html(&'x self, pos: usize) -> Option<Cow<'x, str>> {
395        let part = self.parts.get(*self.html_body.get(pos)? as usize)?;
396        match &part.body {
397            PartType::Html(html) => Some(html.as_ref().into()),
398            PartType::Text(text) => Some(text_to_html(text.as_ref()).into()),
399            _ => None,
400        }
401    }
402
403    /// Returns a message body part as text/plain
404    pub fn body_text(&'x self, pos: usize) -> Option<Cow<'x, str>> {
405        let part = self.parts.get(*self.text_body.get(pos)? as usize)?;
406        match &part.body {
407            PartType::Text(text) => Some(text.as_ref().into()),
408            PartType::Html(html) => Some(html_to_text(html.as_ref()).into()),
409            _ => None,
410        }
411    }
412
413    /// Returns a message part by position
414    pub fn part(&self, pos: u32) -> Option<&MessagePart<'x>> {
415        self.parts.get(pos as usize)
416    }
417
418    /// Returns an inline HTML body part by position
419    pub fn html_part(&self, pos: u32) -> Option<&MessagePart<'x>> {
420        self.parts.get(*self.html_body.get(pos as usize)? as usize)
421    }
422
423    /// Returns an inline text body part by position
424    pub fn text_part(&self, pos: u32) -> Option<&MessagePart<'x>> {
425        self.parts.get(*self.text_body.get(pos as usize)? as usize)
426    }
427
428    /// Returns an attacment by position
429    pub fn attachment(&self, pos: u32) -> Option<&MessagePart<'x>> {
430        self.parts
431            .get(*self.attachments.get(pos as usize)? as usize)
432    }
433
434    /// Returns the number of plain text body parts
435    pub fn text_body_count(&self) -> usize {
436        self.text_body.len()
437    }
438
439    /// Returns the number of HTML body parts
440    pub fn html_body_count(&self) -> usize {
441        self.html_body.len()
442    }
443
444    /// Returns the number of attachments
445    pub fn attachment_count(&self) -> usize {
446        self.attachments.len()
447    }
448
449    /// Returns an Interator over the text body parts
450    pub fn text_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
451        BodyPartIterator::new(self, &self.text_body)
452    }
453
454    /// Returns an Interator over the HTML body parts
455    pub fn html_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
456        BodyPartIterator::new(self, &self.html_body)
457    }
458
459    /// Returns an Interator over the attachments
460    pub fn attachments(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
461        AttachmentIterator::new(self)
462    }
463
464    /// Returns an owned version of the message
465    pub fn into_owned(self) -> Message<'static> {
466        Message {
467            html_body: self.html_body,
468            text_body: self.text_body,
469            attachments: self.attachments,
470            parts: self.parts.into_iter().map(|p| p.into_owned()).collect(),
471            raw_message: self.raw_message.into_owned().into(),
472        }
473    }
474}
475
476impl<'x> TryInto<Message<'x>> for &'x [u8] {
477    type Error = ();
478
479    fn try_into(self) -> Result<Message<'x>, Self::Error> {
480        MessageParser::default().parse(self).ok_or(())
481    }
482}