Skip to main content

io_imap/
send.rs

1//! Base coroutine that all higher-level IMAP coroutines delegate to.
2//!
3//! Serialises a command via `imap_codec`, exchanges the bytes with the
4//! caller, and feeds responses back through the borrowed
5//! `Fragmentizer`.
6
7use core::mem;
8
9use alloc::{boxed::Box, collections::VecDeque, vec::Vec};
10
11use imap_codec::{
12    ResponseCodec,
13    encode::{Encoder, Fragment},
14    fragmentizer::{DecodeMessageError, FragmentInfo, Fragmentizer},
15    imap_types::{
16        IntoStatic,
17        core::LiteralMode,
18        response::{Bye, CommandContinuationRequest, Data, Response, Status, StatusBody, Tagged},
19        secret::Secret,
20        utils::escape_byte_string,
21    },
22};
23use log::{debug, trace};
24use thiserror::Error;
25
26use crate::coroutine::{ImapCoroutine, ImapCoroutineState, ImapYield};
27
28/// Failure causes raised by [`ImapSend`].
29#[derive(Clone, Debug, Error)]
30pub enum ImapSendError {
31    /// The stream reached EOF before the tagged response arrived.
32    #[error("Reached unexpected EOF on IMAP stream")]
33    Eof,
34    /// A response line could not be decoded; carries the raw bytes.
35    #[error("Decode IMAP response error")]
36    DecodingFailure(Secret<Box<[u8]>>),
37    /// The `Fragmentizer` poisoned the message after a framing error;
38    /// carries the raw bytes.
39    #[error("Parse IMAP response error: message is poisoned")]
40    MessageIsPoisoned(Secret<Box<[u8]>>),
41    /// The response message exceeded the `Fragmentizer` size limit;
42    /// carries the raw bytes.
43    #[error("Parse IMAP response error: message is too long")]
44    MessageTooLong(Secret<Box<[u8]>>),
45}
46
47/// Step output emitted by [`ImapSend::resume`].
48pub enum ImapSendResult<T: Encoder> {
49    /// The exchange completed; carries the boxed output (boxed to keep
50    /// the enum small next to the I/O variants).
51    Ok(Box<ImapSendOutput<T>>),
52    /// The caller reads from its stream and resumes with the bytes.
53    WantsRead,
54    /// The caller writes the given bytes to its stream and resumes.
55    WantsWrite(Vec<u8>),
56    /// The exchange failed.
57    Err(ImapSendError),
58}
59
60#[derive(Debug)]
61enum State {
62    Serialize,
63    Read,
64    Deserialize,
65}
66
67/// I/O-free coroutine sending one IMAP command and parsing its response.
68pub struct ImapSend<T: Encoder> {
69    message: Option<T::Message<'static>>,
70    state: State,
71    wants_read: bool,
72    wants_write: Option<Vec<u8>>,
73    fragments: VecDeque<Fragment>,
74    codec: ResponseCodec,
75    data: Vec<Data<'static>>,
76    untagged: Vec<StatusBody<'static>>,
77    tagged: Option<Tagged<'static>>,
78    bye: Option<Bye<'static>>,
79    cr: Option<CommandContinuationRequest<'static>>,
80    limbo_literal: Option<Vec<u8>>,
81    done: bool,
82}
83
84impl<T: Encoder> ImapSend<T> {
85    /// Builds a send serialising `message` through `encoder` and
86    /// parsing its response.
87    pub fn new(encoder: T, message: T::Message<'static>) -> Self {
88        let fragments = encoder.encode(&message).collect();
89
90        Self {
91            message: Some(message),
92            codec: ResponseCodec::new(),
93            state: State::Serialize,
94            wants_read: false,
95            wants_write: None,
96            fragments,
97            data: Vec::new(),
98            untagged: Vec::new(),
99            tagged: None,
100            bye: None,
101            cr: None,
102            limbo_literal: None,
103            done: false,
104        }
105    }
106
107    /// Receive-only: skips serialisation and parses the response of a
108    /// request whose bytes were written out of band.
109    ///
110    /// Used by the streamed APPEND literal; `message` is echoed back
111    /// unchanged in the Ok output.
112    pub fn receive(message: T::Message<'static>) -> Self {
113        Self {
114            message: Some(message),
115            codec: ResponseCodec::new(),
116            state: State::Read,
117            wants_read: false,
118            wants_write: None,
119            fragments: VecDeque::new(),
120            data: Vec::new(),
121            untagged: Vec::new(),
122            tagged: None,
123            bye: None,
124            cr: None,
125            limbo_literal: None,
126            done: false,
127        }
128    }
129
130    /// Pass `None` initially or after `WantsWrite`, `Some(bytes)`
131    /// after `WantsRead`, `Some(&[])` on EOF.
132    pub fn resume(
133        &mut self,
134        fragmentizer: &mut Fragmentizer,
135        mut arg: Option<&[u8]>,
136    ) -> ImapSendResult<T> {
137        loop {
138            if let Some(bytes) = self.wants_write.take() {
139                return ImapSendResult::WantsWrite(bytes);
140            }
141
142            if mem::take(&mut self.wants_read) {
143                return ImapSendResult::WantsRead;
144            }
145
146            match self.state {
147                State::Serialize => {
148                    let mut buf = Vec::new();
149
150                    if let Some(bytes) = self.limbo_literal.take() {
151                        buf.extend(bytes);
152                    }
153
154                    while let Some(fragment) = self.fragments.pop_front() {
155                        match fragment {
156                            Fragment::Line { data } => {
157                                buf.extend(data);
158                            }
159                            Fragment::Literal { data, mode } => match mode {
160                                LiteralMode::NonSync => {
161                                    buf.extend(data);
162                                }
163                                LiteralMode::Sync => {
164                                    self.limbo_literal.replace(data);
165                                    break;
166                                }
167                            },
168                        }
169                    }
170
171                    if !buf.is_empty() {
172                        self.wants_write = Some(buf);
173                    }
174                    self.state = State::Read;
175                }
176                State::Read => match arg.take() {
177                    Some(&[]) => {
178                        return ImapSendResult::Err(ImapSendError::Eof);
179                    }
180                    Some(data) => {
181                        trace!("read bytes: {}", escape_byte_string(data));
182                        fragmentizer.enqueue_bytes(data);
183                        self.state = State::Deserialize;
184                    }
185                    None => {
186                        self.wants_read = true;
187                    }
188                },
189                State::Deserialize => match fragmentizer.progress() {
190                    Some(info @ FragmentInfo::Line { .. }) => {
191                        let bytes = fragmentizer.fragment_bytes(info);
192                        trace!("read line fragment: {}", escape_byte_string(bytes));
193
194                        if !fragmentizer.is_message_complete() {
195                            continue;
196                        }
197
198                        match fragmentizer.decode_message(&self.codec) {
199                            Ok(Response::Data(data)) => {
200                                self.data.push(data.into_static());
201                            }
202                            Ok(Response::Status(Status::Untagged(status))) => {
203                                self.untagged.push(status.into_static());
204                            }
205                            Ok(Response::Status(Status::Tagged(tagged))) => {
206                                self.tagged.replace(tagged.into_static());
207                                self.done = true;
208                            }
209                            Ok(Response::Status(Status::Bye(bye))) => {
210                                self.bye.replace(bye.into_static());
211                                self.done = true;
212                            }
213                            Ok(Response::CommandContinuationRequest(cr)) => {
214                                self.cr.replace(cr.into_static());
215                                self.done = self.limbo_literal.is_none();
216                            }
217                            Err(decode_err) => {
218                                let bytes = fragmentizer.message_bytes();
219                                let err = match decode_err {
220                                    DecodeMessageError::DecodingFailure(_)
221                                    | DecodeMessageError::DecodingRemainder { .. } => {
222                                        // NOTE: do not fail the whole
223                                        // command when an untagged
224                                        // response cannot be decoded:
225                                        // skip it with a warning
226                                        // (pimalaya/himalaya#641).
227                                        if bytes.starts_with(b"* ") {
228                                            debug!("skipping undecodable untagged response");
229                                            trace!("{}", escape_byte_string(bytes));
230                                            continue;
231                                        }
232
233                                        let err = Secret::new(bytes.into());
234                                        ImapSendError::DecodingFailure(err)
235                                    }
236                                    DecodeMessageError::MessageTooLong { .. } => {
237                                        let err = Secret::new(bytes.into());
238                                        ImapSendError::MessageTooLong(err)
239                                    }
240                                    DecodeMessageError::MessagePoisoned { .. } => {
241                                        let err = Secret::new(bytes.into());
242                                        ImapSendError::MessageIsPoisoned(err)
243                                    }
244                                };
245
246                                return ImapSendResult::Err(err);
247                            }
248                        }
249                    }
250                    Some(info @ FragmentInfo::Literal { .. }) => {
251                        let bytes = fragmentizer.fragment_bytes(info);
252                        trace!("read literal fragment ({} bytes)", bytes.len());
253                    }
254                    None if self.done => {
255                        // NOTE: message always exists during a resume cycle.
256                        return ImapSendResult::Ok(Box::new(ImapSendOutput {
257                            message: self.message.take().unwrap(),
258                            data: mem::take(&mut self.data),
259                            untagged: mem::take(&mut self.untagged),
260                            tagged: self.tagged.take(),
261                            bye: self.bye.take(),
262                            continuation_request: self.cr.take(),
263                        }));
264                    }
265                    None if self.limbo_literal.is_some() => {
266                        self.state = State::Serialize;
267                    }
268                    None => {
269                        self.state = State::Read;
270                    }
271                },
272            }
273        }
274    }
275}
276
277/// Successful output of one command exchange: the echoed message plus
278/// everything the server answered before the tagged response.
279#[derive(Debug)]
280pub struct ImapSendOutput<T: Encoder> {
281    /// The sent message, echoed back to the caller.
282    pub message: T::Message<'static>,
283    /// The untagged data responses collected during the exchange.
284    pub data: Vec<Data<'static>>,
285    /// The untagged status responses collected during the exchange.
286    pub untagged: Vec<StatusBody<'static>>,
287    /// The tagged response terminating the exchange, when one arrived.
288    pub tagged: Option<Tagged<'static>>,
289    /// The BYE response, when the server closed the session instead.
290    pub bye: Option<Bye<'static>>,
291    /// The continuation request that paused the exchange, when the
292    /// server asked for more data.
293    pub continuation_request: Option<CommandContinuationRequest<'static>>,
294}
295
296impl<T: Encoder> ImapCoroutine for ImapSend<T> {
297    type Yield = ImapYield;
298    type Return = Result<ImapSendOutput<T>, ImapSendError>;
299
300    fn resume(
301        &mut self,
302        fragmentizer: &mut Fragmentizer,
303        arg: Option<&[u8]>,
304    ) -> ImapCoroutineState<Self::Yield, Self::Return> {
305        // NOTE: qualified path avoids recursing into this trait impl.
306        match ImapSend::<T>::resume(self, fragmentizer, arg) {
307            ImapSendResult::WantsRead => ImapCoroutineState::Yielded(ImapYield::WantsRead),
308            ImapSendResult::WantsWrite(bytes) => {
309                ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes))
310            }
311            ImapSendResult::Ok(output) => ImapCoroutineState::Complete(Ok(*output)),
312            ImapSendResult::Err(err) => ImapCoroutineState::Complete(Err(err)),
313        }
314    }
315}