async_imap/types/
fetch.rs

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