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}