Skip to main content

async_imap/types/
fetch.rs

1use std::borrow::Cow;
2
3use chrono::{DateTime, FixedOffset};
4use imap_proto::types::{
5    AttributeValue, BodyStructure, Envelope, MessageSection, Response, SectionPath,
6};
7
8use super::{Flag, Seq, Uid};
9use crate::types::ResponseData;
10
11/// Format of Date and Time as defined RFC3501.
12/// See `date-time` element in [Formal Syntax](https://tools.ietf.org/html/rfc3501#section-9)
13/// chapter of this RFC.
14const DATE_TIME_FORMAT: &str = "%d-%b-%Y %H:%M:%S %z";
15
16/// An IMAP [`FETCH` response](https://tools.ietf.org/html/rfc3501#section-7.4.2) that contains
17/// data about a particular message.
18///
19/// This response occurs as the result of a `FETCH` or `STORE`
20/// command, as well as by unilateral server decision (e.g., flag updates).
21#[derive(Debug)]
22pub struct Fetch {
23    response: ResponseData,
24    /// The ordinal number of this message in its containing mailbox.
25    pub message: Seq,
26
27    /// A number expressing the unique identifier of the message.
28    /// Only present if `UID` was specified in the query argument to `FETCH` and the server
29    /// supports UIDs.
30    pub uid: Option<Uid>,
31
32    /// A number expressing the [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message.
33    /// Only present if `RFC822.SIZE` was specified in the query argument to `FETCH`.
34    pub size: Option<u32>,
35
36    /// A number expressing the [RFC-7162](https://tools.ietf.org/html/rfc7162) mod-sequence
37    /// of the message.
38    pub modseq: Option<u64>,
39}
40
41impl Fetch {
42    pub(crate) fn new(response: ResponseData) -> Self {
43        let (message, uid, size, modseq) =
44            if let Response::Fetch(message, attrs) = response.parsed() {
45                let mut uid = None;
46                let mut size = None;
47                let mut modseq = None;
48
49                for attr in attrs {
50                    match attr {
51                        AttributeValue::Uid(id) => uid = Some(*id),
52                        AttributeValue::Rfc822Size(sz) => size = Some(*sz),
53                        AttributeValue::ModSeq(ms) => modseq = Some(*ms),
54                        _ => {}
55                    }
56                }
57                (*message, uid, size, modseq)
58            } else {
59                unreachable!()
60            };
61
62        Fetch {
63            response,
64            message,
65            uid,
66            size,
67            modseq,
68        }
69    }
70
71    /// A list of flags that are set for this message.
72    pub fn flags(&self) -> impl Iterator<Item = Flag<'_>> {
73        if let Response::Fetch(_, attrs) = self.response.parsed() {
74            attrs
75                .iter()
76                .filter_map(|attr| match attr {
77                    AttributeValue::Flags(raw_flags) => {
78                        Some(raw_flags.iter().map(|s| Flag::from(s.as_ref())))
79                    }
80                    _ => None,
81                })
82                .flatten()
83        } else {
84            unreachable!()
85        }
86    }
87
88    /// The bytes that make up the header of this message, if `BODY[HEADER]`, `BODY.PEEK[HEADER]`,
89    /// or `RFC822.HEADER` was included in the `query` argument to `FETCH`.
90    pub fn header(&self) -> Option<&[u8]> {
91        if let Response::Fetch(_, attrs) = self.response.parsed() {
92            attrs
93                .iter()
94                .filter_map(|av| match av {
95                    AttributeValue::BodySection {
96                        section: Some(SectionPath::Full(MessageSection::Header)),
97                        data: Some(hdr),
98                        ..
99                    }
100                    | AttributeValue::Rfc822Header(Some(hdr)) => Some(hdr.as_ref()),
101                    _ => None,
102                })
103                .next()
104        } else {
105            unreachable!()
106        }
107    }
108
109    /// The bytes that make up this message, included if `BODY[]` or `RFC822` was included in the
110    /// `query` argument to `FETCH`. The bytes SHOULD be interpreted by the client according to the
111    /// content transfer encoding, body type, and subtype.
112    pub fn body(&self) -> Option<&[u8]> {
113        if let Response::Fetch(_, attrs) = self.response.parsed() {
114            attrs
115                .iter()
116                .filter_map(|av| match av {
117                    AttributeValue::BodySection {
118                        section: None,
119                        data: Some(body),
120                        ..
121                    }
122                    | AttributeValue::Rfc822(Some(body)) => Some(body.as_ref()),
123                    _ => None,
124                })
125                .next()
126        } else {
127            unreachable!()
128        }
129    }
130
131    /// The bytes that make up the text of this message, included if `BODY[TEXT]`, `RFC822.TEXT`,
132    /// or `BODY.PEEK[TEXT]` was included in the `query` argument to `FETCH`. The bytes SHOULD be
133    /// interpreted by the client according to the content transfer encoding, body type, and
134    /// subtype.
135    pub fn text(&self) -> Option<&[u8]> {
136        if let Response::Fetch(_, attrs) = self.response.parsed() {
137            attrs
138                .iter()
139                .filter_map(|av| match av {
140                    AttributeValue::BodySection {
141                        section: Some(SectionPath::Full(MessageSection::Text)),
142                        data: Some(body),
143                        ..
144                    }
145                    | AttributeValue::Rfc822Text(Some(body)) => Some(body.as_ref()),
146                    _ => None,
147                })
148                .next()
149        } else {
150            unreachable!()
151        }
152    }
153
154    /// The envelope of this message, if `ENVELOPE` was included in the `query` argument to
155    /// `FETCH`. This is computed by the server by parsing the
156    /// [RFC-2822](https://tools.ietf.org/html/rfc2822) header into the component parts, defaulting
157    /// various fields as necessary.
158    ///
159    /// The full description of the format of the envelope is given in [RFC 3501 section
160    /// 7.4.2](https://tools.ietf.org/html/rfc3501#section-7.4.2).
161    pub fn envelope(&self) -> Option<&Envelope<'_>> {
162        if let Response::Fetch(_, attrs) = self.response.parsed() {
163            attrs
164                .iter()
165                .filter_map(|av| match av {
166                    AttributeValue::Envelope(env) => Some(&**env),
167                    _ => None,
168                })
169                .next()
170        } else {
171            unreachable!()
172        }
173    }
174
175    /// Extract the bytes that makes up the given `BOD[<section>]` of a `FETCH` response.
176    ///
177    /// See [section 7.4.2 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-7.4.2) for
178    /// details.
179    pub fn section(&self, path: &SectionPath) -> Option<&[u8]> {
180        if let Response::Fetch(_, attrs) = self.response.parsed() {
181            attrs
182                .iter()
183                .filter_map(|av| match av {
184                    AttributeValue::BodySection {
185                        section: Some(sp),
186                        data: Some(data),
187                        ..
188                    } if sp == path => Some(data.as_ref()),
189                    _ => None,
190                })
191                .next()
192        } else {
193            unreachable!()
194        }
195    }
196
197    /// Extract the `INTERNALDATE` of a `FETCH` response
198    ///
199    /// See [section 2.3.3 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.3) for
200    /// details.
201    pub fn internal_date(&self) -> Option<DateTime<FixedOffset>> {
202        if let Response::Fetch(_, attrs) = self.response.parsed() {
203            attrs
204                .iter()
205                .filter_map(|av| match av {
206                    AttributeValue::InternalDate(date_time) => Some(date_time.as_ref()),
207                    _ => None,
208                })
209                .next()
210                .and_then(|date_time| DateTime::parse_from_str(date_time, DATE_TIME_FORMAT).ok())
211        } else {
212            unreachable!()
213        }
214    }
215
216    /// Extract the `BODYSTRUCTURE` of a `FETCH` response
217    ///
218    /// See [section 2.3.6 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.6) for
219    /// details.
220    pub fn bodystructure(&self) -> Option<&BodyStructure<'_>> {
221        if let Response::Fetch(_, attrs) = self.response.parsed() {
222            attrs
223                .iter()
224                .filter_map(|av| match av {
225                    AttributeValue::BodyStructure(bs) => Some(bs),
226                    _ => None,
227                })
228                .next()
229        } else {
230            unreachable!()
231        }
232    }
233
234    /// Extract the `X-GM-LABELS` of a `FETCH` response
235    ///
236    /// See [Access to Gmail labels: X-GM-LABELS](https://developers.google.com/gmail/imap/imap-extensions#access_to_labels_x-gm-labels)
237    /// for details.
238    pub fn gmail_labels(&self) -> Option<&Vec<Cow<'_, str>>> {
239        if let Response::Fetch(_, attrs) = self.response.parsed() {
240            attrs
241                .iter()
242                .filter_map(|av| match av {
243                    AttributeValue::GmailLabels(gl) => Some(gl),
244                    _ => None,
245                })
246                .next()
247        } else {
248            unreachable!()
249        }
250    }
251
252    /// Extract the `X-GM-MSGID` of a `FETCH` response
253    ///
254    /// See [Access to the Gmail unique message ID: X-GM-MSGID](https://developers.google.com/workspace/gmail/imap/imap-extensions#access_to_the_unique_message_id_x-gm-msgid)
255    /// for details.
256    pub fn gmail_msg_id(&self) -> Option<&u64> {
257        if let Response::Fetch(_, attrs) = self.response.parsed() {
258            attrs
259                .iter()
260                .filter_map(|av| match av {
261                    AttributeValue::GmailMsgId(id) => Some(id),
262                    _ => None,
263                })
264                .next()
265        } else {
266            unreachable!()
267        }
268    }
269}