1use core::{fmt, num::NonZeroU32};
96
97use alloc::{collections::BTreeMap, string::String, string::ToString, vec::Vec};
98
99use imap_codec::{
100 CommandCodec,
101 fragmentizer::Fragmentizer,
102 imap_types::{
103 command::{Command, CommandBody, FetchModifier},
104 core::{TagGenerator, Vec1},
105 fetch::{MacroOrMessageDataItemNames, MessageDataItem},
106 response::{Data, StatusKind, Tagged},
107 sequence::{SeqOrUid, SequenceSet},
108 },
109};
110use log::trace;
111use thiserror::Error;
112
113use crate::{coroutine::*, imap_try, send::*};
114
115#[derive(Clone, Debug, Error)]
117pub enum ImapMessageFetchError {
118 #[error("IMAP FETCH failed: NO {0}")]
120 No(String),
121 #[error("IMAP FETCH failed: BAD {0}")]
123 Bad(String),
124 #[error("IMAP FETCH failed: BYE {0}")]
126 Bye(String),
127 #[error("IMAP FETCH failed: server did not return a tagged response")]
129 MissingTagged,
130 #[error("IMAP FETCH failed: server did not return any data")]
133 MissingData,
134 #[error("IMAP FETCH failed: {0}")]
136 Send(#[from] ImapSendError),
137}
138
139#[derive(Clone, Debug, Default, Eq, PartialEq)]
141pub struct ImapMessageFetchOptions {
142 pub uid: bool,
144 pub modifiers: Vec<FetchModifier>,
147}
148
149pub struct ImapMessageFetch {
151 state: State,
152}
153
154impl ImapMessageFetch {
155 pub fn new(
158 sequence_set: SequenceSet,
159 items: MacroOrMessageDataItemNames<'static>,
160 opts: ImapMessageFetchOptions,
161 ) -> Self {
162 let command = Command {
163 tag: TagGenerator::new().generate(),
164 body: CommandBody::Fetch {
165 modifiers: opts.modifiers,
166 sequence_set,
167 macro_or_item_names: items,
168 uid: opts.uid,
169 },
170 };
171
172 trace!("send IMAP command {command:?}");
173
174 let state = State::Send(ImapSend::new(CommandCodec::new(), command));
175
176 Self { state }
177 }
178}
179
180impl ImapCoroutine for ImapMessageFetch {
181 type Yield = ImapYield;
182 type Return =
183 Result<BTreeMap<NonZeroU32, Vec1<MessageDataItem<'static>>>, ImapMessageFetchError>;
184
185 fn resume(
186 &mut self,
187 fragmentizer: &mut Fragmentizer,
188 arg: Option<&[u8]>,
189 ) -> ImapCoroutineState<Self::Yield, Self::Return> {
190 match &mut self.state {
191 State::Send(send) => {
192 let out = imap_try!(send, fragmentizer, arg);
193
194 if let Some(bye) = out.bye {
195 let err = ImapMessageFetchError::Bye(bye.text.to_string());
196 return ImapCoroutineState::Complete(Err(err));
197 }
198
199 let Some(Tagged { body, .. }) = out.tagged else {
200 let err = ImapMessageFetchError::MissingTagged;
201 return ImapCoroutineState::Complete(Err(err));
202 };
203
204 let mut output: BTreeMap<NonZeroU32, Vec<MessageDataItem<'static>>> =
205 BTreeMap::new();
206 for data in out.data {
207 if let Data::Fetch { seq, items } = data {
208 output.entry(seq).or_default().extend(items);
209 }
210 }
211
212 match body.kind {
213 StatusKind::Ok => ImapCoroutineState::Complete(Ok(output
214 .into_iter()
215 .map(|(key, val)| (key, Vec1::unvalidated(val)))
216 .collect())),
217 StatusKind::No => {
218 let err = ImapMessageFetchError::No(body.text.to_string());
219 ImapCoroutineState::Complete(Err(err))
220 }
221 StatusKind::Bad => {
222 let err = ImapMessageFetchError::Bad(body.text.to_string());
223 ImapCoroutineState::Complete(Err(err))
224 }
225 }
226 }
227 }
228 }
229}
230
231pub struct ImapMessageFetchFirst {
233 state: State,
234}
235
236impl ImapMessageFetchFirst {
237 pub fn new(
240 id: NonZeroU32,
241 items: MacroOrMessageDataItemNames<'static>,
242 opts: ImapMessageFetchOptions,
243 ) -> Self {
244 let command = Command {
245 tag: TagGenerator::new().generate(),
246 body: CommandBody::Fetch {
247 modifiers: opts.modifiers,
248 sequence_set: SequenceSet::from(SeqOrUid::from(id)),
249 macro_or_item_names: items,
250 uid: opts.uid,
251 },
252 };
253
254 trace!("send IMAP command {command:?}");
255
256 let state = State::Send(ImapSend::new(CommandCodec::new(), command));
257
258 Self { state }
259 }
260}
261
262impl ImapCoroutine for ImapMessageFetchFirst {
263 type Yield = ImapYield;
264 type Return = Result<Vec1<MessageDataItem<'static>>, ImapMessageFetchError>;
265
266 fn resume(
267 &mut self,
268 fragmentizer: &mut Fragmentizer,
269 arg: Option<&[u8]>,
270 ) -> ImapCoroutineState<Self::Yield, Self::Return> {
271 match &mut self.state {
272 State::Send(send) => {
273 let out = imap_try!(send, fragmentizer, arg);
274
275 if let Some(bye) = out.bye {
276 let err = ImapMessageFetchError::Bye(bye.text.to_string());
277 return ImapCoroutineState::Complete(Err(err));
278 }
279
280 let Some(Tagged { body, .. }) = out.tagged else {
281 let err = ImapMessageFetchError::MissingTagged;
282 return ImapCoroutineState::Complete(Err(err));
283 };
284
285 let mut output = None;
286 for data in out.data {
287 if let Data::Fetch { items, .. } = data {
288 output = Some(items);
289 }
290 }
291
292 match body.kind {
293 StatusKind::Ok => match output {
294 Some(items) => ImapCoroutineState::Complete(Ok(items)),
295 None => {
296 ImapCoroutineState::Complete(Err(ImapMessageFetchError::MissingData))
297 }
298 },
299 StatusKind::No => {
300 let err = ImapMessageFetchError::No(body.text.to_string());
301 ImapCoroutineState::Complete(Err(err))
302 }
303 StatusKind::Bad => {
304 let err = ImapMessageFetchError::Bad(body.text.to_string());
305 ImapCoroutineState::Complete(Err(err))
306 }
307 }
308 }
309 }
310 }
311}
312
313enum State {
314 Send(ImapSend<CommandCodec>),
315}
316
317impl fmt::Display for State {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 match self {
320 Self::Send(_) => f.write_str("send fetch"),
321 }
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use core::str;
328
329 use alloc::{borrow::ToOwned, format};
330
331 use crate::rfc3501::fetch::*;
332
333 #[test]
334 fn fetch_success_groups_by_seq() {
335 let mut fetch = ImapMessageFetch::new(
336 "1:3".try_into().expect("valid sequence set"),
337 MacroOrMessageDataItemNames::Macro(imap_codec::imap_types::fetch::Macro::All),
338 ImapMessageFetchOptions::default(),
339 );
340 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
341
342 let bytes = expect_wants_write_fetch(&mut fetch, &mut frag, None);
343 let line = str::from_utf8(&bytes).expect("utf8 command");
344 let tag = first_word(line).to_owned();
345 assert!(line.contains("FETCH 1:3 "));
346
347 expect_wants_read_fetch(&mut fetch, &mut frag);
348
349 let reply =
350 format!("* 1 FETCH (UID 100)\r\n* 2 FETCH (UID 101)\r\n{tag} OK FETCH completed\r\n",);
351 let out = expect_complete_ok_fetch(&mut fetch, &mut frag, reply.as_bytes());
352 assert_eq!(2, out.len());
353 }
354
355 #[test]
356 fn uid_variant_sends_uid_fetch() {
357 let mut fetch = ImapMessageFetch::new(
358 "42".try_into().expect("valid sequence set"),
359 MacroOrMessageDataItemNames::Macro(imap_codec::imap_types::fetch::Macro::All),
360 ImapMessageFetchOptions {
361 uid: true,
362 ..Default::default()
363 },
364 );
365 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
366
367 let bytes = expect_wants_write_fetch(&mut fetch, &mut frag, None);
368 let line = str::from_utf8(&bytes).expect("utf8 command");
369 assert!(line.contains("UID FETCH 42 "));
370 }
371
372 #[test]
373 fn fetch_tagged_no_returns_no_error() {
374 let mut fetch = ImapMessageFetch::new(
375 "1".try_into().expect("valid sequence set"),
376 MacroOrMessageDataItemNames::Macro(imap_codec::imap_types::fetch::Macro::All),
377 ImapMessageFetchOptions::default(),
378 );
379 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
380
381 let bytes = expect_wants_write_fetch(&mut fetch, &mut frag, None);
382 let tag = first_word(str::from_utf8(&bytes).expect("utf8 command")).to_owned();
383
384 expect_wants_read_fetch(&mut fetch, &mut frag);
385
386 let reply = format!("{tag} NO no mailbox selected\r\n");
387 let err = expect_complete_err_fetch(&mut fetch, &mut frag, reply.as_bytes());
388 let ImapMessageFetchError::No(text) = err else {
389 panic!("expected ImapMessageFetchError::No, got {err:?}");
390 };
391 assert_eq!(text, "no mailbox selected");
392 }
393
394 #[test]
395 fn fetch_bye_returns_bye_error() {
396 let mut fetch = ImapMessageFetch::new(
397 "1".try_into().expect("valid sequence set"),
398 MacroOrMessageDataItemNames::Macro(imap_codec::imap_types::fetch::Macro::All),
399 ImapMessageFetchOptions::default(),
400 );
401 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
402
403 let _ = expect_wants_write_fetch(&mut fetch, &mut frag, None);
404 expect_wants_read_fetch(&mut fetch, &mut frag);
405
406 let err = expect_complete_err_fetch(&mut fetch, &mut frag, b"* BYE going down\r\n");
407 let ImapMessageFetchError::Bye(text) = err else {
408 panic!("expected ImapMessageFetchError::Bye, got {err:?}");
409 };
410 assert_eq!(text, "going down");
411 }
412
413 #[test]
414 fn first_success_returns_items() {
415 let id = NonZeroU32::new(42).expect("non-zero");
416 let mut fetch = ImapMessageFetchFirst::new(
417 id,
418 MacroOrMessageDataItemNames::Macro(imap_codec::imap_types::fetch::Macro::All),
419 ImapMessageFetchOptions::default(),
420 );
421 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
422
423 let bytes = expect_wants_write_first(&mut fetch, &mut frag, None);
424 let tag = first_word(str::from_utf8(&bytes).expect("utf8 command")).to_owned();
425
426 expect_wants_read_first(&mut fetch, &mut frag);
427
428 let reply = format!("* 42 FETCH (UID 100)\r\n{tag} OK FETCH completed\r\n");
429 let items = expect_complete_ok_first(&mut fetch, &mut frag, reply.as_bytes());
430 assert_eq!(1, items.as_ref().len());
431 }
432
433 #[test]
434 fn first_missing_data_returns_missing_data_error() {
435 let id = NonZeroU32::new(42).expect("non-zero");
436 let mut fetch = ImapMessageFetchFirst::new(
437 id,
438 MacroOrMessageDataItemNames::Macro(imap_codec::imap_types::fetch::Macro::All),
439 ImapMessageFetchOptions::default(),
440 );
441 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
442
443 let bytes = expect_wants_write_first(&mut fetch, &mut frag, None);
444 let tag = first_word(str::from_utf8(&bytes).expect("utf8 command")).to_owned();
445
446 expect_wants_read_first(&mut fetch, &mut frag);
447
448 let reply = format!("{tag} OK FETCH completed\r\n");
449 let err = expect_complete_err_first(&mut fetch, &mut frag, reply.as_bytes());
450 assert!(matches!(err, ImapMessageFetchError::MissingData));
451 }
452
453 fn expect_wants_write_fetch(
454 cor: &mut ImapMessageFetch,
455 frag: &mut Fragmentizer,
456 arg: Option<&[u8]>,
457 ) -> Vec<u8> {
458 match cor.resume(frag, arg) {
459 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => bytes,
460 state => panic!("expected WantsWrite, got {state:?}"),
461 }
462 }
463
464 fn expect_wants_read_fetch(cor: &mut ImapMessageFetch, frag: &mut Fragmentizer) {
465 match cor.resume(frag, None) {
466 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {}
467 state => panic!("expected WantsRead, got {state:?}"),
468 }
469 }
470
471 fn expect_complete_ok_fetch(
472 cor: &mut ImapMessageFetch,
473 frag: &mut Fragmentizer,
474 reply: &[u8],
475 ) -> BTreeMap<NonZeroU32, Vec1<MessageDataItem<'static>>> {
476 match cor.resume(frag, Some(reply)) {
477 ImapCoroutineState::Complete(Ok(value)) => value,
478 state => panic!("expected Complete(Ok), got {state:?}"),
479 }
480 }
481
482 fn expect_complete_err_fetch(
483 cor: &mut ImapMessageFetch,
484 frag: &mut Fragmentizer,
485 reply: &[u8],
486 ) -> ImapMessageFetchError {
487 match cor.resume(frag, Some(reply)) {
488 ImapCoroutineState::Complete(Err(err)) => err,
489 state => panic!("expected Complete(Err), got {state:?}"),
490 }
491 }
492
493 fn expect_wants_write_first(
494 cor: &mut ImapMessageFetchFirst,
495 frag: &mut Fragmentizer,
496 arg: Option<&[u8]>,
497 ) -> Vec<u8> {
498 match cor.resume(frag, arg) {
499 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => bytes,
500 state => panic!("expected WantsWrite, got {state:?}"),
501 }
502 }
503
504 fn expect_wants_read_first(cor: &mut ImapMessageFetchFirst, frag: &mut Fragmentizer) {
505 match cor.resume(frag, None) {
506 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {}
507 state => panic!("expected WantsRead, got {state:?}"),
508 }
509 }
510
511 fn expect_complete_ok_first(
512 cor: &mut ImapMessageFetchFirst,
513 frag: &mut Fragmentizer,
514 reply: &[u8],
515 ) -> Vec1<MessageDataItem<'static>> {
516 match cor.resume(frag, Some(reply)) {
517 ImapCoroutineState::Complete(Ok(value)) => value,
518 state => panic!("expected Complete(Ok), got {state:?}"),
519 }
520 }
521
522 fn expect_complete_err_first(
523 cor: &mut ImapMessageFetchFirst,
524 frag: &mut Fragmentizer,
525 reply: &[u8],
526 ) -> ImapMessageFetchError {
527 match cor.resume(frag, Some(reply)) {
528 ImapCoroutineState::Complete(Err(err)) => err,
529 state => panic!("expected Complete(Err), got {state:?}"),
530 }
531 }
532
533 fn first_word(line: &str) -> &str {
534 line.split_whitespace()
535 .next()
536 .expect("first whitespace-separated token")
537 }
538}