Skip to main content

io_imap/
send.rs

1//! Base coroutine that all higher-level IMAP coroutines delegate to:
2//! serialises a command via `imap_codec`, drives read/write, and feeds
3//! responses back through the borrowed `Fragmentizer`.
4
5use core::mem;
6
7use alloc::{boxed::Box, collections::VecDeque, vec::Vec};
8
9use imap_codec::{
10    ResponseCodec,
11    encode::{Encoder, Fragment},
12    fragmentizer::{DecodeMessageError, FragmentInfo, Fragmentizer},
13    imap_types::{
14        IntoStatic,
15        core::LiteralMode,
16        response::{Bye, CommandContinuationRequest, Data, Response, Status, StatusBody, Tagged},
17        secret::Secret,
18        utils::escape_byte_string,
19    },
20};
21use log::trace;
22use thiserror::Error;
23
24use crate::coroutine::{ImapCoroutine, ImapCoroutineState, ImapYield};
25
26/// Failure causes raised by [`SendImapCommand`].
27#[derive(Clone, Debug, Error)]
28pub enum SendImapCommandError {
29    #[error("Reached unexpected EOF on IMAP stream")]
30    Eof,
31    #[error("Decode IMAP response error")]
32    DecodingFailure(Secret<Box<[u8]>>),
33    #[error("Parse IMAP response error: message is poisoned")]
34    MessageIsPoisoned(Secret<Box<[u8]>>),
35    #[error("Parse IMAP response error: message is too long")]
36    MessageTooLong(Secret<Box<[u8]>>),
37}
38
39/// Step output emitted by [`SendImapCommand::resume`].
40pub enum SendImapCommandResult<T: Encoder> {
41    Ok {
42        message: T::Message<'static>,
43        data: Vec<Data<'static>>,
44        untagged: Vec<StatusBody<'static>>,
45        tagged: Option<Tagged<'static>>,
46        bye: Option<Bye<'static>>,
47        continuation_request: Option<CommandContinuationRequest<'static>>,
48    },
49    WantsRead,
50    WantsWrite(Vec<u8>),
51    Err(SendImapCommandError),
52}
53
54#[derive(Debug)]
55enum State {
56    Serialize,
57    Read,
58    Deserialize,
59}
60
61/// I/O-free coroutine sending one IMAP command and parsing its response.
62pub struct SendImapCommand<T: Encoder> {
63    message: Option<T::Message<'static>>,
64    state: State,
65    wants_read: bool,
66    wants_write: Option<Vec<u8>>,
67    fragments: VecDeque<Fragment>,
68    codec: ResponseCodec,
69    data: Vec<Data<'static>>,
70    untagged: Vec<StatusBody<'static>>,
71    tagged: Option<Tagged<'static>>,
72    bye: Option<Bye<'static>>,
73    cr: Option<CommandContinuationRequest<'static>>,
74    limbo_literal: Option<Vec<u8>>,
75    done: bool,
76}
77
78impl<T: Encoder> SendImapCommand<T> {
79    pub fn new(encoder: T, message: T::Message<'static>) -> Self {
80        let fragments = encoder.encode(&message).collect();
81
82        Self {
83            message: Some(message),
84            codec: ResponseCodec::new(),
85            state: State::Serialize,
86            wants_read: false,
87            wants_write: None,
88            fragments,
89            data: Vec::new(),
90            untagged: Vec::new(),
91            tagged: None,
92            bye: None,
93            cr: None,
94            limbo_literal: None,
95            done: false,
96        }
97    }
98
99    /// Pass `None` initially or after `WantsWrite`, `Some(bytes)`
100    /// after `WantsRead`, `Some(&[])` on EOF.
101    pub fn resume(
102        &mut self,
103        fragmentizer: &mut Fragmentizer,
104        mut arg: Option<&[u8]>,
105    ) -> SendImapCommandResult<T> {
106        loop {
107            if let Some(bytes) = self.wants_write.take() {
108                return SendImapCommandResult::WantsWrite(bytes);
109            }
110
111            if mem::take(&mut self.wants_read) {
112                return SendImapCommandResult::WantsRead;
113            }
114
115            match self.state {
116                State::Serialize => {
117                    let mut buf = Vec::new();
118
119                    if let Some(bytes) = self.limbo_literal.take() {
120                        buf.extend(bytes);
121                    }
122
123                    while let Some(fragment) = self.fragments.pop_front() {
124                        match fragment {
125                            Fragment::Line { data } => {
126                                buf.extend(data);
127                            }
128                            Fragment::Literal { data, mode } => match mode {
129                                LiteralMode::NonSync => {
130                                    buf.extend(data);
131                                }
132                                LiteralMode::Sync => {
133                                    self.limbo_literal.replace(data);
134                                    break;
135                                }
136                            },
137                        }
138                    }
139
140                    if !buf.is_empty() {
141                        self.wants_write = Some(buf);
142                    }
143                    self.state = State::Read;
144                }
145                State::Read => match arg.take() {
146                    Some(&[]) => {
147                        return SendImapCommandResult::Err(SendImapCommandError::Eof);
148                    }
149                    Some(data) => {
150                        trace!("read bytes: {}", escape_byte_string(data));
151                        fragmentizer.enqueue_bytes(data);
152                        self.state = State::Deserialize;
153                    }
154                    None => {
155                        self.wants_read = true;
156                    }
157                },
158                State::Deserialize => match fragmentizer.progress() {
159                    Some(info @ FragmentInfo::Line { .. }) => {
160                        let bytes = fragmentizer.fragment_bytes(info);
161                        trace!("read line fragment: {}", escape_byte_string(bytes));
162
163                        if !fragmentizer.is_message_complete() {
164                            continue;
165                        }
166
167                        match fragmentizer.decode_message(&self.codec) {
168                            Ok(Response::Data(data)) => {
169                                self.data.push(data.into_static());
170                            }
171                            Ok(Response::Status(Status::Untagged(status))) => {
172                                self.untagged.push(status.into_static());
173                            }
174                            Ok(Response::Status(Status::Tagged(tagged))) => {
175                                self.tagged.replace(tagged.into_static());
176                                self.done = true;
177                            }
178                            Ok(Response::Status(Status::Bye(bye))) => {
179                                self.bye.replace(bye.into_static());
180                                self.done = true;
181                            }
182                            Ok(Response::CommandContinuationRequest(cr)) => {
183                                self.cr.replace(cr.into_static());
184                                self.done = self.limbo_literal.is_none();
185                            }
186                            Err(decode_err) => {
187                                let bytes = fragmentizer.message_bytes();
188                                let bytes = Secret::new(bytes.into());
189                                let err = match decode_err {
190                                    DecodeMessageError::DecodingFailure(_)
191                                    | DecodeMessageError::DecodingRemainder { .. } => {
192                                        SendImapCommandError::DecodingFailure(bytes)
193                                    }
194                                    DecodeMessageError::MessageTooLong { .. } => {
195                                        SendImapCommandError::MessageTooLong(bytes)
196                                    }
197                                    DecodeMessageError::MessagePoisoned { .. } => {
198                                        SendImapCommandError::MessageIsPoisoned(bytes)
199                                    }
200                                };
201                                return SendImapCommandResult::Err(err);
202                            }
203                        }
204                    }
205                    Some(info @ FragmentInfo::Literal { .. }) => {
206                        let bytes = fragmentizer.fragment_bytes(info);
207                        trace!("read literal fragment ({} bytes)", bytes.len());
208                    }
209                    None if self.done => {
210                        // SAFETY: message always exists during a resume cycle
211                        return SendImapCommandResult::Ok {
212                            message: self.message.take().unwrap(),
213                            data: mem::take(&mut self.data),
214                            untagged: mem::take(&mut self.untagged),
215                            tagged: self.tagged.take(),
216                            bye: self.bye.take(),
217                            continuation_request: self.cr.take(),
218                        };
219                    }
220                    None if self.limbo_literal.is_some() => {
221                        self.state = State::Serialize;
222                    }
223                    None => {
224                        self.state = State::Read;
225                    }
226                },
227            }
228        }
229    }
230}
231
232/// Trait-surface successful output: mirror of [`SendImapCommandResult::Ok`].
233#[derive(Debug)]
234pub struct SendImapCommandOk<T: Encoder> {
235    pub message: T::Message<'static>,
236    pub data: Vec<Data<'static>>,
237    pub untagged: Vec<StatusBody<'static>>,
238    pub tagged: Option<Tagged<'static>>,
239    pub bye: Option<Bye<'static>>,
240    pub continuation_request: Option<CommandContinuationRequest<'static>>,
241}
242
243impl<T: Encoder> ImapCoroutine for SendImapCommand<T> {
244    type Yield = ImapYield;
245    type Return = Result<SendImapCommandOk<T>, SendImapCommandError>;
246
247    fn resume(
248        &mut self,
249        fragmentizer: &mut Fragmentizer,
250        arg: Option<&[u8]>,
251    ) -> ImapCoroutineState<Self::Yield, Self::Return> {
252        // NOTE: qualified path avoids recursing into this trait impl.
253        match SendImapCommand::<T>::resume(self, fragmentizer, arg) {
254            SendImapCommandResult::WantsRead => ImapCoroutineState::Yielded(ImapYield::WantsRead),
255            SendImapCommandResult::WantsWrite(bytes) => {
256                ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes))
257            }
258            SendImapCommandResult::Ok {
259                message,
260                data,
261                untagged,
262                tagged,
263                bye,
264                continuation_request,
265            } => ImapCoroutineState::Complete(Ok(SendImapCommandOk {
266                message,
267                data,
268                untagged,
269                tagged,
270                bye,
271                continuation_request,
272            })),
273            SendImapCommandResult::Err(err) => ImapCoroutineState::Complete(Err(err)),
274        }
275    }
276}