Skip to main content

async_imap/
client.rs

1use std::collections::{HashMap, HashSet};
2use std::fmt;
3use std::ops::{Deref, DerefMut};
4use std::pin::Pin;
5use std::str;
6
7use async_channel::{self as channel, bounded};
8#[cfg(feature = "runtime-async-std")]
9use async_std::io::{Read, Write, WriteExt};
10use base64::Engine as _;
11use extensions::id::{format_identification, parse_id};
12use extensions::quota::parse_get_quota_root;
13use futures::{io, Stream, StreamExt};
14use imap_proto::{Metadata, RequestId, Response};
15#[cfg(feature = "runtime-tokio")]
16use tokio::io::{AsyncRead as Read, AsyncWrite as Write, AsyncWriteExt};
17
18use super::authenticator::Authenticator;
19use super::error::{Error, ParseError, Result, ValidateError};
20use super::parse::*;
21use super::types::*;
22use crate::extensions::{self, quota::parse_get_quota};
23use crate::imap_stream::ImapStream;
24
25macro_rules! quote {
26    ($x:expr) => {
27        format!("\"{}\"", $x.replace(r"\", r"\\").replace("\"", "\\\""))
28    };
29}
30
31/// An authenticated IMAP session providing the usual IMAP commands. This type is what you get from
32/// a succesful login attempt.
33///
34/// Note that the server *is* allowed to unilaterally send things to the client for messages in
35/// a selected mailbox whose status has changed. See the note on [unilateral server responses
36/// in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). Any such messages are parsed out
37/// and sent on `Session::unsolicited_responses`.
38// Both `Client` and `Session` deref to [`Connection`](struct.Connection.html), the underlying
39// primitives type.
40#[derive(Debug)]
41pub struct Session<T: Read + Write + Unpin + fmt::Debug> {
42    pub(crate) conn: Connection<T>,
43    pub(crate) unsolicited_responses_tx: channel::Sender<UnsolicitedResponse>,
44
45    /// Server responses that are not related to the current command. See also the note on
46    /// [unilateral server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7).
47    pub unsolicited_responses: channel::Receiver<UnsolicitedResponse>,
48}
49
50impl<T: Read + Write + Unpin + fmt::Debug> Unpin for Session<T> {}
51impl<T: Read + Write + Unpin + fmt::Debug> Unpin for Client<T> {}
52impl<T: Read + Write + Unpin + fmt::Debug> Unpin for Connection<T> {}
53
54// Make it possible to access the inner connection and modify its settings, such as read/write
55// timeouts.
56impl<T: Read + Write + Unpin + fmt::Debug> AsMut<T> for Session<T> {
57    fn as_mut(&mut self) -> &mut T {
58        self.conn.stream.as_mut()
59    }
60}
61
62/// An (unauthenticated) handle to talk to an IMAP server.
63///
64/// This is what you get when first
65/// connecting. A succesfull call to [`Client::login`] or [`Client::authenticate`] will return a
66/// [`Session`] instance that provides the usual IMAP methods.
67// Both `Client` and `Session` deref to [`Connection`](struct.Connection.html), the underlying
68// primitives type.
69#[derive(Debug)]
70pub struct Client<T: Read + Write + Unpin + fmt::Debug> {
71    conn: Connection<T>,
72}
73
74/// The underlying primitives type. Both `Client`(unauthenticated) and `Session`(after succesful
75/// login) use a `Connection` internally for the TCP stream primitives.
76#[derive(Debug)]
77pub struct Connection<T: Read + Write + Unpin + fmt::Debug> {
78    pub(crate) stream: ImapStream<T>,
79
80    /// Manages the request ids.
81    pub(crate) request_ids: IdGenerator,
82}
83
84// `Deref` instances are so we can make use of the same underlying primitives in `Client` and
85// `Session`
86impl<T: Read + Write + Unpin + fmt::Debug> Deref for Client<T> {
87    type Target = Connection<T>;
88
89    fn deref(&self) -> &Connection<T> {
90        &self.conn
91    }
92}
93
94impl<T: Read + Write + Unpin + fmt::Debug> DerefMut for Client<T> {
95    fn deref_mut(&mut self) -> &mut Connection<T> {
96        &mut self.conn
97    }
98}
99
100impl<T: Read + Write + Unpin + fmt::Debug> Deref for Session<T> {
101    type Target = Connection<T>;
102
103    fn deref(&self) -> &Connection<T> {
104        &self.conn
105    }
106}
107
108impl<T: Read + Write + Unpin + fmt::Debug> DerefMut for Session<T> {
109    fn deref_mut(&mut self) -> &mut Connection<T> {
110        &mut self.conn
111    }
112}
113
114// As the pattern of returning the unauthenticated `Client` (a.k.a. `self`) back with a login error
115// is relatively common, it's abstacted away into a macro here.
116//
117// Note: 1) using `.map_err(|e| (e, self))` or similar here makes the closure own self, so we can't
118//          do that.
119//       2) in theory we wouldn't need the second parameter, and could just use the identifier
120//          `self` from the surrounding function, but being explicit here seems a lot cleaner.
121macro_rules! ok_or_unauth_client_err {
122    ($r:expr, $self:expr) => {
123        match $r {
124            Ok(o) => o,
125            Err(e) => return Err((e, $self)),
126        }
127    };
128}
129
130impl<T: Read + Write + Unpin + fmt::Debug + Send> Client<T> {
131    /// Creates a new client over the given stream.
132    ///
133    /// This method primarily exists for writing tests that mock the underlying transport, but can
134    /// also be used to support IMAP over custom tunnels.
135    pub fn new(stream: T) -> Client<T> {
136        let stream = ImapStream::new(stream);
137
138        Client {
139            conn: Connection {
140                stream,
141                request_ids: IdGenerator::new(),
142            },
143        }
144    }
145
146    /// Convert this Client into the raw underlying stream.
147    pub fn into_inner(self) -> T {
148        let Self { conn, .. } = self;
149        conn.into_inner()
150    }
151
152    /// Convert this client into a session without sending an authentication command.
153    ///
154    /// The caller is responsible for ensuring the server connection is already in a state where
155    /// session commands are valid, such as after a `PREAUTH` greeting or when intentionally
156    /// probing a server that permits unauthenticated commands.
157    pub fn into_session(self) -> Session<T> {
158        Session::new(self.conn)
159    }
160
161    /// Log in to the IMAP server. Upon success a [`Session`](struct.Session.html) instance is
162    /// returned; on error the original `Client` instance is returned in addition to the error.
163    /// This is because `login` takes ownership of `self`, so in order to try again (e.g. after
164    /// prompting the user for credetials), ownership of the original `Client` needs to be
165    /// transferred back to the caller.
166    ///
167    /// ```ignore
168    /// # fn main() -> async_imap::error::Result<()> {
169    /// # async_std::task::block_on(async {
170    ///
171    /// let tls = async_native_tls::TlsConnector::new();
172    /// let client = async_imap::connect(
173    ///     ("imap.example.org", 993),
174    ///     "imap.example.org",
175    ///     tls
176    /// ).await?;
177    ///
178    /// match client.login("user", "pass").await {
179    ///     Ok(s) => {
180    ///         // you are successfully authenticated!
181    ///     },
182    ///     Err((e, orig_client)) => {
183    ///         eprintln!("error logging in: {}", e);
184    ///         // prompt user and try again with orig_client here
185    ///         return Err(e);
186    ///     }
187    /// }
188    ///
189    /// # Ok(())
190    /// # }) }
191    /// ```
192    pub async fn login<U: AsRef<str>, P: AsRef<str>>(
193        mut self,
194        username: U,
195        password: P,
196    ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
197        let u = ok_or_unauth_client_err!(validate_str(username.as_ref()), self);
198        let p = ok_or_unauth_client_err!(validate_str(password.as_ref()), self);
199        ok_or_unauth_client_err!(
200            self.run_command_and_check_ok(&format!("LOGIN {} {}", u, p), None)
201                .await,
202            self
203        );
204
205        Ok(self.into_session())
206    }
207
208    /// Authenticate with the server using the given custom `authenticator` to handle the server's
209    /// challenge.
210    ///
211    /// ```ignore
212    /// struct OAuth2 {
213    ///     user: String,
214    ///     access_token: String,
215    /// }
216    ///
217    /// impl async_imap::Authenticator for &OAuth2 {
218    ///     type Response = String;
219    ///     fn process(&mut self, _: &[u8]) -> Self::Response {
220    ///         format!(
221    ///             "user={}\x01auth=Bearer {}\x01\x01",
222    ///             self.user, self.access_token
223    ///         )
224    ///     }
225    /// }
226    ///
227    /// # fn main() -> async_imap::error::Result<()> {
228    /// # async_std::task::block_on(async {
229    ///
230    ///     let auth = OAuth2 {
231    ///         user: String::from("me@example.com"),
232    ///         access_token: String::from("<access_token>"),
233    ///     };
234    ///
235    ///     let domain = "imap.example.com";
236    ///     let tls = async_native_tls::TlsConnector::new();
237    ///     let client = async_imap::connect((domain, 993), domain, tls).await?;
238    ///     match client.authenticate("XOAUTH2", &auth).await {
239    ///         Ok(session) => {
240    ///             // you are successfully authenticated!
241    ///         },
242    ///         Err((err, orig_client)) => {
243    ///             eprintln!("error authenticating: {}", err);
244    ///             // prompt user and try again with orig_client here
245    ///             return Err(err);
246    ///         }
247    ///     };
248    /// # Ok(())
249    /// # }) }
250    /// ```
251    pub async fn authenticate<A: Authenticator, S: AsRef<str>>(
252        mut self,
253        auth_type: S,
254        authenticator: A,
255    ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
256        let id = ok_or_unauth_client_err!(
257            self.run_command(&format!("AUTHENTICATE {}", auth_type.as_ref()))
258                .await,
259            self
260        );
261        let session = self.do_auth_handshake(id, authenticator).await?;
262        Ok(session)
263    }
264
265    /// This func does the handshake process once the authenticate command is made.
266    async fn do_auth_handshake<A: Authenticator>(
267        mut self,
268        id: RequestId,
269        mut authenticator: A,
270    ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
271        // explicit match blocks neccessary to convert error to tuple and not bind self too
272        // early (see also comment on `login`)
273        loop {
274            if let Some(res) = self.read_response().await {
275                let res = ok_or_unauth_client_err!(res.map_err(Into::into), self);
276                match res.parsed() {
277                    Response::Continue { information, .. } => {
278                        let challenge = if let Some(text) = information {
279                            ok_or_unauth_client_err!(
280                                base64::engine::general_purpose::STANDARD
281                                    .decode(text.as_ref())
282                                    .map_err(|e| Error::Parse(ParseError::Authentication(
283                                        (*text).to_string(),
284                                        Some(e)
285                                    ))),
286                                self
287                            )
288                        } else {
289                            Vec::new()
290                        };
291                        let raw_response = &mut authenticator.process(&challenge);
292                        let auth_response =
293                            base64::engine::general_purpose::STANDARD.encode(raw_response);
294
295                        ok_or_unauth_client_err!(
296                            self.conn.run_command_untagged(&auth_response).await,
297                            self
298                        );
299                    }
300                    _ => {
301                        ok_or_unauth_client_err!(
302                            self.check_done_ok_from(&id, None, res).await,
303                            self
304                        );
305                        return Ok(self.into_session());
306                    }
307                }
308            } else {
309                return Err((Error::ConnectionLost, self));
310            }
311        }
312    }
313}
314
315impl<T: Read + Write + Unpin + fmt::Debug + Send> Session<T> {
316    unsafe_pinned!(conn: Connection<T>);
317
318    pub(crate) fn get_stream(self: Pin<&mut Self>) -> Pin<&mut ImapStream<T>> {
319        self.conn().stream()
320    }
321
322    // not public, just to avoid duplicating the channel creation code
323    fn new(conn: Connection<T>) -> Self {
324        let (tx, rx) = bounded(100);
325        Session {
326            conn,
327            unsolicited_responses: rx,
328            unsolicited_responses_tx: tx,
329        }
330    }
331
332    /// Selects a mailbox.
333    ///
334    /// The `SELECT` command selects a mailbox so that messages in the mailbox can be accessed.
335    /// Note that earlier versions of this protocol only required the FLAGS, EXISTS, and RECENT
336    /// untagged data; consequently, client implementations SHOULD implement default behavior for
337    /// missing data as discussed with the individual item.
338    ///
339    /// Only one mailbox can be selected at a time in a connection; simultaneous access to multiple
340    /// mailboxes requires multiple connections.  The `SELECT` command automatically deselects any
341    /// currently selected mailbox before attempting the new selection. Consequently, if a mailbox
342    /// is selected and a `SELECT` command that fails is attempted, no mailbox is selected.
343    ///
344    /// Note that the server *is* allowed to unilaterally send things to the client for messages in
345    /// a selected mailbox whose status has changed. See the note on [unilateral server responses
346    /// in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). This means that if run commands,
347    /// you *may* see additional untagged `RECENT`, `EXISTS`, `FETCH`, and `EXPUNGE` responses.
348    /// You can get them from the `unsolicited_responses` channel of the [`Session`](struct.Session.html).
349    pub async fn select<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
350        // TODO: also note READ/WRITE vs READ-only mode!
351        let id = self
352            .run_command(&format!("SELECT {}", validate_str(mailbox_name.as_ref())?))
353            .await?;
354        let mbox = parse_mailbox(
355            &mut self.conn.stream,
356            self.unsolicited_responses_tx.clone(),
357            id,
358        )
359        .await?;
360
361        Ok(mbox)
362    }
363
364    /// Selects a mailbox with `(CONDSTORE)` parameter as defined in
365    /// [RFC 7162](https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.8).
366    pub async fn select_condstore<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
367        let id = self
368            .run_command(&format!(
369                "SELECT {} (CONDSTORE)",
370                validate_str(mailbox_name.as_ref())?
371            ))
372            .await?;
373        let mbox = parse_mailbox(
374            &mut self.conn.stream,
375            self.unsolicited_responses_tx.clone(),
376            id,
377        )
378        .await?;
379
380        Ok(mbox)
381    }
382
383    /// The `EXAMINE` command is identical to [`Session::select`] and returns the same output;
384    /// however, the selected mailbox is identified as read-only. No changes to the permanent state
385    /// of the mailbox, including per-user state, will happen in a mailbox opened with `examine`;
386    /// in particular, messagess cannot lose [`Flag::Recent`] in an examined mailbox.
387    pub async fn examine<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
388        let id = self
389            .run_command(&format!("EXAMINE {}", validate_str(mailbox_name.as_ref())?))
390            .await?;
391        let mbox = parse_mailbox(
392            &mut self.conn.stream,
393            self.unsolicited_responses_tx.clone(),
394            id,
395        )
396        .await?;
397
398        Ok(mbox)
399    }
400
401    /// Fetch retreives data associated with a set of messages in the mailbox.
402    ///
403    /// Note that the server *is* allowed to unilaterally include `FETCH` responses for other
404    /// messages in the selected mailbox whose status has changed. See the note on [unilateral
405    /// server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7).
406    ///
407    /// `query` is a list of "data items" (space-separated in parentheses if `>1`). There are three
408    /// "macro items" which specify commonly-used sets of data items, and can be used instead of
409    /// data items.  A macro must be used by itself, and not in conjunction with other macros or
410    /// data items. They are:
411    ///
412    ///  - `ALL`: equivalent to: `(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)`
413    ///  - `FAST`: equivalent to: `(FLAGS INTERNALDATE RFC822.SIZE)`
414    ///
415    /// The currently defined data items that can be fetched are listen [in the
416    /// RFC](https://tools.ietf.org/html/rfc3501#section-6.4.5), but here are some common ones:
417    ///
418    ///  - `FLAGS`: The flags that are set for this message.
419    ///  - `INTERNALDATE`: The internal date of the message.
420    ///  - `BODY[<section>]`:
421    ///
422    ///    The text of a particular body section.  The section specification is a set of zero or
423    ///    more part specifiers delimited by periods.  A part specifier is either a part number
424    ///    (see RFC) or one of the following: `HEADER`, `HEADER.FIELDS`, `HEADER.FIELDS.NOT`,
425    ///    `MIME`, and `TEXT`.  An empty section specification (i.e., `BODY[]`) refers to the
426    ///    entire message, including the header.
427    ///
428    ///    The `HEADER`, `HEADER.FIELDS`, and `HEADER.FIELDS.NOT` part specifiers refer to the
429    ///    [RFC-2822](https://tools.ietf.org/html/rfc2822) header of the message or of an
430    ///    encapsulated [MIME-IMT](https://tools.ietf.org/html/rfc2046)
431    ///    MESSAGE/[RFC822](https://tools.ietf.org/html/rfc822) message. `HEADER.FIELDS` and
432    ///    `HEADER.FIELDS.NOT` are followed by a list of field-name (as defined in
433    ///    [RFC-2822](https://tools.ietf.org/html/rfc2822)) names, and return a subset of the
434    ///    header.  The subset returned by `HEADER.FIELDS` contains only those header fields with
435    ///    a field-name that matches one of the names in the list; similarly, the subset returned
436    ///    by `HEADER.FIELDS.NOT` contains only the header fields with a non-matching field-name.
437    ///    The field-matching is case-insensitive but otherwise exact.  Subsetting does not
438    ///    exclude the [RFC-2822](https://tools.ietf.org/html/rfc2822) delimiting blank line
439    ///    between the header and the body; the blank line is included in all header fetches,
440    ///    except in the case of a message which has no body and no blank line.
441    ///
442    ///    The `MIME` part specifier refers to the [MIME-IMB](https://tools.ietf.org/html/rfc2045)
443    ///    header for this part.
444    ///
445    ///    The `TEXT` part specifier refers to the text body of the message,
446    ///    omitting the [RFC-2822](https://tools.ietf.org/html/rfc2822) header.
447    ///
448    ///    [`Flag::Seen`] is implicitly set when `BODY` is fetched; if this causes the flags to
449    ///    change, they will generally be included as part of the `FETCH` responses.
450    ///  - `BODY.PEEK[<section>]`: An alternate form of `BODY[<section>]` that does not implicitly
451    ///    set [`Flag::Seen`].
452    ///  - `ENVELOPE`: The envelope structure of the message.  This is computed by the server by
453    ///    parsing the [RFC-2822](https://tools.ietf.org/html/rfc2822) header into the component
454    ///    parts, defaulting various fields as necessary.
455    ///  - `RFC822`: Functionally equivalent to `BODY[]`.
456    ///  - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`.
457    ///  - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message.
458    ///  - `UID`: The unique identifier for the message.
459    pub async fn fetch<S1, S2>(
460        &mut self,
461        sequence_set: S1,
462        query: S2,
463    ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send>
464    where
465        S1: AsRef<str>,
466        S2: AsRef<str>,
467    {
468        let id = self
469            .run_command(&format!(
470                "FETCH {} {}",
471                sequence_set.as_ref(),
472                query.as_ref()
473            ))
474            .await?;
475        let res = parse_fetches(
476            &mut self.conn.stream,
477            self.unsolicited_responses_tx.clone(),
478            id,
479        );
480
481        Ok(res)
482    }
483
484    /// Equivalent to [`Session::fetch`], except that all identifiers in `uid_set` are
485    /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
486    pub async fn uid_fetch<S1, S2>(
487        &mut self,
488        uid_set: S1,
489        query: S2,
490    ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send + Unpin>
491    where
492        S1: AsRef<str>,
493        S2: AsRef<str>,
494    {
495        let id = self
496            .run_command(&format!(
497                "UID FETCH {} {}",
498                uid_set.as_ref(),
499                query.as_ref()
500            ))
501            .await?;
502        let res = parse_fetches(
503            &mut self.conn.stream,
504            self.unsolicited_responses_tx.clone(),
505            id,
506        );
507        Ok(res)
508    }
509
510    /// Noop always succeeds, and it does nothing.
511    pub async fn noop(&mut self) -> Result<()> {
512        let id = self.run_command("NOOP").await?;
513        parse_noop(
514            &mut self.conn.stream,
515            self.unsolicited_responses_tx.clone(),
516            id,
517        )
518        .await?;
519        Ok(())
520    }
521
522    /// Logout informs the server that the client is done with the connection.
523    pub async fn logout(&mut self) -> Result<()> {
524        self.run_command_and_check_ok("LOGOUT").await?;
525        Ok(())
526    }
527
528    /// The [`CREATE` command](https://tools.ietf.org/html/rfc3501#section-6.3.3) creates a mailbox
529    /// with the given name.  `Ok` is returned only if a new mailbox with that name has been
530    /// created.  It is an error to attempt to create `INBOX` or a mailbox with a name that
531    /// refers to an extant mailbox.  Any error in creation will return [`Error::No`].
532    ///
533    /// If the mailbox name is suffixed with the server's hierarchy separator character (as
534    /// returned from the server by [`Session::list`]), this is a declaration that the client
535    /// intends to create mailbox names under this name in the hierarchy.  Servers that do not
536    /// require this declaration will ignore the declaration.  In any case, the name created is
537    /// without the trailing hierarchy delimiter.
538    ///
539    /// If the server's hierarchy separator character appears elsewhere in the name, the server
540    /// will generally create any superior hierarchical names that are needed for the `CREATE`
541    /// command to be successfully completed.  In other words, an attempt to create `foo/bar/zap`
542    /// on a server in which `/` is the hierarchy separator character will usually create `foo/`
543    /// and `foo/bar/` if they do not already exist.
544    ///
545    /// If a new mailbox is created with the same name as a mailbox which was deleted, its unique
546    /// identifiers will be greater than any unique identifiers used in the previous incarnation of
547    /// the mailbox UNLESS the new incarnation has a different unique identifier validity value.
548    /// See the description of the [`UID`
549    /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8) for more detail.
550    pub async fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<()> {
551        self.run_command_and_check_ok(&format!("CREATE {}", validate_str(mailbox_name.as_ref())?))
552            .await?;
553
554        Ok(())
555    }
556
557    /// The [`DELETE` command](https://tools.ietf.org/html/rfc3501#section-6.3.4) permanently
558    /// removes the mailbox with the given name.  `Ok` is returned only if the mailbox has been
559    /// deleted.  It is an error to attempt to delete `INBOX` or a mailbox name that does not
560    /// exist.
561    ///
562    /// The `DELETE` command will not remove inferior hierarchical names. For example, if a mailbox
563    /// `foo` has an inferior `foo.bar` (assuming `.` is the hierarchy delimiter character),
564    /// removing `foo` will not remove `foo.bar`.  It is an error to attempt to delete a name that
565    /// has inferior hierarchical names and also has [`NameAttribute::NoSelect`].
566    ///
567    /// It is permitted to delete a name that has inferior hierarchical names and does not have
568    /// [`NameAttribute::NoSelect`].  In this case, all messages in that mailbox are removed, and
569    /// the name will acquire [`NameAttribute::NoSelect`].
570    ///
571    /// The value of the highest-used unique identifier of the deleted mailbox will be preserved so
572    /// that a new mailbox created with the same name will not reuse the identifiers of the former
573    /// incarnation, UNLESS the new incarnation has a different unique identifier validity value.
574    /// See the description of the [`UID`
575    /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8) for more detail.
576    pub async fn delete<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<()> {
577        self.run_command_and_check_ok(&format!("DELETE {}", validate_str(mailbox_name.as_ref())?))
578            .await?;
579
580        Ok(())
581    }
582
583    /// The [`RENAME` command](https://tools.ietf.org/html/rfc3501#section-6.3.5) changes the name
584    /// of a mailbox.  `Ok` is returned only if the mailbox has been renamed.  It is an error to
585    /// attempt to rename from a mailbox name that does not exist or to a mailbox name that already
586    /// exists.  Any error in renaming will return [`Error::No`].
587    ///
588    /// If the name has inferior hierarchical names, then the inferior hierarchical names will also
589    /// be renamed.  For example, a rename of `foo` to `zap` will rename `foo/bar` (assuming `/` is
590    /// the hierarchy delimiter character) to `zap/bar`.
591    ///
592    /// If the server's hierarchy separator character appears in the name, the server will
593    /// generally create any superior hierarchical names that are needed for the `RENAME` command
594    /// to complete successfully.  In other words, an attempt to rename `foo/bar/zap` to
595    /// `baz/rag/zowie` on a server in which `/` is the hierarchy separator character will
596    /// generally create `baz/` and `baz/rag/` if they do not already exist.
597    ///
598    /// The value of the highest-used unique identifier of the old mailbox name will be preserved
599    /// so that a new mailbox created with the same name will not reuse the identifiers of the
600    /// former incarnation, UNLESS the new incarnation has a different unique identifier validity
601    /// value. See the description of the [`UID`
602    /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8) for more detail.
603    ///
604    /// Renaming `INBOX` is permitted, and has special behavior.  It moves all messages in `INBOX`
605    /// to a new mailbox with the given name, leaving `INBOX` empty.  If the server implementation
606    /// supports inferior hierarchical names of `INBOX`, these are unaffected by a rename of
607    /// `INBOX`.
608    pub async fn rename<S1: AsRef<str>, S2: AsRef<str>>(&mut self, from: S1, to: S2) -> Result<()> {
609        self.run_command_and_check_ok(&format!(
610            "RENAME {} {}",
611            quote!(from.as_ref()),
612            quote!(to.as_ref())
613        ))
614        .await?;
615
616        Ok(())
617    }
618
619    /// The [`SUBSCRIBE` command](https://tools.ietf.org/html/rfc3501#section-6.3.6) adds the
620    /// specified mailbox name to the server's set of "active" or "subscribed" mailboxes as
621    /// returned by [`Session::lsub`].  This command returns `Ok` only if the subscription is
622    /// successful.
623    ///
624    /// The server may validate the mailbox argument to `SUBSCRIBE` to verify that it exists.
625    /// However, it will not unilaterally remove an existing mailbox name from the subscription
626    /// list even if a mailbox by that name no longer exists.
627    pub async fn subscribe<S: AsRef<str>>(&mut self, mailbox: S) -> Result<()> {
628        self.run_command_and_check_ok(&format!("SUBSCRIBE {}", quote!(mailbox.as_ref())))
629            .await?;
630        Ok(())
631    }
632
633    /// The [`UNSUBSCRIBE` command](https://tools.ietf.org/html/rfc3501#section-6.3.7) removes the
634    /// specified mailbox name from the server's set of "active" or "subscribed" mailboxes as
635    /// returned by [`Session::lsub`].  This command returns `Ok` only if the unsubscription is
636    /// successful.
637    pub async fn unsubscribe<S: AsRef<str>>(&mut self, mailbox: S) -> Result<()> {
638        self.run_command_and_check_ok(&format!("UNSUBSCRIBE {}", quote!(mailbox.as_ref())))
639            .await?;
640        Ok(())
641    }
642
643    /// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
644    /// listing of capabilities that the server supports.  The server will include "IMAP4rev1" as
645    /// one of the listed capabilities. See [`Capabilities`] for further details.
646    pub async fn capabilities(&mut self) -> Result<Capabilities> {
647        let id = self.run_command("CAPABILITY").await?;
648        let c = parse_capabilities(
649            &mut self.conn.stream,
650            self.unsolicited_responses_tx.clone(),
651            id,
652        )
653        .await?;
654        Ok(c)
655    }
656
657    /// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently
658    /// removes all messages that have [`Flag::Deleted`] set from the currently selected mailbox.
659    /// The message sequence number of each message that is removed is returned.
660    pub async fn expunge(&mut self) -> Result<impl Stream<Item = Result<Seq>> + '_ + Send> {
661        let id = self.run_command("EXPUNGE").await?;
662        let res = parse_expunge(
663            &mut self.conn.stream,
664            self.unsolicited_responses_tx.clone(),
665            id,
666        );
667        Ok(res)
668    }
669
670    /// The [`UID EXPUNGE` command](https://tools.ietf.org/html/rfc4315#section-2.1) permanently
671    /// removes all messages that both have [`Flag::Deleted`] set and have a [`Uid`] that is
672    /// included in the specified sequence set from the currently selected mailbox.  If a message
673    /// either does not have [`Flag::Deleted`] set or has a [`Uid`] that is not included in the
674    /// specified sequence set, it is not affected.
675    ///
676    /// This command is particularly useful for disconnected use clients. By using `uid_expunge`
677    /// instead of [`Self::expunge`] when resynchronizing with the server, the client can ensure that it
678    /// does not inadvertantly remove any messages that have been marked as [`Flag::Deleted`] by
679    /// other clients between the time that the client was last connected and the time the client
680    /// resynchronizes.
681    ///
682    /// This command requires that the server supports [RFC
683    /// 4315](https://tools.ietf.org/html/rfc4315) as indicated by the `UIDPLUS` capability (see
684    /// [`Session::capabilities`]). If the server does not support the `UIDPLUS` capability, the
685    /// client should fall back to using [`Session::store`] to temporarily remove [`Flag::Deleted`]
686    /// from messages it does not want to remove, then invoking [`Session::expunge`].  Finally, the
687    /// client should use [`Session::store`] to restore [`Flag::Deleted`] on the messages in which
688    /// it was temporarily removed.
689    ///
690    /// Alternatively, the client may fall back to using just [`Session::expunge`], risking the
691    /// unintended removal of some messages.
692    pub async fn uid_expunge<S: AsRef<str>>(
693        &mut self,
694        uid_set: S,
695    ) -> Result<impl Stream<Item = Result<Uid>> + '_ + Send> {
696        let id = self
697            .run_command(&format!("UID EXPUNGE {}", uid_set.as_ref()))
698            .await?;
699        let res = parse_expunge(
700            &mut self.conn.stream,
701            self.unsolicited_responses_tx.clone(),
702            id,
703        );
704        Ok(res)
705    }
706
707    /// The [`CHECK` command](https://tools.ietf.org/html/rfc3501#section-6.4.1) requests a
708    /// checkpoint of the currently selected mailbox.  A checkpoint refers to any
709    /// implementation-dependent housekeeping associated with the mailbox (e.g., resolving the
710    /// server's in-memory state of the mailbox with the state on its disk) that is not normally
711    /// executed as part of each command.  A checkpoint MAY take a non-instantaneous amount of real
712    /// time to complete.  If a server implementation has no such housekeeping considerations,
713    /// [`Session::check`] is equivalent to [`Session::noop`].
714    ///
715    /// There is no guarantee that an `EXISTS` untagged response will happen as a result of
716    /// `CHECK`.  [`Session::noop`] SHOULD be used for new message polling.
717    pub async fn check(&mut self) -> Result<()> {
718        self.run_command_and_check_ok("CHECK").await?;
719        Ok(())
720    }
721
722    /// The [`CLOSE` command](https://tools.ietf.org/html/rfc3501#section-6.4.2) permanently
723    /// removes all messages that have [`Flag::Deleted`] set from the currently selected mailbox,
724    /// and returns to the authenticated state from the selected state.  No `EXPUNGE` responses are
725    /// sent.
726    ///
727    /// No messages are removed, and no error is given, if the mailbox is selected by
728    /// [`Session::examine`] or is otherwise selected read-only.
729    ///
730    /// Even if a mailbox is selected, [`Session::select`], [`Session::examine`], or
731    /// [`Session::logout`] command MAY be issued without previously invoking [`Session::close`].
732    /// [`Session::select`], [`Session::examine`], and [`Session::logout`] implicitly close the
733    /// currently selected mailbox without doing an expunge.  However, when many messages are
734    /// deleted, a `CLOSE-LOGOUT` or `CLOSE-SELECT` sequence is considerably faster than an
735    /// `EXPUNGE-LOGOUT` or `EXPUNGE-SELECT` because no `EXPUNGE` responses (which the client would
736    /// probably ignore) are sent.
737    pub async fn close(&mut self) -> Result<()> {
738        self.run_command_and_check_ok("CLOSE").await?;
739        Ok(())
740    }
741
742    /// The [`STORE` command](https://tools.ietf.org/html/rfc3501#section-6.4.6) alters data
743    /// associated with a message in the mailbox.  Normally, `STORE` will return the updated value
744    /// of the data with an untagged FETCH response.  A suffix of `.SILENT` in `query` prevents the
745    /// untagged `FETCH`, and the server assumes that the client has determined the updated value
746    /// itself or does not care about the updated value.
747    ///
748    /// The currently defined data items that can be stored are:
749    ///
750    ///  - `FLAGS <flag list>`:
751    ///
752    ///    Replace the flags for the message (other than [`Flag::Recent`]) with the argument.  The
753    ///    new value of the flags is returned as if a `FETCH` of those flags was done.
754    ///
755    ///  - `FLAGS.SILENT <flag list>`: Equivalent to `FLAGS`, but without returning a new value.
756    ///
757    ///  - `+FLAGS <flag list>`
758    ///
759    ///    Add the argument to the flags for the message.  The new value of the flags is returned
760    ///    as if a `FETCH` of those flags was done.
761    ///  - `+FLAGS.SILENT <flag list>`: Equivalent to `+FLAGS`, but without returning a new value.
762    ///
763    ///  - `-FLAGS <flag list>`
764    ///
765    ///    Remove the argument from the flags for the message.  The new value of the flags is
766    ///    returned as if a `FETCH` of those flags was done.
767    ///
768    ///  - `-FLAGS.SILENT <flag list>`: Equivalent to `-FLAGS`, but without returning a new value.
769    ///
770    /// In all cases, `<flag list>` is a space-separated list enclosed in parentheses.
771    ///
772    /// # Examples
773    ///
774    /// Delete a message:
775    ///
776    /// ```no_run
777    /// use async_imap::{types::Seq, Session, error::Result};
778    /// #[cfg(feature = "runtime-async-std")]
779    /// use async_std::net::TcpStream;
780    /// #[cfg(feature = "runtime-tokio")]
781    /// use tokio::net::TcpStream;
782    /// use futures::TryStreamExt;
783    ///
784    /// async fn delete(seq: Seq, s: &mut Session<TcpStream>) -> Result<()> {
785    ///     let updates_stream = s.store(format!("{}", seq), "+FLAGS (\\Deleted)").await?;
786    ///     let _updates: Vec<_> = updates_stream.try_collect().await?;
787    ///     s.expunge().await?;
788    ///     Ok(())
789    /// }
790    /// ```
791    pub async fn store<S1, S2>(
792        &mut self,
793        sequence_set: S1,
794        query: S2,
795    ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send>
796    where
797        S1: AsRef<str>,
798        S2: AsRef<str>,
799    {
800        let id = self
801            .run_command(&format!(
802                "STORE {} {}",
803                sequence_set.as_ref(),
804                query.as_ref()
805            ))
806            .await?;
807        let res = parse_fetches(
808            &mut self.conn.stream,
809            self.unsolicited_responses_tx.clone(),
810            id,
811        );
812        Ok(res)
813    }
814
815    /// Equivalent to [`Session::store`], except that all identifiers in `sequence_set` are
816    /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
817    pub async fn uid_store<S1, S2>(
818        &mut self,
819        uid_set: S1,
820        query: S2,
821    ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send>
822    where
823        S1: AsRef<str>,
824        S2: AsRef<str>,
825    {
826        let id = self
827            .run_command(&format!(
828                "UID STORE {} {}",
829                uid_set.as_ref(),
830                query.as_ref()
831            ))
832            .await?;
833        let res = parse_fetches(
834            &mut self.conn.stream,
835            self.unsolicited_responses_tx.clone(),
836            id,
837        );
838        Ok(res)
839    }
840
841    /// The [`COPY` command](https://tools.ietf.org/html/rfc3501#section-6.4.7) copies the
842    /// specified message(s) to the end of the specified destination mailbox.  The flags and
843    /// internal date of the message(s) will generally be preserved, and [`Flag::Recent`] will
844    /// generally be set, in the copy.
845    ///
846    /// If the `COPY` command is unsuccessful for any reason, the server restores the destination
847    /// mailbox to its state before the `COPY` attempt.
848    pub async fn copy<S1: AsRef<str>, S2: AsRef<str>>(
849        &mut self,
850        sequence_set: S1,
851        mailbox_name: S2,
852    ) -> Result<()> {
853        self.run_command_and_check_ok(&format!(
854            "COPY {} {}",
855            sequence_set.as_ref(),
856            mailbox_name.as_ref()
857        ))
858        .await?;
859
860        Ok(())
861    }
862
863    /// Equivalent to [`Session::copy`], except that all identifiers in `sequence_set` are
864    /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
865    pub async fn uid_copy<S1: AsRef<str>, S2: AsRef<str>>(
866        &mut self,
867        uid_set: S1,
868        mailbox_name: S2,
869    ) -> Result<()> {
870        self.run_command_and_check_ok(&format!(
871            "UID COPY {} {}",
872            uid_set.as_ref(),
873            mailbox_name.as_ref()
874        ))
875        .await?;
876
877        Ok(())
878    }
879
880    /// The [`MOVE` command](https://tools.ietf.org/html/rfc6851#section-3.1) takes two
881    /// arguments: a sequence set and a named mailbox. Each message included in the set is moved,
882    /// rather than copied, from the selected (source) mailbox to the named (target) mailbox.
883    ///
884    /// This means that a new message is created in the target mailbox with a
885    /// new [`Uid`], the original message is removed from the source mailbox, and
886    /// it appears to the client as a single action.  This has the same
887    /// effect for each message as this sequence:
888    ///
889    ///   1. COPY
890    ///   2. STORE +FLAGS.SILENT \DELETED
891    ///   3. EXPUNGE
892    ///
893    /// This command requires that the server supports [RFC
894    /// 6851](https://tools.ietf.org/html/rfc6851) as indicated by the `MOVE` capability (see
895    /// [`Session::capabilities`]).
896    ///
897    /// Although the effect of the `MOVE` is the same as the preceding steps, the semantics are not
898    /// identical: The intermediate states produced by those steps do not occur, and the response
899    /// codes are different.  In particular, though the `COPY` and `EXPUNGE` response codes will be
900    /// returned, response codes for a `store` will not be generated and [`Flag::Deleted`] will not
901    /// be set for any message.
902    ///
903    /// Because a `MOVE` applies to a set of messages, it might fail partway through the set.
904    /// Regardless of whether the command is successful in moving the entire set, each individual
905    /// message will either be moved or unaffected.  The server will leave each message in a state
906    /// where it is in at least one of the source or target mailboxes (no message can be lost or
907    /// orphaned).  The server will generally not leave any message in both mailboxes (it would be
908    /// bad for a partial failure to result in a bunch of duplicate messages).  This is true even
909    /// if the server returns with [`Error::No`].
910    pub async fn mv<S1: AsRef<str>, S2: AsRef<str>>(
911        &mut self,
912        sequence_set: S1,
913        mailbox_name: S2,
914    ) -> Result<()> {
915        self.run_command_and_check_ok(&format!(
916            "MOVE {} {}",
917            sequence_set.as_ref(),
918            validate_str(mailbox_name.as_ref())?
919        ))
920        .await?;
921
922        Ok(())
923    }
924
925    /// Equivalent to [`Session::copy`], except that all identifiers in `sequence_set` are
926    /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8)
927    /// and the [semantics of `MOVE` and `UID
928    /// MOVE`](https://tools.ietf.org/html/rfc6851#section-3.3).
929    pub async fn uid_mv<S1: AsRef<str>, S2: AsRef<str>>(
930        &mut self,
931        uid_set: S1,
932        mailbox_name: S2,
933    ) -> Result<()> {
934        self.run_command_and_check_ok(&format!(
935            "UID MOVE {} {}",
936            uid_set.as_ref(),
937            validate_str(mailbox_name.as_ref())?
938        ))
939        .await?;
940
941        Ok(())
942    }
943
944    /// The [`LIST` command](https://tools.ietf.org/html/rfc3501#section-6.3.8) returns a subset of
945    /// names from the complete set of all names available to the client.  It returns the name
946    /// attributes, hierarchy delimiter, and name of each such name; see [`Name`] for more detail.
947    ///
948    /// If `reference_name` is `None` (or `""`), the currently selected mailbox is used.
949    /// The returned mailbox names must match the supplied `mailbox_pattern`.  A non-empty
950    /// reference name argument is the name of a mailbox or a level of mailbox hierarchy, and
951    /// indicates the context in which the mailbox name is interpreted.
952    ///
953    /// If `mailbox_pattern` is `None` (or `""`), it is a special request to return the hierarchy
954    /// delimiter and the root name of the name given in the reference.  The value returned as the
955    /// root MAY be the empty string if the reference is non-rooted or is an empty string.  In all
956    /// cases, a hierarchy delimiter (or `NIL` if there is no hierarchy) is returned.  This permits
957    /// a client to get the hierarchy delimiter (or find out that the mailbox names are flat) even
958    /// when no mailboxes by that name currently exist.
959    ///
960    /// The reference and mailbox name arguments are interpreted into a canonical form that
961    /// represents an unambiguous left-to-right hierarchy.  The returned mailbox names will be in
962    /// the interpreted form.
963    ///
964    /// The character `*` is a wildcard, and matches zero or more characters at this position.  The
965    /// character `%` is similar to `*`, but it does not match a hierarchy delimiter.  If the `%`
966    /// wildcard is the last character of a mailbox name argument, matching levels of hierarchy are
967    /// also returned.  If these levels of hierarchy are not also selectable mailboxes, they are
968    /// returned with [`NameAttribute::NoSelect`].
969    ///
970    /// The special name `INBOX` is included if `INBOX` is supported by this server for this user
971    /// and if the uppercase string `INBOX` matches the interpreted reference and mailbox name
972    /// arguments with wildcards.  The criteria for omitting `INBOX` is whether `SELECT INBOX` will
973    /// return failure; it is not relevant whether the user's real `INBOX` resides on this or some
974    /// other server.
975    pub async fn list(
976        &mut self,
977        reference_name: Option<&str>,
978        mailbox_pattern: Option<&str>,
979    ) -> Result<impl Stream<Item = Result<Name>> + '_ + Send> {
980        let id = self
981            .run_command(&format!(
982                "LIST {} {}",
983                quote!(reference_name.unwrap_or("")),
984                mailbox_pattern.unwrap_or("\"\"")
985            ))
986            .await?;
987
988        Ok(parse_names(
989            &mut self.conn.stream,
990            self.unsolicited_responses_tx.clone(),
991            id,
992        ))
993    }
994
995    /// The [`LSUB` command](https://tools.ietf.org/html/rfc3501#section-6.3.9) returns a subset of
996    /// names from the set of names that the user has declared as being "active" or "subscribed".
997    /// The arguments to this method the same as for [`Session::list`].
998    ///
999    /// The returned [`Name`]s MAY contain different mailbox flags from response to
1000    /// [`Session::list`].  If this should happen, the flags returned by [`Session::list`] are
1001    /// considered more authoritative.
1002    ///
1003    /// A special situation occurs when invoking `lsub` with the `%` wildcard. Consider what
1004    /// happens if `foo/bar` (with a hierarchy delimiter of `/`) is subscribed but `foo` is not.  A
1005    /// `%` wildcard to `lsub` must return `foo`, not `foo/bar`, and it will be flagged with
1006    /// [`NameAttribute::NoSelect`].
1007    ///
1008    /// The server will not unilaterally remove an existing mailbox name from the subscription list
1009    /// even if a mailbox by that name no longer exists.
1010    pub async fn lsub(
1011        &mut self,
1012        reference_name: Option<&str>,
1013        mailbox_pattern: Option<&str>,
1014    ) -> Result<impl Stream<Item = Result<Name>> + '_ + Send> {
1015        let id = self
1016            .run_command(&format!(
1017                "LSUB {} {}",
1018                quote!(reference_name.unwrap_or("")),
1019                mailbox_pattern.unwrap_or("")
1020            ))
1021            .await?;
1022        let names = parse_names(
1023            &mut self.conn.stream,
1024            self.unsolicited_responses_tx.clone(),
1025            id,
1026        );
1027
1028        Ok(names)
1029    }
1030
1031    /// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the
1032    /// status of the indicated mailbox. It does not change the currently selected mailbox, nor
1033    /// does it affect the state of any messages in the queried mailbox (in particular, `status`
1034    /// will not cause messages to lose [`Flag::Recent`]).
1035    ///
1036    /// `status` provides an alternative to opening a second [`Session`] and using
1037    /// [`Session::examine`] on a mailbox to query that mailbox's status without deselecting the
1038    /// current mailbox in the first `Session`.
1039    ///
1040    /// Unlike [`Session::list`], `status` is not guaranteed to be fast in its response.  Under
1041    /// certain circumstances, it can be quite slow.  In some implementations, the server is
1042    /// obliged to open the mailbox read-only internally to obtain certain status information.
1043    /// Also unlike [`Session::list`], `status` does not accept wildcards.
1044    ///
1045    /// > Note: `status` is intended to access the status of mailboxes other than the currently
1046    /// > selected mailbox.  Because `status` can cause the mailbox to be opened internally, and
1047    /// > because this information is available by other means on the selected mailbox, `status`
1048    /// > SHOULD NOT be used on the currently selected mailbox.
1049    ///
1050    /// The STATUS command MUST NOT be used as a "check for new messages in the selected mailbox"
1051    /// operation (refer to sections [7](https://tools.ietf.org/html/rfc3501#section-7),
1052    /// [7.3.1](https://tools.ietf.org/html/rfc3501#section-7.3.1), and
1053    /// [7.3.2](https://tools.ietf.org/html/rfc3501#section-7.3.2) for more information about the
1054    /// proper method for new message checking).
1055    ///
1056    /// The currently defined status data items that can be requested are:
1057    ///
1058    ///  - `MESSAGES`: The number of messages in the mailbox.
1059    ///  - `RECENT`: The number of messages with [`Flag::Recent`] set.
1060    ///  - `UIDNEXT`: The next [`Uid`] of the mailbox.
1061    ///  - `UIDVALIDITY`: The unique identifier validity value of the mailbox (see [`Uid`]).
1062    ///  - `UNSEEN`: The number of messages which do not have [`Flag::Seen`] set.
1063    ///
1064    /// `data_items` is a space-separated list enclosed in parentheses.
1065    pub async fn status<S1: AsRef<str>, S2: AsRef<str>>(
1066        &mut self,
1067        mailbox_name: S1,
1068        data_items: S2,
1069    ) -> Result<Mailbox> {
1070        let id = self
1071            .run_command(&format!(
1072                "STATUS {} {}",
1073                validate_str(mailbox_name.as_ref())?,
1074                data_items.as_ref()
1075            ))
1076            .await?;
1077        let mbox = parse_status(
1078            &mut self.conn.stream,
1079            mailbox_name.as_ref(),
1080            self.unsolicited_responses_tx.clone(),
1081            id,
1082        )
1083        .await?;
1084        Ok(mbox)
1085    }
1086
1087    /// This method returns a handle that lets you use the [`IDLE`
1088    /// command](https://tools.ietf.org/html/rfc2177#section-3) to listen for changes to the
1089    /// currently selected mailbox.
1090    ///
1091    /// It's often more desirable to have the server transmit updates to the client in real time.
1092    /// This allows a user to see new mail immediately.  It also helps some real-time applications
1093    /// based on IMAP, which might otherwise need to poll extremely often (such as every few
1094    /// seconds).  While the spec actually does allow a server to push `EXISTS` responses
1095    /// aysynchronously, a client can't expect this behaviour and must poll.  This method provides
1096    /// you with such a mechanism.
1097    ///
1098    /// `idle` may be used with any server that returns `IDLE` as one of the supported capabilities
1099    /// (see [`Session::capabilities`]). If the server does not advertise the `IDLE` capability,
1100    /// the client MUST NOT use `idle` and must instead poll for mailbox updates.  In particular,
1101    /// the client MUST continue to be able to accept unsolicited untagged responses to ANY
1102    /// command, as specified in the base IMAP specification.
1103    ///
1104    /// See [`extensions::idle::Handle`] for details.
1105    pub fn idle(self) -> extensions::idle::Handle<T> {
1106        extensions::idle::Handle::new(self)
1107    }
1108
1109    /// The [`APPEND` command](https://tools.ietf.org/html/rfc3501#section-6.3.11) appends
1110    /// `content` as a new message to the end of the specified destination `mailbox`.  This
1111    /// argument SHOULD be in the format of an [RFC-2822](https://tools.ietf.org/html/rfc2822)
1112    /// message.
1113    ///
1114    /// > Note: There MAY be exceptions, e.g., draft messages, in which required RFC-2822 header
1115    /// > lines are omitted in the message literal argument to `append`.  The full implications of
1116    /// > doing so MUST be understood and carefully weighed.
1117    ///
1118    /// If the append is unsuccessful for any reason, the mailbox is restored to its state before
1119    /// the append attempt; no partial appending will happen.
1120    ///
1121    /// If the destination `mailbox` does not exist, the server returns an error, and does not
1122    /// automatically create the mailbox.
1123    ///
1124    /// If the mailbox is currently selected, the normal new message actions will generally occur.
1125    /// Specifically, the server will generally notify the client immediately via an untagged
1126    /// `EXISTS` response.  If the server does not do so, the client MAY issue a `NOOP` command (or
1127    /// failing that, a `CHECK` command) after one or more `APPEND` commands.
1128    pub async fn append(
1129        &mut self,
1130        mailbox: impl AsRef<str>,
1131        flags: Option<&str>,
1132        internaldate: Option<&str>,
1133        content: impl AsRef<[u8]>,
1134    ) -> Result<()> {
1135        let content = content.as_ref();
1136        let id = self
1137            .run_command(&format!(
1138                "APPEND \"{}\"{}{}{}{} {{{}}}",
1139                mailbox.as_ref(),
1140                if flags.is_some() { " " } else { "" },
1141                flags.unwrap_or(""),
1142                if internaldate.is_some() { " " } else { "" },
1143                internaldate.unwrap_or(""),
1144                content.len()
1145            ))
1146            .await?;
1147
1148        match self.read_response().await {
1149            Some(Ok(res)) => {
1150                if let Response::Continue { .. } = res.parsed() {
1151                    self.stream.as_mut().write_all(content).await?;
1152                    self.stream.as_mut().write_all(b"\r\n").await?;
1153                    self.stream.flush().await?;
1154                    self.conn
1155                        .check_done_ok(&id, Some(self.unsolicited_responses_tx.clone()))
1156                        .await?;
1157                    Ok(())
1158                } else {
1159                    Err(Error::Append)
1160                }
1161            }
1162            Some(Err(err)) => Err(err.into()),
1163            _ => Err(Error::Append),
1164        }
1165    }
1166
1167    /// The [`SEARCH` command](https://tools.ietf.org/html/rfc3501#section-6.4.4) searches the
1168    /// mailbox for messages that match the given `query`.  `query` consist of one or more search
1169    /// keys separated by spaces.  The response from the server contains a listing of [`Seq`]s
1170    /// corresponding to those messages that match the searching criteria.
1171    ///
1172    /// When multiple search keys are specified, the result is the intersection of all the messages
1173    /// that match those keys.  Or, in other words, only messages that match *all* the keys. For
1174    /// example, the criteria
1175    ///
1176    /// ```text
1177    /// DELETED FROM "SMITH" SINCE 1-Feb-1994
1178    /// ```
1179    ///
1180    /// refers to all deleted messages from Smith that were placed in the mailbox since February 1,
1181    /// 1994.  A search key can also be a parenthesized list of one or more search keys (e.g., for
1182    /// use with the `OR` and `NOT` keys).
1183    ///
1184    /// In all search keys that use strings, a message matches the key if the string is a substring
1185    /// of the field.  The matching is case-insensitive.
1186    ///
1187    /// Below is a selection of common search keys.  The full list can be found in the
1188    /// specification of the [`SEARCH command`](https://tools.ietf.org/html/rfc3501#section-6.4.4).
1189    ///
1190    ///  - `NEW`: Messages that have [`Flag::Recent`] set but not [`Flag::Seen`]. This is functionally equivalent to `(RECENT UNSEEN)`.
1191    ///  - `OLD`: Messages that do not have [`Flag::Recent`] set.  This is functionally equivalent to `NOT RECENT` (as opposed to `NOT NEW`).
1192    ///  - `RECENT`: Messages that have [`Flag::Recent`] set.
1193    ///  - `ANSWERED`: Messages with [`Flag::Answered`] set.
1194    ///  - `DELETED`: Messages with [`Flag::Deleted`] set.
1195    ///  - `DRAFT`: Messages with [`Flag::Draft`] set.
1196    ///  - `FLAGGED`: Messages with [`Flag::Flagged`] set.
1197    ///  - `SEEN`: Messages that have [`Flag::Seen`] set.
1198    ///  - `<sequence set>`: Messages with message sequence numbers corresponding to the specified message sequence number set.
1199    ///  - `UID <sequence set>`: Messages with [`Uid`] corresponding to the specified unique identifier set.  Sequence set ranges are permitted.
1200    ///
1201    ///  - `SUBJECT <string>`: Messages that contain the specified string in the envelope structure's `SUBJECT` field.
1202    ///  - `BODY <string>`: Messages that contain the specified string in the body of the message.
1203    ///  - `FROM <string>`: Messages that contain the specified string in the envelope structure's `FROM` field.
1204    ///  - `TO <string>`: Messages that contain the specified string in the envelope structure's `TO` field.
1205    ///
1206    ///  - `NOT <search-key>`: Messages that do not match the specified search key.
1207    ///  - `OR <search-key1> <search-key2>`: Messages that match either search key.
1208    ///
1209    ///  - `BEFORE <date>`: Messages whose internal date (disregarding time and timezone) is earlier than the specified date.
1210    ///  - `SINCE <date>`: Messages whose internal date (disregarding time and timezone) is within or later than the specified date.
1211    pub async fn search<S: AsRef<str>>(&mut self, query: S) -> Result<HashSet<Seq>> {
1212        let id = self
1213            .run_command(&format!("SEARCH {}", query.as_ref()))
1214            .await?;
1215        let seqs = parse_ids(
1216            &mut self.conn.stream,
1217            self.unsolicited_responses_tx.clone(),
1218            id,
1219        )
1220        .await?;
1221
1222        Ok(seqs)
1223    }
1224
1225    /// Equivalent to [`Session::search`], except that the returned identifiers
1226    /// are [`Uid`] instead of [`Seq`]. See also the [`UID`
1227    /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
1228    pub async fn uid_search<S: AsRef<str>>(&mut self, query: S) -> Result<HashSet<Uid>> {
1229        let id = self
1230            .run_command(&format!("UID SEARCH {}", query.as_ref()))
1231            .await?;
1232        let uids = parse_ids(
1233            &mut self.conn.stream,
1234            self.unsolicited_responses_tx.clone(),
1235            id,
1236        )
1237        .await?;
1238
1239        Ok(uids)
1240    }
1241
1242    /// The [`GETQUOTA` command](https://tools.ietf.org/html/rfc2087#section-4.2)
1243    pub async fn get_quota(&mut self, quota_root: &str) -> Result<Quota> {
1244        let id = self
1245            .run_command(format!("GETQUOTA {}", quote!(quota_root)))
1246            .await?;
1247        let c = parse_get_quota(
1248            &mut self.conn.stream,
1249            self.unsolicited_responses_tx.clone(),
1250            id,
1251        )
1252        .await?;
1253        Ok(c)
1254    }
1255
1256    /// The [`GETQUOTAROOT` command](https://tools.ietf.org/html/rfc2087#section-4.3)
1257    pub async fn get_quota_root(
1258        &mut self,
1259        mailbox_name: &str,
1260    ) -> Result<(Vec<QuotaRoot>, Vec<Quota>)> {
1261        let id = self
1262            .run_command(format!("GETQUOTAROOT {}", quote!(mailbox_name)))
1263            .await?;
1264        let c = parse_get_quota_root(
1265            &mut self.conn.stream,
1266            self.unsolicited_responses_tx.clone(),
1267            id,
1268        )
1269        .await?;
1270        Ok(c)
1271    }
1272
1273    /// The [`GETMETADATA` command](https://datatracker.ietf.org/doc/html/rfc5464.html#section-4.2)
1274    pub async fn get_metadata(
1275        &mut self,
1276        mailbox_name: &str,
1277        options: &str,
1278        entry_specifier: &str,
1279    ) -> Result<Vec<Metadata>> {
1280        let options = if options.is_empty() {
1281            String::new()
1282        } else {
1283            format!(" {options}")
1284        };
1285        let id = self
1286            .run_command(format!(
1287                "GETMETADATA {} {}{}",
1288                quote!(mailbox_name),
1289                options,
1290                entry_specifier
1291            ))
1292            .await?;
1293        let metadata = parse_metadata(
1294            &mut self.conn.stream,
1295            mailbox_name,
1296            self.unsolicited_responses_tx.clone(),
1297            id,
1298        )
1299        .await?;
1300        Ok(metadata)
1301    }
1302
1303    /// The [`ID` command](https://datatracker.ietf.org/doc/html/rfc2971)
1304    ///
1305    /// `identification` is an iterable sequence of pairs such as `("name", Some("MyMailClient"))`.
1306    pub async fn id(
1307        &mut self,
1308        identification: impl IntoIterator<Item = (&str, Option<&str>)>,
1309    ) -> Result<Option<HashMap<String, String>>> {
1310        let id = self
1311            .run_command(format!("ID ({})", format_identification(identification)))
1312            .await?;
1313        let server_identification = parse_id(
1314            &mut self.conn.stream,
1315            self.unsolicited_responses_tx.clone(),
1316            id,
1317        )
1318        .await?;
1319        Ok(server_identification)
1320    }
1321
1322    /// Similar to `id`, but don't identify ourselves.
1323    ///
1324    /// Sends `ID NIL` command and returns server response.
1325    pub async fn id_nil(&mut self) -> Result<Option<HashMap<String, String>>> {
1326        let id = self.run_command("ID NIL").await?;
1327        let server_identification = parse_id(
1328            &mut self.conn.stream,
1329            self.unsolicited_responses_tx.clone(),
1330            id,
1331        )
1332        .await?;
1333        Ok(server_identification)
1334    }
1335
1336    // these are only here because they are public interface, the rest is in `Connection`
1337    /// Runs a command and checks if it returns OK.
1338    pub async fn run_command_and_check_ok<S: AsRef<str>>(&mut self, command: S) -> Result<()> {
1339        self.conn
1340            .run_command_and_check_ok(
1341                command.as_ref(),
1342                Some(self.unsolicited_responses_tx.clone()),
1343            )
1344            .await?;
1345
1346        Ok(())
1347    }
1348
1349    /// Runs any command passed to it.
1350    pub async fn run_command<S: AsRef<str>>(&mut self, command: S) -> Result<RequestId> {
1351        let id = self.conn.run_command(command.as_ref()).await?;
1352
1353        Ok(id)
1354    }
1355
1356    /// Runs an arbitrary command, without adding a tag to it.
1357    pub async fn run_command_untagged<S: AsRef<str>>(&mut self, command: S) -> Result<()> {
1358        self.conn.run_command_untagged(command.as_ref()).await?;
1359
1360        Ok(())
1361    }
1362
1363    /// Read the next response on the connection.
1364    pub async fn read_response(&mut self) -> Option<io::Result<ResponseData>> {
1365        self.conn.read_response().await
1366    }
1367
1368    /// Enable server capabilities for this authenticated session.
1369    pub async fn enable<S: AsRef<str>>(&mut self, caps: &[S]) -> Result<Capabilities> {
1370        let command = format!(
1371            "ENABLE {}",
1372            caps.iter()
1373                .map(|cap| cap.as_ref())
1374                .collect::<Vec<_>>()
1375                .join(" ")
1376        );
1377        let id = self.run_command(command).await?;
1378        parse_capabilities(
1379            &mut self.conn.stream,
1380            self.unsolicited_responses_tx.clone(),
1381            id,
1382        )
1383        .await
1384    }
1385
1386    /// Return namespace information as defined by RFC 2342.
1387    pub async fn namespace(&mut self) -> Result<Namespace> {
1388        let responses = self.collect_responses("NAMESPACE").await?;
1389        let mut namespace = Namespace::default();
1390        for response in &responses {
1391            if let Some(raw) = response.raw_str() {
1392                if raw.starts_with("* NAMESPACE ") {
1393                    namespace = parse_namespace_response(raw)?;
1394                }
1395            }
1396        }
1397        Ok(namespace)
1398    }
1399
1400    /// LIST with STATUS return data in one roundtrip.
1401    pub async fn list_status<S1: AsRef<str>, S2: AsRef<str>>(
1402        &mut self,
1403        reference_name: S1,
1404        mailbox_pattern: S2,
1405    ) -> Result<Vec<ListStatus>> {
1406        let command = format!(
1407            "LIST {} {} RETURN (STATUS (MESSAGES UNSEEN UIDNEXT UIDVALIDITY HIGHESTMODSEQ))",
1408            quote!(reference_name.as_ref()),
1409            quote!(mailbox_pattern.as_ref())
1410        );
1411        let id = self.run_command(command).await?;
1412        parse_list_status(
1413            &mut self.conn.stream,
1414            self.unsolicited_responses_tx.clone(),
1415            id,
1416        )
1417        .await
1418    }
1419
1420    /// SELECT a mailbox using QRESYNC parameters.
1421    pub async fn select_qresync<S1: AsRef<str>, S2: AsRef<str>>(
1422        &mut self,
1423        mailbox_name: S1,
1424        qresync_args: S2,
1425    ) -> Result<QresyncResponse> {
1426        let command = format!(
1427            "SELECT {} (QRESYNC ({}))",
1428            validate_str(mailbox_name.as_ref())?,
1429            qresync_args.as_ref()
1430        );
1431        let id = self.run_command(command).await?;
1432        parse_qresync(
1433            &mut self.conn.stream,
1434            self.unsolicited_responses_tx.clone(),
1435            id,
1436        )
1437        .await
1438    }
1439
1440    async fn collect_responses<S: AsRef<str>>(&mut self, command: S) -> Result<Vec<ResponseData>> {
1441        let id = self.run_command(command).await?;
1442        let mut responses = Vec::new();
1443
1444        while let Some(response) = self.read_response().await {
1445            let response = response?;
1446            match response.parsed() {
1447                Response::Done {
1448                    tag,
1449                    status,
1450                    code,
1451                    information,
1452                    ..
1453                } if tag == &id => {
1454                    self.conn
1455                        .check_status_ok(status, code.as_ref(), information.as_deref())?;
1456                    break;
1457                }
1458                _ => responses.push(response),
1459            }
1460        }
1461
1462        Ok(responses)
1463    }
1464}
1465
1466fn parse_namespace_response(raw: &str) -> Result<Namespace> {
1467    let payload = raw
1468        .trim()
1469        .strip_prefix("* NAMESPACE ")
1470        .ok_or_else(|| Error::Parse(ParseError::Invalid(raw.as_bytes().to_vec())))?;
1471    let sections = split_namespace_sections(payload)?;
1472    Ok(Namespace {
1473        personal: parse_namespace_group(sections.first().copied().unwrap_or("NIL")),
1474        other_users: parse_namespace_group(sections.get(1).copied().unwrap_or("NIL")),
1475        shared: parse_namespace_group(sections.get(2).copied().unwrap_or("NIL")),
1476    })
1477}
1478
1479fn split_namespace_sections(payload: &str) -> Result<Vec<&str>> {
1480    let mut sections = Vec::new();
1481    let mut depth = 0i32;
1482    let mut in_quotes = false;
1483    let bytes = payload.as_bytes();
1484    let mut start = 0usize;
1485
1486    for (idx, ch) in payload.char_indices() {
1487        match ch {
1488            '"' if idx == 0 || bytes[idx.saturating_sub(1)] != b'\\' => in_quotes = !in_quotes,
1489            '(' if !in_quotes => depth += 1,
1490            ')' if !in_quotes => depth -= 1,
1491            ' ' if !in_quotes && depth == 0 => {
1492                sections.push(payload[start..idx].trim());
1493                start = idx + 1;
1494            }
1495            _ => {}
1496        }
1497    }
1498
1499    sections.push(payload[start..].trim());
1500    if sections.len() == 3 {
1501        Ok(sections)
1502    } else {
1503        Err(Error::Parse(ParseError::Invalid(
1504            payload.as_bytes().to_vec(),
1505        )))
1506    }
1507}
1508
1509fn parse_namespace_group(section: &str) -> Vec<NamespaceEntry> {
1510    if section.eq_ignore_ascii_case("NIL") {
1511        return Vec::new();
1512    }
1513
1514    let mut entries = Vec::new();
1515    let mut rest = section;
1516    while let Some(start) = rest.find("(\"") {
1517        let slice = &rest[start + 2..];
1518        let Some(prefix_end) = slice.find('"') else {
1519            break;
1520        };
1521        let prefix = &slice[..prefix_end];
1522        let slice = &slice[prefix_end + 1..].trim_start();
1523
1524        let (delimiter, next_rest) = if let Some(stripped) = slice.strip_prefix("NIL") {
1525            (None, stripped)
1526        } else if let Some(stripped) = slice.strip_prefix('"') {
1527            let Some(delim_end) = stripped.find('"') else {
1528                break;
1529            };
1530            (
1531                Some(stripped[..delim_end].to_string()),
1532                &stripped[delim_end + 1..],
1533            )
1534        } else {
1535            break;
1536        };
1537
1538        entries.push(NamespaceEntry {
1539            prefix: prefix.to_string(),
1540            delimiter,
1541        });
1542        rest = next_rest;
1543    }
1544
1545    entries
1546}
1547
1548impl<T: Read + Write + Unpin + fmt::Debug> Connection<T> {
1549    unsafe_pinned!(stream: ImapStream<T>);
1550
1551    /// Convert this connection into the raw underlying stream.
1552    pub fn into_inner(self) -> T {
1553        let Self { stream, .. } = self;
1554        stream.into_inner()
1555    }
1556
1557    /// Read the next response on the connection.
1558    pub async fn read_response(&mut self) -> Option<io::Result<ResponseData>> {
1559        self.stream.next().await
1560    }
1561
1562    pub(crate) async fn run_command_untagged(&mut self, command: &str) -> Result<()> {
1563        self.stream
1564            .encode(Request(None, command.as_bytes().into()))
1565            .await?;
1566        self.stream.flush().await?;
1567        Ok(())
1568    }
1569
1570    pub(crate) async fn run_command(&mut self, command: &str) -> Result<RequestId> {
1571        let request_id = self.request_ids.next().unwrap(); // safe: never returns Err
1572        self.stream
1573            .encode(Request(Some(request_id.clone()), command.as_bytes().into()))
1574            .await?;
1575        self.stream.flush().await?;
1576        Ok(request_id)
1577    }
1578
1579    /// Execute a command and check that the next response is a matching done.
1580    pub async fn run_command_and_check_ok(
1581        &mut self,
1582        command: &str,
1583        unsolicited: Option<channel::Sender<UnsolicitedResponse>>,
1584    ) -> Result<()> {
1585        let id = self.run_command(command).await?;
1586        self.check_done_ok(&id, unsolicited).await?;
1587
1588        Ok(())
1589    }
1590
1591    pub(crate) async fn check_done_ok(
1592        &mut self,
1593        id: &RequestId,
1594        unsolicited: Option<channel::Sender<UnsolicitedResponse>>,
1595    ) -> Result<()> {
1596        if let Some(first_res) = self.stream.next().await {
1597            self.check_done_ok_from(id, unsolicited, first_res?).await
1598        } else {
1599            Err(Error::ConnectionLost)
1600        }
1601    }
1602
1603    pub(crate) async fn check_done_ok_from(
1604        &mut self,
1605        id: &RequestId,
1606        unsolicited: Option<channel::Sender<UnsolicitedResponse>>,
1607        mut response: ResponseData,
1608    ) -> Result<()> {
1609        loop {
1610            if let Response::Done {
1611                status,
1612                code,
1613                information,
1614                tag,
1615            } = response.parsed()
1616            {
1617                self.check_status_ok(status, code.as_ref(), information.as_deref())?;
1618
1619                if tag == id {
1620                    return Ok(());
1621                }
1622            }
1623
1624            if let Some(unsolicited) = unsolicited.clone() {
1625                handle_unilateral(response, unsolicited);
1626            }
1627
1628            if let Some(res) = self.stream.next().await {
1629                response = res?;
1630            } else {
1631                return Err(Error::ConnectionLost);
1632            }
1633        }
1634    }
1635
1636    pub(crate) fn check_status_ok(
1637        &self,
1638        status: &imap_proto::Status,
1639        code: Option<&imap_proto::ResponseCode<'_>>,
1640        information: Option<&str>,
1641    ) -> Result<()> {
1642        use imap_proto::Status;
1643        match status {
1644            Status::Ok => Ok(()),
1645            Status::Bad => Err(Error::Bad(format!(
1646                "code: {:?}, info: {:?}",
1647                code, information
1648            ))),
1649            Status::No => Err(Error::No(format!(
1650                "code: {:?}, info: {:?}",
1651                code, information
1652            ))),
1653            _ => Err(Error::Io(io::Error::other(format!(
1654                "status: {:?}, code: {:?}, information: {:?}",
1655                status, code, information
1656            )))),
1657        }
1658    }
1659}
1660
1661fn validate_str(value: &str) -> Result<String> {
1662    let quoted = quote!(value);
1663    if quoted.find('\n').is_some() {
1664        return Err(Error::Validate(ValidateError('\n')));
1665    }
1666    if quoted.find('\r').is_some() {
1667        return Err(Error::Validate(ValidateError('\r')));
1668    }
1669    Ok(quoted)
1670}
1671
1672#[cfg(test)]
1673mod tests {
1674    use pretty_assertions::assert_eq;
1675
1676    use super::super::error::Result;
1677    use super::super::mock_stream::MockStream;
1678    use super::*;
1679    use std::borrow::Cow;
1680    use std::future::Future;
1681
1682    use async_std::sync::{Arc, Mutex};
1683    use imap_proto::Status;
1684
1685    macro_rules! mock_client {
1686        ($s:expr) => {
1687            Client::new($s)
1688        };
1689    }
1690
1691    macro_rules! mock_session {
1692        ($s:expr) => {
1693            Session::new(mock_client!($s).conn)
1694        };
1695    }
1696
1697    macro_rules! assert_eq_bytes {
1698        ($a:expr, $b:expr, $c:expr) => {
1699            assert_eq!(
1700                std::str::from_utf8($a).unwrap(),
1701                std::str::from_utf8($b).unwrap(),
1702                $c
1703            )
1704        };
1705    }
1706
1707    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1708    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1709    async fn fetch_body() {
1710        let response = "a0 OK Logged in.\r\n\
1711                        * 2 FETCH (BODY[TEXT] {3}\r\nfoo)\r\n\
1712                        a0 OK FETCH completed\r\n";
1713        let mut session = mock_session!(MockStream::new(response.as_bytes().to_vec()));
1714        session.read_response().await.unwrap().unwrap();
1715        session.read_response().await.unwrap().unwrap();
1716    }
1717
1718    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1719    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1720    async fn readline_delay_read() {
1721        let greeting = "* OK Dovecot ready.\r\n";
1722        let mock_stream = MockStream::default()
1723            .with_buf(greeting.as_bytes().to_vec())
1724            .with_delay();
1725
1726        let mut client = mock_client!(mock_stream);
1727        let actual_response = client.read_response().await.unwrap().unwrap();
1728        assert_eq!(
1729            actual_response.parsed(),
1730            &Response::Data {
1731                status: Status::Ok,
1732                code: None,
1733                information: Some(Cow::Borrowed("Dovecot ready.")),
1734            }
1735        );
1736    }
1737
1738    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1739    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1740    async fn readline_eof() {
1741        let mock_stream = MockStream::default().with_eof();
1742        let mut client = mock_client!(mock_stream);
1743        let res = client.read_response().await;
1744        assert!(res.is_none());
1745    }
1746
1747    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1748    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1749    #[should_panic]
1750    async fn readline_err() {
1751        // TODO Check the error test
1752        let mock_stream = MockStream::default().with_err();
1753        let mut client = mock_client!(mock_stream);
1754        client.read_response().await.unwrap().unwrap();
1755    }
1756
1757    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1758    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1759    async fn authenticate() {
1760        let response = b"+ YmFy\r\n\
1761                         A0001 OK Logged in\r\n"
1762            .to_vec();
1763        let command = "A0001 AUTHENTICATE PLAIN\r\n\
1764                       Zm9v\r\n";
1765        let mock_stream = MockStream::new(response);
1766        let client = mock_client!(mock_stream);
1767        enum Authenticate {
1768            Auth,
1769        }
1770        impl Authenticator for &Authenticate {
1771            type Response = Vec<u8>;
1772            fn process(&mut self, challenge: &[u8]) -> Self::Response {
1773                assert!(challenge == b"bar", "Invalid authenticate challenge");
1774                b"foo".to_vec()
1775            }
1776        }
1777        let session = client
1778            .authenticate("PLAIN", &Authenticate::Auth)
1779            .await
1780            .ok()
1781            .unwrap();
1782        assert_eq_bytes!(
1783            &session.stream.inner.written_buf,
1784            command.as_bytes(),
1785            "Invalid authenticate command"
1786        );
1787    }
1788
1789    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1790    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1791    async fn login() {
1792        let response = b"A0001 OK Logged in\r\n".to_vec();
1793        let username = "username";
1794        let password = "password";
1795        let command = format!("A0001 LOGIN {} {}\r\n", quote!(username), quote!(password));
1796        let mock_stream = MockStream::new(response);
1797        let client = mock_client!(mock_stream);
1798        if let Ok(session) = client.login(username, password).await {
1799            assert_eq!(
1800                session.stream.inner.written_buf,
1801                command.as_bytes().to_vec(),
1802                "Invalid login command"
1803            );
1804        } else {
1805            unreachable!("invalid login");
1806        }
1807    }
1808
1809    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1810    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1811    async fn logout() {
1812        let response = b"A0001 OK Logout completed.\r\n".to_vec();
1813        let command = "A0001 LOGOUT\r\n";
1814        let mock_stream = MockStream::new(response);
1815        let mut session = mock_session!(mock_stream);
1816        session.logout().await.unwrap();
1817        assert!(
1818            session.stream.inner.written_buf == command.as_bytes().to_vec(),
1819            "Invalid logout command"
1820        );
1821    }
1822
1823    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1824    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1825    async fn rename() {
1826        let response = b"A0001 OK RENAME completed\r\n".to_vec();
1827        let current_mailbox_name = "INBOX";
1828        let new_mailbox_name = "NEWINBOX";
1829        let command = format!(
1830            "A0001 RENAME {} {}\r\n",
1831            quote!(current_mailbox_name),
1832            quote!(new_mailbox_name)
1833        );
1834        let mock_stream = MockStream::new(response);
1835        let mut session = mock_session!(mock_stream);
1836        session
1837            .rename(current_mailbox_name, new_mailbox_name)
1838            .await
1839            .unwrap();
1840        assert!(
1841            session.stream.inner.written_buf == command.as_bytes().to_vec(),
1842            "Invalid rename command"
1843        );
1844    }
1845
1846    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1847    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1848    async fn subscribe() {
1849        let response = b"A0001 OK SUBSCRIBE completed\r\n".to_vec();
1850        let mailbox = "INBOX";
1851        let command = format!("A0001 SUBSCRIBE {}\r\n", quote!(mailbox));
1852        let mock_stream = MockStream::new(response);
1853        let mut session = mock_session!(mock_stream);
1854        session.subscribe(mailbox).await.unwrap();
1855        assert!(
1856            session.stream.inner.written_buf == command.as_bytes().to_vec(),
1857            "Invalid subscribe command"
1858        );
1859    }
1860
1861    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1862    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1863    async fn unsubscribe() {
1864        let response = b"A0001 OK UNSUBSCRIBE completed\r\n".to_vec();
1865        let mailbox = "INBOX";
1866        let command = format!("A0001 UNSUBSCRIBE {}\r\n", quote!(mailbox));
1867        let mock_stream = MockStream::new(response);
1868        let mut session = mock_session!(mock_stream);
1869        session.unsubscribe(mailbox).await.unwrap();
1870        assert!(
1871            session.stream.inner.written_buf == command.as_bytes().to_vec(),
1872            "Invalid unsubscribe command"
1873        );
1874    }
1875
1876    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1877    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1878    async fn expunge() {
1879        let response = b"A0001 OK EXPUNGE completed\r\n".to_vec();
1880        let mock_stream = MockStream::new(response);
1881        let mut session = mock_session!(mock_stream);
1882        session.expunge().await.unwrap().collect::<Vec<_>>().await;
1883        assert!(
1884            session.stream.inner.written_buf == b"A0001 EXPUNGE\r\n".to_vec(),
1885            "Invalid expunge command"
1886        );
1887    }
1888
1889    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1890    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1891    async fn uid_expunge() {
1892        let response = b"* 2 EXPUNGE\r\n\
1893            * 3 EXPUNGE\r\n\
1894            * 4 EXPUNGE\r\n\
1895            A0001 OK UID EXPUNGE completed\r\n"
1896            .to_vec();
1897        let mock_stream = MockStream::new(response);
1898        let mut session = mock_session!(mock_stream);
1899        session
1900            .uid_expunge("2:4")
1901            .await
1902            .unwrap()
1903            .collect::<Vec<_>>()
1904            .await;
1905        assert!(
1906            session.stream.inner.written_buf == b"A0001 UID EXPUNGE 2:4\r\n".to_vec(),
1907            "Invalid expunge command"
1908        );
1909    }
1910
1911    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1912    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1913    async fn check() {
1914        let response = b"A0001 OK CHECK completed\r\n".to_vec();
1915        let mock_stream = MockStream::new(response);
1916        let mut session = mock_session!(mock_stream);
1917        session.check().await.unwrap();
1918        assert!(
1919            session.stream.inner.written_buf == b"A0001 CHECK\r\n".to_vec(),
1920            "Invalid check command"
1921        );
1922    }
1923
1924    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1925    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1926    async fn examine() {
1927        let response = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n\
1928            * OK [PERMANENTFLAGS ()] Read-only mailbox.\r\n\
1929            * 1 EXISTS\r\n\
1930            * 1 RECENT\r\n\
1931            * OK [UNSEEN 1] First unseen.\r\n\
1932            * OK [UIDVALIDITY 1257842737] UIDs valid\r\n\
1933            * OK [UIDNEXT 2] Predicted next UID\r\n\
1934            A0001 OK [READ-ONLY] Select completed.\r\n"
1935            .to_vec();
1936        let expected_mailbox = Mailbox {
1937            flags: vec![
1938                Flag::Answered,
1939                Flag::Flagged,
1940                Flag::Deleted,
1941                Flag::Seen,
1942                Flag::Draft,
1943            ],
1944            exists: 1,
1945            recent: 1,
1946            unseen: Some(1),
1947            permanent_flags: vec![],
1948            uid_next: Some(2),
1949            uid_validity: Some(1257842737),
1950            highest_modseq: None,
1951        };
1952        let mailbox_name = "INBOX";
1953        let command = format!("A0001 EXAMINE {}\r\n", quote!(mailbox_name));
1954        let mock_stream = MockStream::new(response);
1955        let mut session = mock_session!(mock_stream);
1956        let mailbox = session.examine(mailbox_name).await.unwrap();
1957        assert!(
1958            session.stream.inner.written_buf == command.as_bytes().to_vec(),
1959            "Invalid examine command"
1960        );
1961        assert_eq!(mailbox, expected_mailbox);
1962    }
1963
1964    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1965    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1966    async fn select() {
1967        let response = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n\
1968            * OK [PERMANENTFLAGS (\\* \\Answered \\Flagged \\Deleted \\Draft \\Seen)] \
1969              Read-only mailbox.\r\n\
1970            * 1 EXISTS\r\n\
1971            * 1 RECENT\r\n\
1972            * OK [UNSEEN 1] First unseen.\r\n\
1973            * OK [UIDVALIDITY 1257842737] UIDs valid\r\n\
1974            * OK [UIDNEXT 2] Predicted next UID\r\n\
1975            * OK [HIGHESTMODSEQ 90060115205545359] Highest mailbox modsequence\r\n\
1976            A0001 OK [READ-ONLY] Select completed.\r\n"
1977            .to_vec();
1978        let expected_mailbox = Mailbox {
1979            flags: vec![
1980                Flag::Answered,
1981                Flag::Flagged,
1982                Flag::Deleted,
1983                Flag::Seen,
1984                Flag::Draft,
1985            ],
1986            exists: 1,
1987            recent: 1,
1988            unseen: Some(1),
1989            permanent_flags: vec![
1990                Flag::MayCreate,
1991                Flag::Answered,
1992                Flag::Flagged,
1993                Flag::Deleted,
1994                Flag::Draft,
1995                Flag::Seen,
1996            ],
1997            uid_next: Some(2),
1998            uid_validity: Some(1257842737),
1999            highest_modseq: Some(90060115205545359),
2000        };
2001        let mailbox_name = "INBOX";
2002        let command = format!("A0001 SELECT {}\r\n", quote!(mailbox_name));
2003        let mock_stream = MockStream::new(response);
2004        let mut session = mock_session!(mock_stream);
2005        let mailbox = session.select(mailbox_name).await.unwrap();
2006        assert!(
2007            session.stream.inner.written_buf == command.as_bytes().to_vec(),
2008            "Invalid select command"
2009        );
2010        assert_eq!(mailbox, expected_mailbox);
2011    }
2012
2013    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2014    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2015    async fn search() {
2016        let response = b"* SEARCH 1 2 3 4 5\r\n\
2017            A0001 OK Search completed\r\n"
2018            .to_vec();
2019        let mock_stream = MockStream::new(response);
2020        let mut session = mock_session!(mock_stream);
2021        let ids = session.search("Unseen").await.unwrap();
2022        let ids: HashSet<u32> = ids.iter().cloned().collect();
2023        assert!(
2024            session.stream.inner.written_buf == b"A0001 SEARCH Unseen\r\n".to_vec(),
2025            "Invalid search command"
2026        );
2027        assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
2028    }
2029
2030    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2031    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2032    async fn uid_search() {
2033        let response = b"* SEARCH 1 2 3 4 5\r\n\
2034            A0001 OK Search completed\r\n"
2035            .to_vec();
2036        let mock_stream = MockStream::new(response);
2037        let mut session = mock_session!(mock_stream);
2038        let ids = session.uid_search("Unseen").await.unwrap();
2039        let ids: HashSet<Uid> = ids.iter().cloned().collect();
2040        assert!(
2041            session.stream.inner.written_buf == b"A0001 UID SEARCH Unseen\r\n".to_vec(),
2042            "Invalid search command"
2043        );
2044        assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
2045    }
2046
2047    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2048    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2049    async fn uid_search_unordered() {
2050        let response = b"* SEARCH 1 2 3 4 5\r\n\
2051            A0002 OK CAPABILITY completed\r\n\
2052            A0001 OK Search completed\r\n"
2053            .to_vec();
2054        let mock_stream = MockStream::new(response);
2055        let mut session = mock_session!(mock_stream);
2056        let ids = session.uid_search("Unseen").await.unwrap();
2057        let ids: HashSet<Uid> = ids.iter().cloned().collect();
2058        assert!(
2059            session.stream.inner.written_buf == b"A0001 UID SEARCH Unseen\r\n".to_vec(),
2060            "Invalid search command"
2061        );
2062        assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
2063    }
2064
2065    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2066    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2067    async fn capability() {
2068        let response = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n\
2069            A0001 OK CAPABILITY completed\r\n"
2070            .to_vec();
2071        let expected_capabilities = vec!["IMAP4rev1", "STARTTLS", "AUTH=GSSAPI", "LOGINDISABLED"];
2072        let mock_stream = MockStream::new(response);
2073        let mut session = mock_session!(mock_stream);
2074        let capabilities = session.capabilities().await.unwrap();
2075        assert!(
2076            session.stream.inner.written_buf == b"A0001 CAPABILITY\r\n".to_vec(),
2077            "Invalid capability command"
2078        );
2079        assert_eq!(capabilities.len(), 4);
2080        for e in expected_capabilities {
2081            assert!(capabilities.has_str(e));
2082        }
2083    }
2084
2085    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2086    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2087    async fn create() {
2088        let response = b"A0001 OK CREATE completed\r\n".to_vec();
2089        let mailbox_name = "INBOX";
2090        let command = format!("A0001 CREATE {}\r\n", quote!(mailbox_name));
2091        let mock_stream = MockStream::new(response);
2092        let mut session = mock_session!(mock_stream);
2093        session.create(mailbox_name).await.unwrap();
2094        assert!(
2095            session.stream.inner.written_buf == command.as_bytes().to_vec(),
2096            "Invalid create command"
2097        );
2098    }
2099
2100    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2101    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2102    async fn delete() {
2103        let response = b"A0001 OK DELETE completed\r\n".to_vec();
2104        let mailbox_name = "INBOX";
2105        let command = format!("A0001 DELETE {}\r\n", quote!(mailbox_name));
2106        let mock_stream = MockStream::new(response);
2107        let mut session = mock_session!(mock_stream);
2108        session.delete(mailbox_name).await.unwrap();
2109        assert!(
2110            session.stream.inner.written_buf == command.as_bytes().to_vec(),
2111            "Invalid delete command"
2112        );
2113    }
2114
2115    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2116    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2117    async fn noop() {
2118        let response = b"A0001 OK NOOP completed\r\n".to_vec();
2119        let mock_stream = MockStream::new(response);
2120        let mut session = mock_session!(mock_stream);
2121        session.noop().await.unwrap();
2122        assert!(
2123            session.stream.inner.written_buf == b"A0001 NOOP\r\n".to_vec(),
2124            "Invalid noop command"
2125        );
2126    }
2127
2128    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2129    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2130    async fn close() {
2131        let response = b"A0001 OK CLOSE completed\r\n".to_vec();
2132        let mock_stream = MockStream::new(response);
2133        let mut session = mock_session!(mock_stream);
2134        session.close().await.unwrap();
2135        assert!(
2136            session.stream.inner.written_buf == b"A0001 CLOSE\r\n".to_vec(),
2137            "Invalid close command"
2138        );
2139    }
2140
2141    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2142    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2143    async fn store() {
2144        generic_store(" ", |c, set, query| async move {
2145            c.lock()
2146                .await
2147                .store(set, query)
2148                .await?
2149                .collect::<Vec<_>>()
2150                .await;
2151            Ok(())
2152        })
2153        .await;
2154    }
2155
2156    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2157    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2158    async fn uid_store() {
2159        generic_store(" UID ", |c, set, query| async move {
2160            c.lock()
2161                .await
2162                .uid_store(set, query)
2163                .await?
2164                .collect::<Vec<_>>()
2165                .await;
2166            Ok(())
2167        })
2168        .await;
2169    }
2170
2171    async fn generic_store<'a, F, T, K>(prefix: &'a str, op: F)
2172    where
2173        F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2174        K: 'a + Future<Output = Result<T>>,
2175    {
2176        let res = "* 2 FETCH (FLAGS (\\Deleted \\Seen))\r\n\
2177                   * 3 FETCH (FLAGS (\\Deleted))\r\n\
2178                   * 4 FETCH (FLAGS (\\Deleted \\Flagged \\Seen))\r\n\
2179                   A0001 OK STORE completed\r\n";
2180
2181        generic_with_uid(res, "STORE", "2.4", "+FLAGS (\\Deleted)", prefix, op).await;
2182    }
2183
2184    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2185    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2186    async fn copy() {
2187        generic_copy(" ", |c, set, query| async move {
2188            c.lock().await.copy(set, query).await?;
2189            Ok(())
2190        })
2191        .await;
2192    }
2193
2194    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2195    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2196    async fn uid_copy() {
2197        generic_copy(" UID ", |c, set, query| async move {
2198            c.lock().await.uid_copy(set, query).await?;
2199            Ok(())
2200        })
2201        .await;
2202    }
2203
2204    async fn generic_copy<'a, F, T, K>(prefix: &'a str, op: F)
2205    where
2206        F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2207        K: 'a + Future<Output = Result<T>>,
2208    {
2209        generic_with_uid(
2210            "A0001 OK COPY completed\r\n",
2211            "COPY",
2212            "2:4",
2213            "MEETING",
2214            prefix,
2215            op,
2216        )
2217        .await;
2218    }
2219
2220    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2221    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2222    async fn mv() {
2223        let response = b"* OK [COPYUID 1511554416 142,399 41:42] Moved UIDs.\r\n\
2224            * 2 EXPUNGE\r\n\
2225            * 1 EXPUNGE\r\n\
2226            A0001 OK Move completed\r\n"
2227            .to_vec();
2228        let mailbox_name = "MEETING";
2229        let command = format!("A0001 MOVE 1:2 {}\r\n", quote!(mailbox_name));
2230        let mock_stream = MockStream::new(response);
2231        let mut session = mock_session!(mock_stream);
2232        session.mv("1:2", mailbox_name).await.unwrap();
2233        assert!(
2234            session.stream.inner.written_buf == command.as_bytes().to_vec(),
2235            "Invalid move command"
2236        );
2237    }
2238
2239    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2240    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2241    async fn uid_mv() {
2242        let response = b"* OK [COPYUID 1511554416 142,399 41:42] Moved UIDs.\r\n\
2243            * 2 EXPUNGE\r\n\
2244            * 1 EXPUNGE\r\n\
2245            A0001 OK Move completed\r\n"
2246            .to_vec();
2247        let mailbox_name = "MEETING";
2248        let command = format!("A0001 UID MOVE 41:42 {}\r\n", quote!(mailbox_name));
2249        let mock_stream = MockStream::new(response);
2250        let mut session = mock_session!(mock_stream);
2251        session.uid_mv("41:42", mailbox_name).await.unwrap();
2252        assert!(
2253            session.stream.inner.written_buf == command.as_bytes().to_vec(),
2254            "Invalid uid move command"
2255        );
2256    }
2257
2258    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2259    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2260    async fn fetch() {
2261        generic_fetch(" ", |c, seq, query| async move {
2262            c.lock()
2263                .await
2264                .fetch(seq, query)
2265                .await?
2266                .collect::<Vec<_>>()
2267                .await;
2268
2269            Ok(())
2270        })
2271        .await;
2272    }
2273
2274    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2275    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2276    async fn uid_fetch() {
2277        generic_fetch(" UID ", |c, seq, query| async move {
2278            c.lock()
2279                .await
2280                .uid_fetch(seq, query)
2281                .await?
2282                .collect::<Vec<_>>()
2283                .await;
2284            Ok(())
2285        })
2286        .await;
2287    }
2288
2289    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2290    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2291    async fn fetch_unexpected_eof() {
2292        // Connection is lost, there will never be any response.
2293        let response = b"".to_vec();
2294
2295        let mock_stream = MockStream::new(response);
2296        let mut session = mock_session!(mock_stream);
2297
2298        {
2299            let mut fetch_result = session
2300                .uid_fetch("1:*", "(FLAGS BODY.PEEK[])")
2301                .await
2302                .unwrap();
2303
2304            // Unexpected EOF.
2305            let err = fetch_result.next().await.unwrap().unwrap_err();
2306            let Error::Io(io_err) = err else {
2307                panic!("Unexpected error type: {err}")
2308            };
2309            assert_eq!(io_err.kind(), io::ErrorKind::UnexpectedEof);
2310        }
2311
2312        assert_eq!(
2313            session.stream.inner.written_buf,
2314            b"A0001 UID FETCH 1:* (FLAGS BODY.PEEK[])\r\n".to_vec()
2315        );
2316    }
2317
2318    async fn generic_fetch<'a, F, T, K>(prefix: &'a str, op: F)
2319    where
2320        F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2321        K: 'a + Future<Output = Result<T>>,
2322    {
2323        generic_with_uid(
2324            "A0001 OK FETCH completed\r\n",
2325            "FETCH",
2326            "1",
2327            "BODY[]",
2328            prefix,
2329            op,
2330        )
2331        .await;
2332    }
2333
2334    async fn generic_with_uid<'a, F, T, K>(
2335        res: &'a str,
2336        cmd: &'a str,
2337        seq: &'a str,
2338        query: &'a str,
2339        prefix: &'a str,
2340        op: F,
2341    ) where
2342        F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2343        K: 'a + Future<Output = Result<T>>,
2344    {
2345        let resp = res.as_bytes().to_vec();
2346        let line = format!("A0001{}{} {} {}\r\n", prefix, cmd, seq, query);
2347        let session = Arc::new(Mutex::new(mock_session!(MockStream::new(resp))));
2348
2349        {
2350            let _ = op(session.clone(), seq, query).await.unwrap();
2351        }
2352        assert!(
2353            session.lock().await.stream.inner.written_buf == line.as_bytes().to_vec(),
2354            "Invalid command"
2355        );
2356    }
2357
2358    #[test]
2359    fn quote_backslash() {
2360        assert_eq!("\"test\\\\text\"", quote!(r"test\text"));
2361    }
2362
2363    #[test]
2364    fn quote_dquote() {
2365        assert_eq!("\"test\\\"text\"", quote!("test\"text"));
2366    }
2367
2368    #[test]
2369    fn validate_random() {
2370        assert_eq!(
2371            "\"~iCQ_k;>[&\\\"sVCvUW`e<<P!wJ\"",
2372            &validate_str("~iCQ_k;>[&\"sVCvUW`e<<P!wJ").unwrap()
2373        );
2374    }
2375
2376    #[test]
2377    fn validate_newline() {
2378        if let Err(ref e) = validate_str("test\nstring") {
2379            if let Error::Validate(ref ve) = e {
2380                if ve.0 == '\n' {
2381                    return;
2382                }
2383            }
2384            panic!("Wrong error: {:?}", e);
2385        }
2386        panic!("No error");
2387    }
2388
2389    #[test]
2390    #[allow(unreachable_patterns)]
2391    fn validate_carriage_return() {
2392        if let Err(ref e) = validate_str("test\rstring") {
2393            if let Error::Validate(ref ve) = e {
2394                if ve.0 == '\r' {
2395                    return;
2396                }
2397            }
2398            panic!("Wrong error: {:?}", e);
2399        }
2400        panic!("No error");
2401    }
2402
2403    /// Emulates a server responding to `FETCH` requests
2404    /// with a body of 76 bytes of headers and N 74-byte lines,
2405    /// where N is the requested message sequence number.
2406    #[cfg(feature = "runtime-tokio")]
2407    async fn handle_client(stream: tokio::io::DuplexStream) -> Result<()> {
2408        use tokio::io::AsyncBufReadExt;
2409
2410        let (reader, mut writer) = tokio::io::split(stream);
2411        let reader = tokio::io::BufReader::new(reader);
2412
2413        let mut lines = reader.lines();
2414        while let Some(line) = lines.next_line().await? {
2415            let (request_id, request) = line.split_once(' ').unwrap();
2416            eprintln!("Received request {request_id}.");
2417
2418            let (id, _) = request
2419                .strip_prefix("FETCH ")
2420                .unwrap()
2421                .split_once(' ')
2422                .unwrap();
2423            let id = id.parse().unwrap();
2424
2425            let mut body = concat!(
2426                "From: Bob <bob@example.com>\r\n",
2427                "To: Alice <alice@example.org>\r\n",
2428                "Subject: Test\r\n",
2429                "Message-Id: <foobar@example.com>\r\n",
2430                "Date: Sun, 22 Mar 2020 00:00:00 +0100\r\n",
2431                "\r\n",
2432            )
2433            .to_string();
2434            for _ in 1..id {
2435                body +=
2436                    "012345678901234567890123456789012345678901234567890123456789012345678901\r\n";
2437            }
2438            let body_len = body.len();
2439
2440            let response = format!("* {id} FETCH (RFC822.SIZE {body_len} BODY[] {{{body_len}}}\r\n{body} FLAGS (\\Seen))\r\n");
2441            writer.write_all(response.as_bytes()).await?;
2442            writer
2443                .write_all(format!("{request_id} OK FETCH completed\r\n").as_bytes())
2444                .await?;
2445            writer.flush().await?;
2446        }
2447
2448        Ok(())
2449    }
2450
2451    /// Test requestng 1000 messages each larger than a previous one.
2452    ///
2453    /// This is a regression test for v0.6.0 async-imap,
2454    /// which sometimes failed to allocate free buffer space,
2455    /// read into a buffer of zero size and erroneously detected it
2456    /// as the end of stream.
2457    #[cfg(feature = "runtime-tokio")]
2458    #[cfg_attr(
2459        feature = "runtime-tokio",
2460        tokio::test(flavor = "multi_thread", worker_threads = 2)
2461    )]
2462    async fn large_fetch() -> Result<()> {
2463        use futures::TryStreamExt;
2464
2465        let (client, server) = tokio::io::duplex(4096);
2466        tokio::spawn(handle_client(server));
2467
2468        let client = crate::Client::new(client);
2469        let mut imap_session = Session::new(client.conn);
2470
2471        for i in 200..300 {
2472            eprintln!("Fetching {i}.");
2473            let mut messages_stream = imap_session
2474                .fetch(format!("{i}"), "(RFC822.SIZE BODY.PEEK[] FLAGS)")
2475                .await?;
2476            let fetch = messages_stream
2477                .try_next()
2478                .await?
2479                .expect("no FETCH returned");
2480            let body = fetch.body().expect("message did not have a body!");
2481            assert_eq!(body.len(), 76 + 74 * i);
2482
2483            let no_fetch = messages_stream.try_next().await?;
2484            assert!(no_fetch.is_none());
2485            drop(messages_stream);
2486        }
2487
2488        Ok(())
2489    }
2490
2491    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2492    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2493    async fn status() {
2494        {
2495            let response = b"* STATUS INBOX (UIDNEXT 25)\r\n\
2496                A0001 OK [CLIENTBUG] Status on selected mailbox completed (0.001 + 0.000 secs).\r\n"
2497                .to_vec();
2498
2499            let mock_stream = MockStream::new(response);
2500            let mut session = mock_session!(mock_stream);
2501            let status = session.status("INBOX", "(UIDNEXT)").await.unwrap();
2502            assert_eq!(
2503                session.stream.inner.written_buf,
2504                b"A0001 STATUS \"INBOX\" (UIDNEXT)\r\n".to_vec()
2505            );
2506            assert_eq!(status.uid_next, Some(25));
2507        }
2508
2509        {
2510            let response = b"* STATUS INBOX (RECENT 15)\r\n\
2511                A0001 OK STATUS completed\r\n"
2512                .to_vec();
2513
2514            let mock_stream = MockStream::new(response);
2515            let mut session = mock_session!(mock_stream);
2516            let status = session.status("INBOX", "(RECENT)").await.unwrap();
2517            assert_eq!(
2518                session.stream.inner.written_buf,
2519                b"A0001 STATUS \"INBOX\" (RECENT)\r\n".to_vec()
2520            );
2521            assert_eq!(status.recent, 15);
2522        }
2523
2524        {
2525            // Example from RFC 3501.
2526            let response = b"* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)\r\n\
2527                A0001 OK STATUS completed\r\n"
2528                .to_vec();
2529
2530            let mock_stream = MockStream::new(response);
2531            let mut session = mock_session!(mock_stream);
2532            let status = session
2533                .status("blurdybloop", "(UIDNEXT MESSAGES)")
2534                .await
2535                .unwrap();
2536            assert_eq!(
2537                session.stream.inner.written_buf,
2538                b"A0001 STATUS \"blurdybloop\" (UIDNEXT MESSAGES)\r\n".to_vec()
2539            );
2540            assert_eq!(status.uid_next, Some(44292));
2541            assert_eq!(status.exists, 231);
2542        }
2543    }
2544
2545    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2546    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2547    async fn append() {
2548        {
2549            // APPEND command when INBOX is *not* selected.
2550            //
2551            // Only APPENDUID response is returned.
2552            let response = b"+ OK\r\nA0001 OK [APPENDUID 1725735035 2] Append completed (0.052 + 12.097 + 0.049 secs).\r\n".to_vec();
2553
2554            let mock_stream = MockStream::new(response);
2555            let mut session = mock_session!(mock_stream);
2556            session
2557                .append("INBOX", Some(r"(\Seen)"), None, "foobarbaz")
2558                .await
2559                .unwrap();
2560            assert_eq!(
2561                session.stream.inner.written_buf,
2562                b"A0001 APPEND \"INBOX\" (\\Seen) {9}\r\nfoobarbaz\r\n".to_vec()
2563            );
2564        }
2565
2566        {
2567            // APPEND command when INBOX is selected.
2568            //
2569            // EXISTS response is returned before APPENDUID response is returned.
2570            let response = b"+ OK\r\n* 3 EXISTS\r\n* 2 RECENT\r\nA0001 OK [APPENDUID 1725735035 2] Append completed (0.052 + 12.097 + 0.049 secs).\r\n".to_vec();
2571
2572            let mock_stream = MockStream::new(response);
2573            let mut session = mock_session!(mock_stream);
2574            session
2575                .append("INBOX", Some(r"(\Seen)"), None, "foobarbaz")
2576                .await
2577                .unwrap();
2578            assert_eq!(
2579                session.stream.inner.written_buf,
2580                b"A0001 APPEND \"INBOX\" (\\Seen) {9}\r\nfoobarbaz\r\n".to_vec()
2581            );
2582            let exists_response = session.unsolicited_responses.recv().await.unwrap();
2583            assert_eq!(exists_response, UnsolicitedResponse::Exists(3));
2584            let recent_response = session.unsolicited_responses.recv().await.unwrap();
2585            assert_eq!(recent_response, UnsolicitedResponse::Recent(2));
2586        }
2587
2588        {
2589            // APPEND to nonexisting folder fails.
2590            let response =
2591                b"A0001 NO [TRYCREATE] Mailbox doesn't exist: foobar (0.001 + 0.000 secs)."
2592                    .to_vec();
2593            let mock_stream = MockStream::new(response);
2594            let mut session = mock_session!(mock_stream);
2595            session
2596                .append("foobar", None, None, "foobarbaz")
2597                .await
2598                .unwrap_err();
2599            assert_eq!(
2600                session.stream.inner.written_buf,
2601                b"A0001 APPEND \"foobar\" {9}\r\n".to_vec()
2602            );
2603        }
2604    }
2605
2606    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2607    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2608    async fn get_metadata() {
2609        {
2610            let response = b"* METADATA \"INBOX\" (/private/comment \"My own comment\")\r\n\
2611                A0001 OK GETMETADATA complete\r\n"
2612                .to_vec();
2613
2614            let mock_stream = MockStream::new(response);
2615            let mut session = mock_session!(mock_stream);
2616            let metadata = session
2617                .get_metadata("INBOX", "", "/private/comment")
2618                .await
2619                .unwrap();
2620            assert_eq!(
2621                session.stream.inner.written_buf,
2622                b"A0001 GETMETADATA \"INBOX\" /private/comment\r\n".to_vec()
2623            );
2624            assert_eq!(metadata.len(), 1);
2625            assert_eq!(metadata[0].entry, "/private/comment");
2626            assert_eq!(metadata[0].value.as_ref().unwrap(), "My own comment");
2627        }
2628
2629        {
2630            let response = b"* METADATA \"INBOX\" (/shared/comment \"Shared comment\" /private/comment \"My own comment\")\r\n\
2631                A0001 OK GETMETADATA complete\r\n"
2632                .to_vec();
2633
2634            let mock_stream = MockStream::new(response);
2635            let mut session = mock_session!(mock_stream);
2636            let metadata = session
2637                .get_metadata("INBOX", "", "(/shared/comment /private/comment)")
2638                .await
2639                .unwrap();
2640            assert_eq!(
2641                session.stream.inner.written_buf,
2642                b"A0001 GETMETADATA \"INBOX\" (/shared/comment /private/comment)\r\n".to_vec()
2643            );
2644            assert_eq!(metadata.len(), 2);
2645            assert_eq!(metadata[0].entry, "/shared/comment");
2646            assert_eq!(metadata[0].value.as_ref().unwrap(), "Shared comment");
2647            assert_eq!(metadata[1].entry, "/private/comment");
2648            assert_eq!(metadata[1].value.as_ref().unwrap(), "My own comment");
2649        }
2650
2651        {
2652            let response = b"* METADATA \"\" (/shared/comment {15}\r\nChatmail server /shared/admin {28}\r\nmailto:root@nine.testrun.org)\r\n\
2653                A0001 OK OK Getmetadata completed (0.001 + 0.000 secs).\r\n"
2654                .to_vec();
2655
2656            let mock_stream = MockStream::new(response);
2657            let mut session = mock_session!(mock_stream);
2658            let metadata = session
2659                .get_metadata("", "", "(/shared/comment /shared/admin)")
2660                .await
2661                .unwrap();
2662            assert_eq!(
2663                session.stream.inner.written_buf,
2664                b"A0001 GETMETADATA \"\" (/shared/comment /shared/admin)\r\n".to_vec()
2665            );
2666            assert_eq!(metadata.len(), 2);
2667            assert_eq!(metadata[0].entry, "/shared/comment");
2668            assert_eq!(metadata[0].value.as_ref().unwrap(), "Chatmail server");
2669            assert_eq!(metadata[1].entry, "/shared/admin");
2670            assert_eq!(
2671                metadata[1].value.as_ref().unwrap(),
2672                "mailto:root@nine.testrun.org"
2673            );
2674        }
2675
2676        {
2677            let response = b"* METADATA \"\" (/shared/comment \"Chatmail server\")\r\n\
2678                * METADATA \"\" (/shared/admin \"mailto:root@nine.testrun.org\")\r\n\
2679                A0001 OK OK Getmetadata completed (0.001 + 0.000 secs).\r\n"
2680                .to_vec();
2681
2682            let mock_stream = MockStream::new(response);
2683            let mut session = mock_session!(mock_stream);
2684            let metadata = session
2685                .get_metadata("", "", "(/shared/comment /shared/admin)")
2686                .await
2687                .unwrap();
2688            assert_eq!(
2689                session.stream.inner.written_buf,
2690                b"A0001 GETMETADATA \"\" (/shared/comment /shared/admin)\r\n".to_vec()
2691            );
2692            assert_eq!(metadata.len(), 2);
2693            assert_eq!(metadata[0].entry, "/shared/comment");
2694            assert_eq!(metadata[0].value.as_ref().unwrap(), "Chatmail server");
2695            assert_eq!(metadata[1].entry, "/shared/admin");
2696            assert_eq!(
2697                metadata[1].value.as_ref().unwrap(),
2698                "mailto:root@nine.testrun.org"
2699            );
2700        }
2701
2702        {
2703            let response = b"* METADATA \"\" (/shared/comment NIL /shared/admin NIL)\r\n\
2704                A0001 OK OK Getmetadata completed (0.001 + 0.000 secs).\r\n"
2705                .to_vec();
2706
2707            let mock_stream = MockStream::new(response);
2708            let mut session = mock_session!(mock_stream);
2709            let metadata = session
2710                .get_metadata("", "", "(/shared/comment /shared/admin)")
2711                .await
2712                .unwrap();
2713            assert_eq!(
2714                session.stream.inner.written_buf,
2715                b"A0001 GETMETADATA \"\" (/shared/comment /shared/admin)\r\n".to_vec()
2716            );
2717            assert_eq!(metadata.len(), 2);
2718            assert_eq!(metadata[0].entry, "/shared/comment");
2719            assert_eq!(metadata[0].value, None);
2720            assert_eq!(metadata[1].entry, "/shared/admin");
2721            assert_eq!(metadata[1].value, None);
2722        }
2723    }
2724
2725    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2726    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2727    async fn test_get_quota_root() {
2728        {
2729            let response = b"* QUOTAROOT Sent Userquota\r\n\
2730                    * QUOTA Userquota (STORAGE 4855 48576)\r\n\
2731                    A0001 OK Getquotaroot completed (0.004 + 0.000 + 0.004 secs).\r\n"
2732                .to_vec();
2733
2734            let mock_stream = MockStream::new(response);
2735            let mut session = mock_session!(mock_stream);
2736            let (quotaroots, quota) = dbg!(session.get_quota_root("Sent").await.unwrap());
2737            assert_eq!(
2738                str::from_utf8(&session.stream.inner.written_buf).unwrap(),
2739                "A0001 GETQUOTAROOT \"Sent\"\r\n"
2740            );
2741            assert_eq!(
2742                quotaroots,
2743                vec![QuotaRoot {
2744                    mailbox_name: "Sent".to_string(),
2745                    quota_root_names: vec!["Userquota".to_string(),],
2746                },],
2747            );
2748            assert_eq!(
2749                quota,
2750                vec![Quota {
2751                    root_name: "Userquota".to_string(),
2752                    resources: vec![QuotaResource {
2753                        name: QuotaResourceName::Storage,
2754                        usage: 4855,
2755                        limit: 48576,
2756                    }],
2757                }]
2758            );
2759            assert_eq!(quota[0].resources[0].get_usage_percentage(), 9);
2760        }
2761
2762        {
2763            let response = b"* QUOTAROOT \"INBOX\" \"#19\"\r\n\
2764                    * QUOTA \"#19\" (STORAGE 0 0)\r\n\
2765                    A0001 OK GETQUOTAROOT successful.\r\n"
2766                .to_vec();
2767
2768            let mock_stream = MockStream::new(response);
2769            let mut session = mock_session!(mock_stream);
2770            let (quotaroots, quota) = session.get_quota_root("INBOX").await.unwrap();
2771            assert_eq!(
2772                str::from_utf8(&session.stream.inner.written_buf).unwrap(),
2773                "A0001 GETQUOTAROOT \"INBOX\"\r\n"
2774            );
2775            assert_eq!(
2776                quotaroots,
2777                vec![QuotaRoot {
2778                    mailbox_name: "INBOX".to_string(),
2779                    quota_root_names: vec!["#19".to_string(),],
2780                },],
2781            );
2782            assert_eq!(
2783                quota,
2784                vec![Quota {
2785                    root_name: "#19".to_string(),
2786                    resources: vec![QuotaResource {
2787                        name: QuotaResourceName::Storage,
2788                        usage: 0,
2789                        limit: 0,
2790                    }],
2791                }]
2792            );
2793            assert_eq!(quota[0].resources[0].get_usage_percentage(), 0);
2794        }
2795    }
2796
2797    #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2798    #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2799    async fn test_parsing_error() {
2800        // Simulate someone connecting to SMTP server with IMAP client.
2801        let response = b"220 mail.example.org ESMTP Postcow\r\n".to_vec();
2802        let command = "A0001 NOOP\r\n";
2803        let mock_stream = MockStream::new(response);
2804        let mut session = mock_session!(mock_stream);
2805        assert!(session
2806            .noop()
2807            .await
2808            .unwrap_err()
2809            .to_string()
2810            .contains("220 mail.example.org ESMTP Postcow"));
2811        assert!(
2812            session.stream.inner.written_buf == command.as_bytes().to_vec(),
2813            "Invalid NOOP command"
2814        );
2815    }
2816}