io_email/message/imap/
get.rs1use 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#[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
63pub 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)] enum 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}