Skip to main content

io_imap/rfc3501/
greeting.rs

1//! IMAP server greeting reader; optionally forces a CAPABILITY round-trip
2//! if the greeting carries none.
3//!
4//! # Example
5//!
6//! ```rust,no_run
7//! use std::{
8//!     io::{Read, Write},
9//!     net::TcpStream,
10//! };
11//!
12//! use io_imap::{
13//!     codec::fragmentizer::Fragmentizer,
14//!     coroutine::{ImapCoroutine, ImapCoroutineState, ImapYield},
15//!     rfc3501::greeting::{ImapGreetingGet, ImapGreetingGetOptions},
16//! };
17//!
18//! // Ready stream needed (TCP-connected, TLS-negociated)
19//! let mut stream = TcpStream::connect("localhost:143").unwrap();
20//!
21//! let mut fragmentizer = Fragmentizer::new(50 * 1024 * 1024);
22//! let mut buf = [0u8; 4096];
23//!
24//! let opts = ImapGreetingGetOptions {
25//!     ensure_capabilities: true,
26//! };
27//! let mut coroutine = ImapGreetingGet::new(opts);
28//! let mut arg = None;
29//!
30//! let greeting = loop {
31//!     match coroutine.resume(&mut fragmentizer, arg.take()) {
32//!         ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
33//!             stream.write_all(&bytes).unwrap();
34//!         }
35//!         ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
36//!             let n = stream.read(&mut buf).unwrap();
37//!             arg = Some(&buf[..n]);
38//!         }
39//!         ImapCoroutineState::Complete(Ok(greeting)) => break greeting,
40//!         ImapCoroutineState::Complete(Err(err)) => panic!("{err}"),
41//!     }
42//! };
43//!
44//! println!("{greeting:?}");
45//! ```
46
47use core::{fmt, mem};
48
49use alloc::{boxed::Box, string::String, string::ToString, vec::Vec};
50
51use imap_codec::{
52    GreetingCodec,
53    fragmentizer::{DecodeMessageError, FragmentInfo, Fragmentizer},
54    imap_types::{
55        IntoStatic,
56        response::{Capability, Code, GreetingKind},
57        secret::Secret,
58        utils::escape_byte_string,
59    },
60};
61use log::trace;
62use thiserror::Error;
63
64use crate::{coroutine::*, imap_try, rfc3501::capability::*};
65
66/// Failure causes while reading the IMAP greeting.
67#[derive(Clone, Debug, Error)]
68pub enum ImapGreetingGetError {
69    #[error("IMAP greeting failed: BYE {0}")]
70    Bye(String),
71
72    #[error("IMAP greeting failed: reached unexpected EOF on stream")]
73    Eof,
74    #[error("IMAP greeting failed: decode error")]
75    DecodingFailure(Secret<Box<[u8]>>),
76    #[error("IMAP greeting failed: parse error: message is poisoned")]
77    MessageIsPoisoned(Secret<Box<[u8]>>),
78    #[error("IMAP greeting failed: parse error: message is too long")]
79    MessageTooLong(Secret<Box<[u8]>>),
80
81    #[error(transparent)]
82    Capability(#[from] ImapCapabilityGetError),
83}
84
85/// Decoded greeting outcome.
86#[derive(Debug)]
87pub struct ImapGreetingOk {
88    pub capability: Vec<Capability<'static>>,
89    pub pre_authenticated: bool,
90}
91
92/// Options for [`ImapGreetingGet::new`].
93#[derive(Clone, Debug, Default, Eq, PartialEq)]
94pub struct ImapGreetingGetOptions {
95    /// Fetch capabilities explicitly when the greeting carries none.
96    pub ensure_capabilities: bool,
97}
98
99/// I/O-free IMAP greeting-read coroutine.
100pub struct ImapGreetingGet {
101    codec: GreetingCodec,
102    state: State,
103    wants_read: bool,
104    observed: Vec<Capability<'static>>,
105    pre_authenticated: bool,
106    opts: ImapGreetingGetOptions,
107}
108
109impl ImapGreetingGet {
110    pub fn new(opts: ImapGreetingGetOptions) -> Self {
111        Self {
112            codec: GreetingCodec::new(),
113            state: State::Read,
114            wants_read: false,
115            observed: Vec::new(),
116            pre_authenticated: false,
117            opts,
118        }
119    }
120}
121
122impl ImapCoroutine for ImapGreetingGet {
123    type Yield = ImapYield;
124    type Return = Result<ImapGreetingOk, ImapGreetingGetError>;
125
126    fn resume(
127        &mut self,
128        fragmentizer: &mut Fragmentizer,
129        mut arg: Option<&[u8]>,
130    ) -> ImapCoroutineState<Self::Yield, Self::Return> {
131        loop {
132            trace!("greeting: {}", self.state);
133
134            if mem::take(&mut self.wants_read) {
135                return ImapCoroutineState::Yielded(ImapYield::WantsRead);
136            }
137
138            match &mut self.state {
139                State::Read => match arg.take() {
140                    Some(&[]) => {
141                        return ImapCoroutineState::Complete(Err(ImapGreetingGetError::Eof));
142                    }
143                    Some(bytes) => {
144                        trace!("read bytes: {}", escape_byte_string(bytes));
145                        fragmentizer.enqueue_bytes(bytes);
146                        self.state = State::Deserialize;
147                    }
148                    None => {
149                        self.wants_read = true;
150                    }
151                },
152                State::Deserialize => match fragmentizer.progress() {
153                    Some(info @ FragmentInfo::Line { .. }) => {
154                        let bytes = fragmentizer.fragment_bytes(info);
155                        trace!("read greeting line: {}", escape_byte_string(bytes));
156
157                        if !fragmentizer.is_message_complete() {
158                            continue;
159                        }
160
161                        match fragmentizer.decode_message(&self.codec) {
162                            Ok(greeting) if greeting.kind == GreetingKind::Bye => {
163                                let err = ImapGreetingGetError::Bye(greeting.text.to_string());
164                                return ImapCoroutineState::Complete(Err(err));
165                            }
166                            Ok(greeting) => {
167                                self.pre_authenticated = greeting.kind == GreetingKind::PreAuth;
168
169                                if let Some(Code::Capability(capability)) = greeting.code {
170                                    self.observed = capability.into_static().into_iter().collect();
171                                }
172
173                                if self.opts.ensure_capabilities && self.observed.is_empty() {
174                                    self.state = State::Capability(ImapCapabilityGet::new());
175                                    continue;
176                                }
177
178                                return ImapCoroutineState::Complete(Ok(ImapGreetingOk {
179                                    capability: mem::take(&mut self.observed),
180                                    pre_authenticated: self.pre_authenticated,
181                                }));
182                            }
183                            Err(err) => {
184                                let bytes = fragmentizer.message_bytes();
185                                let bytes = Secret::new(bytes.into());
186                                let err = match err {
187                                    DecodeMessageError::DecodingFailure(_)
188                                    | DecodeMessageError::DecodingRemainder { .. } => {
189                                        ImapGreetingGetError::DecodingFailure(bytes)
190                                    }
191                                    DecodeMessageError::MessageTooLong { .. } => {
192                                        ImapGreetingGetError::MessageTooLong(bytes)
193                                    }
194                                    DecodeMessageError::MessagePoisoned { .. } => {
195                                        ImapGreetingGetError::MessageIsPoisoned(bytes)
196                                    }
197                                };
198                                return ImapCoroutineState::Complete(Err(err));
199                            }
200                        }
201                    }
202                    // NOTE: greetings never carry literals.
203                    Some(FragmentInfo::Literal { .. }) => unreachable!(),
204                    None => {
205                        self.state = State::Read;
206                    }
207                },
208                State::Capability(capability) => {
209                    let caps = imap_try!(capability, fragmentizer, arg.take());
210                    return ImapCoroutineState::Complete(Ok(ImapGreetingOk {
211                        capability: caps,
212                        pre_authenticated: self.pre_authenticated,
213                    }));
214                }
215            }
216        }
217    }
218}
219
220enum State {
221    Read,
222    Deserialize,
223    Capability(ImapCapabilityGet),
224}
225
226impl fmt::Display for State {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        match self {
229            Self::Read => f.write_str("read greeting"),
230            Self::Deserialize => f.write_str("decode greeting"),
231            Self::Capability(_) => f.write_str("fetch capabilities"),
232        }
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use alloc::vec::Vec;
239
240    use super::*;
241
242    #[test]
243    fn ok_with_inline_capability_returns_ok() {
244        let mut greeting = ImapGreetingGet::new(ImapGreetingGetOptions {
245            ensure_capabilities: true,
246        });
247        let mut frag = Fragmentizer::new(50 * 1024 * 1024);
248
249        expect_wants_read(&mut greeting, &mut frag);
250
251        let reply = b"* OK [CAPABILITY IMAP4REV1 IDLE] hello\r\n";
252        let ok = expect_complete_ok(&mut greeting, &mut frag, reply);
253        assert!(!ok.pre_authenticated);
254        assert_eq!(2, ok.capability.len());
255    }
256
257    #[test]
258    fn ok_without_inline_capability_drives_extra_round_trip() {
259        let mut greeting = ImapGreetingGet::new(ImapGreetingGetOptions {
260            ensure_capabilities: true,
261        });
262        let mut frag = Fragmentizer::new(50 * 1024 * 1024);
263
264        expect_wants_read(&mut greeting, &mut frag);
265        expect_wants_write_after(&mut greeting, &mut frag, b"* OK hello\r\n");
266    }
267
268    #[test]
269    fn preauth_sets_flag() {
270        let mut greeting = ImapGreetingGet::new(ImapGreetingGetOptions::default());
271        let mut frag = Fragmentizer::new(50 * 1024 * 1024);
272
273        expect_wants_read(&mut greeting, &mut frag);
274
275        let reply = b"* PREAUTH [CAPABILITY IMAP4REV1] welcome\r\n";
276        let ok = expect_complete_ok(&mut greeting, &mut frag, reply);
277        assert!(ok.pre_authenticated);
278    }
279
280    #[test]
281    fn bye_returns_bye_error() {
282        let mut greeting = ImapGreetingGet::new(ImapGreetingGetOptions::default());
283        let mut frag = Fragmentizer::new(50 * 1024 * 1024);
284
285        expect_wants_read(&mut greeting, &mut frag);
286
287        let err = expect_complete_err(&mut greeting, &mut frag, b"* BYE service unavailable\r\n");
288        let ImapGreetingGetError::Bye(text) = err else {
289            panic!("expected ImapGreetingGetError::Bye, got {err:?}");
290        };
291        assert_eq!(text, "service unavailable");
292    }
293
294    #[test]
295    fn eof_returns_eof_error() {
296        let mut greeting = ImapGreetingGet::new(ImapGreetingGetOptions::default());
297        let mut frag = Fragmentizer::new(50 * 1024 * 1024);
298
299        expect_wants_read(&mut greeting, &mut frag);
300
301        let err = expect_complete_err(&mut greeting, &mut frag, b"");
302        assert!(matches!(err, ImapGreetingGetError::Eof));
303    }
304
305    // --- utils
306
307    fn expect_wants_read(cor: &mut ImapGreetingGet, frag: &mut Fragmentizer) {
308        match cor.resume(frag, None) {
309            ImapCoroutineState::Yielded(ImapYield::WantsRead) => {}
310            state => panic!("expected WantsRead, got {state:?}"),
311        }
312    }
313
314    fn expect_wants_write_after(
315        cor: &mut ImapGreetingGet,
316        frag: &mut Fragmentizer,
317        arg: &[u8],
318    ) -> Vec<u8> {
319        match cor.resume(frag, Some(arg)) {
320            ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => bytes,
321            state => panic!("expected WantsWrite, got {state:?}"),
322        }
323    }
324
325    fn expect_complete_ok(
326        cor: &mut ImapGreetingGet,
327        frag: &mut Fragmentizer,
328        reply: &[u8],
329    ) -> ImapGreetingOk {
330        match cor.resume(frag, Some(reply)) {
331            ImapCoroutineState::Complete(Ok(value)) => value,
332            state => panic!("expected Complete(Ok), got {state:?}"),
333        }
334    }
335
336    fn expect_complete_err(
337        cor: &mut ImapGreetingGet,
338        frag: &mut Fragmentizer,
339        reply: &[u8],
340    ) -> ImapGreetingGetError {
341        match cor.resume(frag, Some(reply)) {
342            ImapCoroutineState::Complete(Err(err)) => err,
343            state => panic!("expected Complete(Err), got {state:?}"),
344        }
345    }
346}