imap_next/
client.rs

1use std::fmt::{Debug, Formatter};
2
3use imap_codec::{
4    imap_types::{
5        auth::AuthenticateData,
6        command::Command,
7        response::{CommandContinuationRequest, Data, Greeting, Response, Status},
8        secret::Secret,
9    },
10    AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec,
11};
12use thiserror::Error;
13
14use crate::{
15    client_send::{ClientSendEvent, ClientSendState, ClientSendTermination},
16    handle::{Handle, HandleGenerator, HandleGeneratorGenerator, RawHandle},
17    receive::{ReceiveError, ReceiveEvent, ReceiveState},
18    types::CommandAuthenticate,
19    Interrupt, State,
20};
21
22static HANDLE_GENERATOR_GENERATOR: HandleGeneratorGenerator<CommandHandle> =
23    HandleGeneratorGenerator::new();
24
25#[derive(Clone, Debug, PartialEq)]
26#[non_exhaustive]
27pub struct Options {
28    pub crlf_relaxed: bool,
29
30    /// Max response size that can be parsed by the client.
31    ///
32    /// Bigger responses raise an error.
33    pub max_response_size: u32,
34
35    /// Forces the client not to handle greetings.
36    ///
37    /// If `true`, the next expected message is directly set to
38    /// `NextExpectedMessage::Response`. This is particularly useful
39    /// when greetings are processed outside of the [`Client`] scope
40    /// (e.g. custom STARTTLS).
41    pub discard_greeting: bool,
42}
43
44#[allow(clippy::derivable_impls)]
45impl Default for Options {
46    fn default() -> Self {
47        Self {
48            // Lean towards conformity
49            crlf_relaxed: false,
50            // We use a value larger than the default `server::Options::max_command_size`
51            // because we assume that a client has usually less resource constraints than the
52            // server.
53            max_response_size: 100 * 1024 * 1024,
54            // Process greetings by default
55            discard_greeting: false,
56        }
57    }
58}
59
60pub struct Client {
61    handle_generator: HandleGenerator<CommandHandle>,
62    send_state: ClientSendState,
63    receive_state: ReceiveState,
64    next_expected_message: NextExpectedMessage,
65}
66
67impl Client {
68    pub fn new(options: Options) -> Self {
69        let send_state = ClientSendState::new(
70            CommandCodec::default(),
71            AuthenticateDataCodec::default(),
72            IdleDoneCodec::default(),
73        );
74
75        let receive_state =
76            ReceiveState::new(options.crlf_relaxed, Some(options.max_response_size));
77        let next_expected_message = if options.discard_greeting {
78            NextExpectedMessage::Response(ResponseCodec::default())
79        } else {
80            NextExpectedMessage::Greeting(GreetingCodec::default())
81        };
82
83        Self {
84            handle_generator: HANDLE_GENERATOR_GENERATOR.generate(),
85            send_state,
86            receive_state,
87            next_expected_message,
88        }
89    }
90
91    /// Enqueues the [`Command`] for being sent to the client.
92    ///
93    /// The [`Command`] is not sent immediately but during one of the next calls of
94    /// [`Client::next`]. All [`Command`]s are sent in the same order they have been
95    /// enqueued.
96    pub fn enqueue_command(&mut self, command: Command<'static>) -> CommandHandle {
97        let handle = self.handle_generator.generate();
98        self.send_state.enqueue_command(handle, command);
99        handle
100    }
101
102    fn progress_send(&mut self) -> Result<Option<Event>, Interrupt<Error>> {
103        // Abort if we didn't received the greeting yet
104        if let NextExpectedMessage::Greeting(_) = &self.next_expected_message {
105            return Ok(None);
106        }
107
108        match self.send_state.next() {
109            Ok(Some(ClientSendEvent::Command { handle, command })) => {
110                Ok(Some(Event::CommandSent { handle, command }))
111            }
112            Ok(Some(ClientSendEvent::Authenticate { handle })) => {
113                Ok(Some(Event::AuthenticateStarted { handle }))
114            }
115            Ok(Some(ClientSendEvent::Idle { handle })) => {
116                Ok(Some(Event::IdleCommandSent { handle }))
117            }
118            Ok(Some(ClientSendEvent::IdleDone { handle })) => {
119                Ok(Some(Event::IdleDoneSent { handle }))
120            }
121            Ok(None) => Ok(None),
122            Err(Interrupt::Io(io)) => Err(Interrupt::Io(io)),
123            Err(Interrupt::Error(_)) => unreachable!(),
124        }
125    }
126
127    fn progress_receive(&mut self) -> Result<Option<Event>, Interrupt<Error>> {
128        let event = loop {
129            match &self.next_expected_message {
130                NextExpectedMessage::Greeting(codec) => {
131                    match self.receive_state.next::<GreetingCodec>(codec) {
132                        Ok(ReceiveEvent::DecodingSuccess(greeting)) => {
133                            self.next_expected_message =
134                                NextExpectedMessage::Response(ResponseCodec::default());
135                            break Some(Event::GreetingReceived { greeting });
136                        }
137                        Ok(ReceiveEvent::LiteralAnnouncement { .. }) => {
138                            // Unexpected literal, let's continue and see what happens
139                            continue;
140                        }
141                        Err(interrupt) => return Err(handle_receive_interrupt(interrupt)),
142                    }
143                }
144                NextExpectedMessage::Response(codec) => {
145                    let response = match self.receive_state.next::<ResponseCodec>(codec) {
146                        Ok(ReceiveEvent::DecodingSuccess(response)) => response,
147                        Ok(ReceiveEvent::LiteralAnnouncement { .. }) => {
148                            // The client must accept the literal in any case.
149                            continue;
150                        }
151                        Err(interrupt) => return Err(handle_receive_interrupt(interrupt)),
152                    };
153
154                    match response {
155                        Response::Status(status) => {
156                            let event = if let Some(finish_result) =
157                                self.send_state.maybe_terminate(&status)
158                            {
159                                match finish_result {
160                                    ClientSendTermination::LiteralRejected { handle, command } => {
161                                        Event::CommandRejected {
162                                            handle,
163                                            command,
164                                            status,
165                                        }
166                                    }
167                                    ClientSendTermination::AuthenticateAccepted {
168                                        handle,
169                                        command_authenticate,
170                                    }
171                                    | ClientSendTermination::AuthenticateRejected {
172                                        handle,
173                                        command_authenticate,
174                                    } => Event::AuthenticateStatusReceived {
175                                        handle,
176                                        command_authenticate,
177                                        status,
178                                    },
179                                    ClientSendTermination::IdleRejected { handle } => {
180                                        Event::IdleRejected { handle, status }
181                                    }
182                                }
183                            } else {
184                                Event::StatusReceived { status }
185                            };
186
187                            break Some(event);
188                        }
189                        Response::Data(data) => break Some(Event::DataReceived { data }),
190                        Response::CommandContinuationRequest(continuation_request) => {
191                            if self.send_state.literal_continue() {
192                                // We received a continuation request that was necessary for
193                                // sending a command. So we abort receiving responses for now
194                                // and continue with sending commands.
195                                break None;
196                            } else if let Some(handle) = self.send_state.authenticate_continue() {
197                                break Some(Event::AuthenticateContinuationRequestReceived {
198                                    handle,
199                                    continuation_request,
200                                });
201                            } else if let Some(handle) = self.send_state.idle_continue() {
202                                break Some(Event::IdleAccepted {
203                                    handle,
204                                    continuation_request,
205                                });
206                            } else {
207                                break Some(Event::ContinuationRequestReceived {
208                                    continuation_request,
209                                });
210                            }
211                        }
212                    }
213                }
214            }
215        };
216
217        Ok(event)
218    }
219
220    pub fn set_authenticate_data(
221        &mut self,
222        authenticate_data: AuthenticateData<'static>,
223    ) -> Result<CommandHandle, AuthenticateData<'static>> {
224        self.send_state.set_authenticate_data(authenticate_data)
225    }
226
227    pub fn set_idle_done(&mut self) -> Option<CommandHandle> {
228        self.send_state.set_idle_done()
229    }
230}
231
232impl Debug for Client {
233    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
234        f.debug_struct("Client")
235            .field("handle_generator", &self.handle_generator)
236            .finish_non_exhaustive()
237    }
238}
239
240impl State for Client {
241    type Event = Event;
242    type Error = Error;
243
244    fn enqueue_input(&mut self, bytes: &[u8]) {
245        self.receive_state.enqueue_input(bytes);
246    }
247
248    fn next(&mut self) -> Result<Self::Event, Interrupt<Self::Error>> {
249        loop {
250            if let Some(event) = self.progress_send()? {
251                return Ok(event);
252            }
253
254            if let Some(event) = self.progress_receive()? {
255                return Ok(event);
256            }
257        }
258    }
259}
260
261fn handle_receive_interrupt(interrupt: Interrupt<ReceiveError>) -> Interrupt<Error> {
262    match interrupt {
263        Interrupt::Io(io) => Interrupt::Io(io),
264        Interrupt::Error(ReceiveError::DecodingFailure { discarded_bytes }) => {
265            Interrupt::Error(Error::MalformedMessage { discarded_bytes })
266        }
267        Interrupt::Error(ReceiveError::ExpectedCrlfGotLf { discarded_bytes }) => {
268            Interrupt::Error(Error::ExpectedCrlfGotLf { discarded_bytes })
269        }
270        Interrupt::Error(ReceiveError::MessageIsPoisoned { .. }) => {
271            // Unreachable because we don't poison messages
272            unreachable!()
273        }
274        Interrupt::Error(ReceiveError::MessageTooLong { discarded_bytes }) => {
275            Interrupt::Error(Error::ResponseTooLong { discarded_bytes })
276        }
277    }
278}
279
280/// Handle for enqueued [`Command`].
281///
282/// This handle can be used to track the sending progress. After a [`Command`] was enqueued via
283/// [`Client::enqueue_command`] it is in the process of being sent until [`Client::next`] returns
284/// a [`Event::CommandSent`] or [`Event::CommandRejected`] with the corresponding handle.
285#[derive(Clone, Copy, Eq, PartialEq, Hash)]
286pub struct CommandHandle(RawHandle);
287
288/// Debug representation hiding the raw handle.
289impl Debug for CommandHandle {
290    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
291        f.debug_tuple("CommandHandle")
292            .field(&self.0.generator_id())
293            .field(&self.0.handle_id())
294            .finish()
295    }
296}
297
298impl Handle for CommandHandle {
299    fn from_raw(handle: RawHandle) -> Self {
300        Self(handle)
301    }
302}
303
304#[derive(Debug)]
305pub enum Event {
306    /// [`Greeting`] received.
307    GreetingReceived { greeting: Greeting<'static> },
308    /// [`Command`] sent completely.
309    CommandSent {
310        /// Handle to the enqueued [`Command`].
311        handle: CommandHandle,
312        /// Formerly enqueued [`Command`].
313        command: Command<'static>,
314    },
315    /// [`Command`] rejected due to literal.
316    CommandRejected {
317        /// Handle to enqueued [`Command`].
318        handle: CommandHandle,
319        /// Formerly enqueued [`Command`].
320        command: Command<'static>,
321        /// [`Status`] sent by the server to reject the [`Command`].
322        ///
323        /// Note: [`Client`] already handled this [`Status`] but it might still have
324        /// useful information that could be logged or displayed to the user
325        /// (e.g. [`Code::Alert`](crate::imap_types::response::Code::Alert)).
326        status: Status<'static>,
327    },
328    /// AUTHENTICATE sent.
329    AuthenticateStarted { handle: CommandHandle },
330    /// Server requests (more) authentication data.
331    ///
332    /// The client MUST call [`Client::set_authenticate_data`] next.
333    ///
334    /// Note: The client can also progress the authentication by sending [`AuthenticateData::Cancel`].
335    /// However, it's up to the server to abort the authentication flow by sending a tagged status response.
336    AuthenticateContinuationRequestReceived {
337        /// Handle to the enqueued [`Command`].
338        handle: CommandHandle,
339        continuation_request: CommandContinuationRequest<'static>,
340    },
341    /// [`Status`] received to authenticate command.
342    AuthenticateStatusReceived {
343        handle: CommandHandle,
344        command_authenticate: CommandAuthenticate,
345        status: Status<'static>,
346    },
347    /// IDLE sent.
348    IdleCommandSent { handle: CommandHandle },
349    /// IDLE accepted by server. Entering IDLE state.
350    IdleAccepted {
351        handle: CommandHandle,
352        continuation_request: CommandContinuationRequest<'static>,
353    },
354    /// IDLE rejected by server.
355    IdleRejected {
356        handle: CommandHandle,
357        status: Status<'static>,
358    },
359    /// DONE sent. Exiting IDLE state.
360    IdleDoneSent { handle: CommandHandle },
361    /// Server [`Data`] received.
362    DataReceived { data: Data<'static> },
363    /// Server [`Status`] received.
364    StatusReceived { status: Status<'static> },
365    /// Server [`CommandContinuationRequest`] response received.
366    ///
367    /// Note: The received continuation request was not part of [`Client`] handling.
368    ContinuationRequestReceived {
369        continuation_request: CommandContinuationRequest<'static>,
370    },
371}
372
373#[derive(Debug, Error)]
374pub enum Error {
375    #[error("Expected `\\r\\n`, got `\\n`")]
376    ExpectedCrlfGotLf { discarded_bytes: Secret<Box<[u8]>> },
377    #[error("Received malformed message")]
378    MalformedMessage { discarded_bytes: Secret<Box<[u8]>> },
379    #[error("Response is too long")]
380    ResponseTooLong { discarded_bytes: Secret<Box<[u8]>> },
381}
382
383#[derive(Clone, Debug)]
384enum NextExpectedMessage {
385    Greeting(GreetingCodec),
386    Response(ResponseCodec),
387}