Skip to main content

io_email/message/imap/
get.rs

1//! IMAP message-get coroutine: optional SELECT then UID FETCH
2//! BODY.PEEK[], returning raw RFC 5322 bytes without flipping \\Seen.
3//!
4//! # Example
5//!
6//! ```rust,ignore
7//! use io_email::message::imap::get::ImapMessageGet;
8//!
9//! let raw = client.run(ImapMessageGet::new("INBOX", "12", true)?)?;
10//! ```
11
12use alloc::{string::String, vec, vec::Vec};
13use core::mem;
14
15use io_imap::{
16    codec::fragmentizer::Fragmentizer,
17    coroutine::{ImapCoroutine, ImapCoroutineState, ImapYield},
18    rfc3501::{
19        fetch::{ImapMessageFetch, ImapMessageFetchError, ImapMessageFetchOptions},
20        select::{ImapMailboxSelect, ImapMailboxSelectError, ImapMailboxSelectOptions},
21    },
22    types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
23};
24use log::trace;
25use thiserror::Error;
26
27use crate::imap::convert::{InvalidMailboxName, InvalidUidSet, parse_mailbox, parse_uids};
28
29/// Errors produced by [`ImapMessageGet`].
30#[derive(Debug, Error)]
31pub enum ImapMessageGetError {
32    #[error(transparent)]
33    Select(#[from] ImapMailboxSelectError),
34    #[error(transparent)]
35    Fetch(#[from] ImapMessageFetchError),
36    #[error("invalid IMAP mailbox `{0}`")]
37    InvalidMailbox(String),
38    #[error("invalid message UID `{0}`")]
39    InvalidUid(String),
40    #[error("empty UID set")]
41    EmptyUidSet,
42    #[error("FETCH returned no body for the requested message")]
43    EmptyBody,
44    #[error("coroutine was resumed after completion")]
45    ResumedAfterDone,
46}
47
48impl From<InvalidMailboxName> for ImapMessageGetError {
49    fn from(err: InvalidMailboxName) -> Self {
50        Self::InvalidMailbox(err.0)
51    }
52}
53
54impl From<InvalidUidSet> for ImapMessageGetError {
55    fn from(err: InvalidUidSet) -> Self {
56        match err {
57            InvalidUidSet::Empty => Self::EmptyUidSet,
58            InvalidUidSet::Invalid(s) => Self::InvalidUid(s),
59        }
60    }
61}
62
63/// I/O-free coroutine fetching one message's raw RFC 5322 bytes.
64pub struct ImapMessageGet {
65    state: State,
66}
67
68impl ImapMessageGet {
69    pub fn new(mailbox: &str, id: &str, auto_select: bool) -> Result<Self, ImapMessageGetError> {
70        trace!("prepare IMAP message get (auto_select={auto_select})");
71        let mbox = parse_mailbox(mailbox)?;
72        let sequence_set = parse_uids(&[id])?;
73        let item_names =
74            MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::BodyExt {
75                section: None,
76                partial: None,
77                peek: true,
78            }]);
79        let fetch = ImapMessageFetch::new(
80            sequence_set,
81            item_names,
82            ImapMessageFetchOptions {
83                uid: true,
84                ..Default::default()
85            },
86        );
87        let state = if auto_select {
88            State::Selecting {
89                select: ImapMailboxSelect::new(mbox, ImapMailboxSelectOptions::default()),
90                fetch,
91            }
92        } else {
93            State::Fetching(fetch)
94        };
95        Ok(Self { state })
96    }
97}
98
99#[allow(clippy::large_enum_variant)] // see flag_store.rs for rationale
100enum State {
101    Selecting {
102        select: ImapMailboxSelect,
103        fetch: ImapMessageFetch,
104    },
105    Fetching(ImapMessageFetch),
106    Done,
107}
108
109impl ImapCoroutine for ImapMessageGet {
110    type Yield = ImapYield;
111    type Return = Result<Vec<u8>, ImapMessageGetError>;
112
113    fn resume(
114        &mut self,
115        fragmentizer: &mut Fragmentizer,
116        mut bytes: Option<&[u8]>,
117    ) -> ImapCoroutineState<Self::Yield, Self::Return> {
118        loop {
119            match mem::replace(&mut self.state, State::Done) {
120                State::Selecting { mut select, fetch } => {
121                    match select.resume(fragmentizer, bytes.take()) {
122                        ImapCoroutineState::Yielded(yielded) => {
123                            self.state = State::Selecting { select, fetch };
124                            return ImapCoroutineState::Yielded(yielded);
125                        }
126                        ImapCoroutineState::Complete(Ok(_)) => {
127                            self.state = State::Fetching(fetch);
128                        }
129                        ImapCoroutineState::Complete(Err(err)) => {
130                            return ImapCoroutineState::Complete(Err(err.into()));
131                        }
132                    }
133                }
134                State::Fetching(mut fetch) => match fetch.resume(fragmentizer, bytes.take()) {
135                    ImapCoroutineState::Yielded(yielded) => {
136                        self.state = State::Fetching(fetch);
137                        return ImapCoroutineState::Yielded(yielded);
138                    }
139                    ImapCoroutineState::Complete(Ok(data)) => {
140                        let raw = data
141                            .into_values()
142                            .flat_map(|items| items.into_inner().into_iter())
143                            .find_map(|item| match item {
144                                MessageDataItem::BodyExt { data, .. } => {
145                                    data.0.map(|d| d.as_ref().to_vec())
146                                }
147                                _ => None,
148                            });
149                        match raw {
150                            Some(raw) => return ImapCoroutineState::Complete(Ok(raw)),
151                            None => {
152                                return ImapCoroutineState::Complete(Err(
153                                    ImapMessageGetError::EmptyBody,
154                                ));
155                            }
156                        }
157                    }
158                    ImapCoroutineState::Complete(Err(err)) => {
159                        return ImapCoroutineState::Complete(Err(err.into()));
160                    }
161                },
162                State::Done => {
163                    return ImapCoroutineState::Complete(Err(
164                        ImapMessageGetError::ResumedAfterDone,
165                    ));
166                }
167            }
168        }
169    }
170}