imap_patch_for_async_imap_lite/extensions/
idle.rs

1//! Adds support for the IMAP IDLE command specificed in [RFC
2//! 2177](https://tools.ietf.org/html/rfc2177).
3
4use crate::client::Session;
5use crate::error::{Error, Result};
6#[cfg(feature = "tls")]
7use native_tls::TlsStream;
8use std::io::{self, Read, Write};
9use std::net::TcpStream;
10use std::time::Duration;
11
12/// `Handle` allows a client to block waiting for changes to the remote mailbox.
13///
14/// The handle blocks using the [`IDLE` command](https://tools.ietf.org/html/rfc2177#section-3)
15/// specificed in [RFC 2177](https://tools.ietf.org/html/rfc2177) until the underlying server state
16/// changes in some way. While idling does inform the client what changes happened on the server,
17/// this implementation will currently just block until _anything_ changes, and then notify the
18///
19/// Note that the server MAY consider a client inactive if it has an IDLE command running, and if
20/// such a server has an inactivity timeout it MAY log the client off implicitly at the end of its
21/// timeout period.  Because of that, clients using IDLE are advised to terminate the IDLE and
22/// re-issue it at least every 29 minutes to avoid being logged off. [`Handle::wait_keepalive`]
23/// does this. This still allows a client to receive immediate mailbox updates even though it need
24/// only "poll" at half hour intervals.
25///
26/// As long as a [`Handle`] is active, the mailbox cannot be otherwise accessed.
27#[derive(Debug)]
28pub struct Handle<'a, T: Read + Write> {
29    session: &'a mut Session<T>,
30    keepalive: Duration,
31    done: bool,
32}
33
34/// The result of a wait on a [`Handle`]
35#[derive(Debug, PartialEq, Eq)]
36pub enum WaitOutcome {
37    /// The wait timed out
38    TimedOut,
39    /// The mailbox was modified
40    MailboxChanged,
41}
42
43/// Must be implemented for a transport in order for a `Session` using that transport to support
44/// operations with timeouts.
45///
46/// Examples of where this is useful is for `Handle::wait_keepalive` and
47/// `Handle::wait_timeout`.
48pub trait SetReadTimeout {
49    /// Set the timeout for subsequent reads to the given one.
50    ///
51    /// If `timeout` is `None`, the read timeout should be removed.
52    ///
53    /// See also `std::net::TcpStream::set_read_timeout`.
54    fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<()>;
55}
56
57impl<'a, T: Read + Write + 'a> Handle<'a, T> {
58    pub(crate) fn make(session: &'a mut Session<T>) -> Result<Self> {
59        let mut h = Handle {
60            session,
61            keepalive: Duration::from_secs(29 * 60),
62            done: false,
63        };
64        h.init()?;
65        Ok(h)
66    }
67
68    fn init(&mut self) -> Result<()> {
69        // https://tools.ietf.org/html/rfc2177
70        //
71        // The IDLE command takes no arguments.
72        self.session.run_command("IDLE")?;
73
74        // A tagged response will be sent either
75        //
76        //   a) if there's an error, or
77        //   b) *after* we send DONE
78        let mut v = Vec::new();
79        self.session.readline(&mut v)?;
80        if v.starts_with(b"+") {
81            self.done = false;
82            return Ok(());
83        }
84
85        self.session.read_response_onto(&mut v)?;
86        // We should *only* get a continuation on an error (i.e., it gives BAD or NO).
87        unreachable!();
88    }
89
90    fn terminate(&mut self) -> Result<()> {
91        if !self.done {
92            self.done = true;
93            self.session.write_line(b"DONE")?;
94            self.session.read_response().map(|_| ())
95        } else {
96            Ok(())
97        }
98    }
99
100    /// Internal helper that doesn't consume self.
101    ///
102    /// This is necessary so that we can keep using the inner `Session` in `wait_keepalive`.
103    fn wait_inner(&mut self, reconnect: bool) -> Result<WaitOutcome> {
104        let mut v = Vec::new();
105        loop {
106            let result = match self.session.readline(&mut v).map(|_| ()) {
107                Err(Error::Io(ref e))
108                    if e.kind() == io::ErrorKind::TimedOut
109                        || e.kind() == io::ErrorKind::WouldBlock =>
110                {
111                    if reconnect {
112                        self.terminate()?;
113                        self.init()?;
114                        return self.wait_inner(reconnect);
115                    }
116                    Ok(WaitOutcome::TimedOut)
117                }
118                Ok(()) => Ok(WaitOutcome::MailboxChanged),
119                Err(r) => Err(r),
120            }?;
121
122            // Handle Dovecot's imap_idle_notify_interval message
123            if v.eq_ignore_ascii_case(b"* OK Still here\r\n") {
124                v.clear();
125            } else {
126                break Ok(result);
127            }
128        }
129    }
130
131    /// Block until the selected mailbox changes.
132    pub fn wait(mut self) -> Result<()> {
133        self.wait_inner(true).map(|_| ())
134    }
135}
136
137impl<'a, T: SetReadTimeout + Read + Write + 'a> Handle<'a, T> {
138    /// Set the keep-alive interval to use when `wait_keepalive` is called.
139    ///
140    /// The interval defaults to 29 minutes as dictated by RFC 2177.
141    pub fn set_keepalive(&mut self, interval: Duration) {
142        self.keepalive = interval;
143    }
144
145    /// Block until the selected mailbox changes.
146    ///
147    /// This method differs from [`Handle::wait`] in that it will periodically refresh the IDLE
148    /// connection, to prevent the server from timing out our connection. The keepalive interval is
149    /// set to 29 minutes by default, as dictated by RFC 2177, but can be changed using
150    /// [`Handle::set_keepalive`].
151    ///
152    /// This is the recommended method to use for waiting.
153    pub fn wait_keepalive(self) -> Result<()> {
154        // The server MAY consider a client inactive if it has an IDLE command
155        // running, and if such a server has an inactivity timeout it MAY log
156        // the client off implicitly at the end of its timeout period.  Because
157        // of that, clients using IDLE are advised to terminate the IDLE and
158        // re-issue it at least every 29 minutes to avoid being logged off.
159        // This still allows a client to receive immediate mailbox updates even
160        // though it need only "poll" at half hour intervals.
161        let keepalive = self.keepalive;
162        self.timed_wait(keepalive, true).map(|_| ())
163    }
164
165    /// Block until the selected mailbox changes, or until the given amount of time has expired.
166    #[deprecated(note = "use wait_with_timeout instead")]
167    pub fn wait_timeout(self, timeout: Duration) -> Result<()> {
168        self.wait_with_timeout(timeout).map(|_| ())
169    }
170
171    /// Block until the selected mailbox changes, or until the given amount of time has expired.
172    pub fn wait_with_timeout(self, timeout: Duration) -> Result<WaitOutcome> {
173        self.timed_wait(timeout, false)
174    }
175
176    fn timed_wait(mut self, timeout: Duration, reconnect: bool) -> Result<WaitOutcome> {
177        self.session
178            .stream
179            .get_mut()
180            .set_read_timeout(Some(timeout))?;
181        let res = self.wait_inner(reconnect);
182        let _ = self.session.stream.get_mut().set_read_timeout(None).is_ok();
183        res
184    }
185}
186
187impl<'a, T: Read + Write + 'a> Drop for Handle<'a, T> {
188    fn drop(&mut self) {
189        // we don't want to panic here if we can't terminate the Idle
190        let _ = self.terminate().is_ok();
191    }
192}
193
194impl<'a> SetReadTimeout for TcpStream {
195    fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<()> {
196        TcpStream::set_read_timeout(self, timeout).map_err(Error::Io)
197    }
198}
199
200#[cfg(feature = "tls")]
201impl<'a> SetReadTimeout for TlsStream<TcpStream> {
202    fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<()> {
203        self.get_ref().set_read_timeout(timeout).map_err(Error::Io)
204    }
205}