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