Skip to main content

async_imap/
client.rs

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