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