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}