imap_patch_for_async_imap_lite/
client.rs

1use base64;
2use bufstream::BufStream;
3#[cfg(feature = "tls")]
4use native_tls::{TlsConnector, TlsStream};
5use nom;
6use std::collections::HashSet;
7use std::io::{Read, Write};
8use std::net::{TcpStream, ToSocketAddrs};
9use std::ops::{Deref, DerefMut};
10use std::str;
11use std::sync::mpsc;
12
13use super::authenticator::Authenticator;
14use super::error::{Error, ParseError, Result, ValidateError};
15use super::extensions;
16use super::parse::*;
17use super::types::*;
18
19static TAG_PREFIX: &str = "a";
20const INITIAL_TAG: u32 = 0;
21const CR: u8 = 0x0d;
22const LF: u8 = 0x0a;
23
24macro_rules! quote {
25    ($x:expr) => {
26        format!("\"{}\"", $x.replace(r"\", r"\\").replace("\"", "\\\""))
27    };
28}
29
30/// PATCH_FOR_ASYNC_IMAP_LITE [pub]
31pub fn validate_str(value: &str) -> Result<String> {
32    let quoted = quote!(value);
33    if quoted.find('\n').is_some() {
34        return Err(Error::Validate(ValidateError('\n')));
35    }
36    if quoted.find('\r').is_some() {
37        return Err(Error::Validate(ValidateError('\r')));
38    }
39    Ok(quoted)
40}
41
42/// An authenticated IMAP session providing the usual IMAP commands. This type is what you get from
43/// a succesful login attempt.
44///
45/// Note that the server *is* allowed to unilaterally send things to the client for messages in
46/// a selected mailbox whose status has changed. See the note on [unilateral server responses
47/// in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). Any such messages are parsed out
48/// and sent on `Session::unsolicited_responses`.
49// Both `Client` and `Session` deref to [`Connection`](struct.Connection.html), the underlying
50// primitives type.
51#[derive(Debug)]
52pub struct Session<T: Read + Write> {
53    conn: Connection<T>,
54    unsolicited_responses_tx: mpsc::Sender<UnsolicitedResponse>,
55
56    /// Server responses that are not related to the current command. See also the note on
57    /// [unilateral server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7).
58    pub unsolicited_responses: mpsc::Receiver<UnsolicitedResponse>,
59}
60
61/// An (unauthenticated) handle to talk to an IMAP server. This is what you get when first
62/// connecting. A succesfull call to [`Client::login`] or [`Client::authenticate`] will return a
63/// [`Session`] instance that provides the usual IMAP methods.
64// Both `Client` and `Session` deref to [`Connection`](struct.Connection.html), the underlying
65// primitives type.
66#[derive(Debug)]
67pub struct Client<T: Read + Write> {
68    conn: Connection<T>,
69}
70
71/// The underlying primitives type. Both `Client`(unauthenticated) and `Session`(after succesful
72/// login) use a `Connection` internally for the TCP stream primitives.
73#[derive(Debug)]
74#[doc(hidden)]
75pub struct Connection<T: Read + Write> {
76    pub(crate) stream: BufStream<T>,
77    tag: u32,
78
79    /// Enable debug mode for this connection so that all client-server interactions are printed to
80    /// `STDERR`.
81    pub debug: bool,
82
83    /// Tracks if we have read a greeting.
84    pub greeting_read: bool,
85}
86
87/// PATCH_FOR_ASYNC_IMAP_LITE [add]
88impl<T: Read + Write> Connection<T> {
89    /// PATCH_FOR_ASYNC_IMAP_LITE [add]
90    pub fn new(stream: T, debug: bool, greeting_read: bool) -> Self {
91        Self {
92            stream: BufStream::new(stream),
93            tag: INITIAL_TAG,
94            debug,
95            greeting_read,
96        }
97    }
98
99    /// PATCH_FOR_ASYNC_IMAP_LITE [add]
100    pub fn get_mut(&mut self) -> &mut BufStream<T> {
101        &mut self.stream
102    }
103}
104
105// `Deref` instances are so we can make use of the same underlying primitives in `Client` and
106// `Session`
107impl<T: Read + Write> Deref for Client<T> {
108    type Target = Connection<T>;
109
110    fn deref(&self) -> &Connection<T> {
111        &self.conn
112    }
113}
114
115impl<T: Read + Write> DerefMut for Client<T> {
116    fn deref_mut(&mut self) -> &mut Connection<T> {
117        &mut self.conn
118    }
119}
120
121impl<T: Read + Write> Deref for Session<T> {
122    type Target = Connection<T>;
123
124    fn deref(&self) -> &Connection<T> {
125        &self.conn
126    }
127}
128
129impl<T: Read + Write> DerefMut for Session<T> {
130    fn deref_mut(&mut self) -> &mut Connection<T> {
131        &mut self.conn
132    }
133}
134
135/// Connect to a server using a TLS-encrypted connection.
136///
137/// The returned [`Client`] is unauthenticated; to access session-related methods (through
138/// [`Session`]), use [`Client::login`] or [`Client::authenticate`].
139///
140/// The domain must be passed in separately from the `TlsConnector` so that the certificate of the
141/// IMAP server can be validated.
142///
143/// # Examples
144///
145/// ```no_run
146/// # extern crate native_tls;
147/// # extern crate imap;
148/// # use std::io;
149/// # use native_tls::TlsConnector;
150/// # fn main() {
151/// let tls = TlsConnector::builder().build().unwrap();
152/// let client = imap::connect(("imap.example.org", 993), "imap.example.org", &tls).unwrap();
153/// # }
154/// ```
155#[cfg(feature = "tls")]
156pub fn connect<A: ToSocketAddrs, S: AsRef<str>>(
157    addr: A,
158    domain: S,
159    ssl_connector: &TlsConnector,
160) -> Result<Client<TlsStream<TcpStream>>> {
161    match TcpStream::connect(addr) {
162        Ok(stream) => {
163            let ssl_stream = match TlsConnector::connect(ssl_connector, domain.as_ref(), stream) {
164                Ok(s) => s,
165                Err(e) => return Err(Error::TlsHandshake(e)),
166            };
167            let mut socket = Client::new(ssl_stream);
168
169            socket.read_greeting()?;
170            Ok(socket)
171        }
172        Err(e) => Err(Error::Io(e)),
173    }
174}
175
176/// Connect to a server and upgrade to a TLS-encrypted connection.
177///
178/// This is the [STARTTLS](https://tools.ietf.org/html/rfc2595) equivalent to [`connect`]. All
179/// notes there also apply here.
180///
181/// # Examples
182///
183/// ```no_run
184/// # extern crate native_tls;
185/// # extern crate imap;
186/// # use std::io;
187/// # use native_tls::TlsConnector;
188/// # fn main() {
189/// let tls = TlsConnector::builder().build().unwrap();
190/// let client = imap::connect_starttls(("imap.example.org", 143), "imap.example.org", &tls).unwrap();
191/// # }
192/// ```
193#[cfg(feature = "tls")]
194pub fn connect_starttls<A: ToSocketAddrs, S: AsRef<str>>(
195    addr: A,
196    domain: S,
197    ssl_connector: &TlsConnector,
198) -> Result<Client<TlsStream<TcpStream>>> {
199    match TcpStream::connect(addr) {
200        Ok(stream) => {
201            let mut socket = Client::new(stream);
202            socket.read_greeting()?;
203            socket.run_command_and_check_ok("STARTTLS")?;
204            TlsConnector::connect(
205                ssl_connector,
206                domain.as_ref(),
207                socket.conn.stream.into_inner()?,
208            )
209            .map(Client::new)
210            .map_err(Error::TlsHandshake)
211        }
212        Err(e) => Err(Error::Io(e)),
213    }
214}
215
216impl Client<TcpStream> {
217    /// This will upgrade an IMAP client from using a regular TCP connection to use TLS.
218    ///
219    /// The domain parameter is required to perform hostname verification.
220    #[cfg(feature = "tls")]
221    pub fn secure<S: AsRef<str>>(
222        mut self,
223        domain: S,
224        ssl_connector: &TlsConnector,
225    ) -> Result<Client<TlsStream<TcpStream>>> {
226        // TODO This needs to be tested
227        self.run_command_and_check_ok("STARTTLS")?;
228        TlsConnector::connect(
229            ssl_connector,
230            domain.as_ref(),
231            self.conn.stream.into_inner()?,
232        )
233        .map(Client::new)
234        .map_err(Error::TlsHandshake)
235    }
236}
237
238// As the pattern of returning the unauthenticated `Client` (a.k.a. `self`) back with a login error
239// is relatively common, it's abstacted away into a macro here.
240//
241// Note: 1) using `.map_err(|e| (e, self))` or similar here makes the closure own self, so we can't
242//          do that.
243//       2) in theory we wouldn't need the second parameter, and could just use the identifier
244//          `self` from the surrounding function, but being explicit here seems a lot cleaner.
245macro_rules! ok_or_unauth_client_err {
246    ($r:expr, $self:expr) => {
247        match $r {
248            Ok(o) => o,
249            Err(e) => return Err((e, $self)),
250        }
251    };
252}
253
254impl<T: Read + Write> Client<T> {
255    /// Creates a new client over the given stream.
256    ///
257    /// For an example of how to use this method to provide a pure-Rust TLS integration, see the
258    /// rustls.rs in the examples/ directory.
259    ///
260    /// This method primarily exists for writing tests that mock the underlying transport, but can
261    /// also be used to support IMAP over custom tunnels.
262    ///
263    /// **Note:** In case you do need to use `Client::new` over `imap::connect`, you will need to
264    /// listen for the IMAP protocol server greeting before authenticating:
265    ///
266    /// ```rust,no_run
267    /// # extern crate imap;
268    /// # extern crate native_tls;
269    /// # use imap::Client;
270    /// # use native_tls::TlsConnector;
271    /// # use std::io;
272    /// # use std::net::TcpStream;
273    /// # fn main() {
274    /// # let server = "imap.example.com";
275    /// # let username = "";
276    /// # let password = "";
277    /// # let tcp = TcpStream::connect((server, 993)).unwrap();
278    /// # let ssl_connector = TlsConnector::builder().build().unwrap();
279    /// # let tls = TlsConnector::connect(&ssl_connector, server.as_ref(), tcp).unwrap();
280    /// let mut client = Client::new(tls);
281    /// client.read_greeting().unwrap();
282    /// let session = client.login(username, password).unwrap();
283    /// # }
284    /// ```
285    pub fn new(stream: T) -> Client<T> {
286        Client {
287            conn: Connection {
288                stream: BufStream::new(stream),
289                tag: INITIAL_TAG,
290                debug: false,
291                greeting_read: false,
292            },
293        }
294    }
295
296    /// Log in to the IMAP server. Upon success a [`Session`](struct.Session.html) instance is
297    /// returned; on error the original `Client` instance is returned in addition to the error.
298    /// This is because `login` takes ownership of `self`, so in order to try again (e.g. after
299    /// prompting the user for credetials), ownership of the original `Client` needs to be
300    /// transferred back to the caller.
301    ///
302    /// ```rust,no_run
303    /// # extern crate imap;
304    /// # extern crate native_tls;
305    /// # use std::io;
306    /// # use native_tls::TlsConnector;
307    /// # fn main() {
308    /// # let tls_connector = TlsConnector::builder().build().unwrap();
309    /// let client = imap::connect(
310    ///     ("imap.example.org", 993),
311    ///     "imap.example.org",
312    ///     &tls_connector).unwrap();
313    ///
314    /// match client.login("user", "pass") {
315    ///     Ok(s) => {
316    ///         // you are successfully authenticated!
317    ///     },
318    ///     Err((e, orig_client)) => {
319    ///         eprintln!("error logging in: {}", e);
320    ///         // prompt user and try again with orig_client here
321    ///         return;
322    ///     }
323    /// }
324    /// # }
325    /// ```
326    pub fn login<U: AsRef<str>, P: AsRef<str>>(
327        mut self,
328        username: U,
329        password: P,
330    ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
331        let u = ok_or_unauth_client_err!(validate_str(username.as_ref()), self);
332        let p = ok_or_unauth_client_err!(validate_str(password.as_ref()), self);
333        ok_or_unauth_client_err!(
334            self.run_command_and_check_ok(&format!("LOGIN {} {}", u, p)),
335            self
336        );
337
338        Ok(Session::new(self.conn))
339    }
340
341    /// Authenticate with the server using the given custom `authenticator` to handle the server's
342    /// challenge.
343    ///
344    /// ```no_run
345    /// extern crate imap;
346    /// extern crate native_tls;
347    /// use native_tls::TlsConnector;
348    ///
349    /// struct OAuth2 {
350    ///     user: String,
351    ///     access_token: String,
352    /// }
353    ///
354    /// impl imap::Authenticator for OAuth2 {
355    ///     type Response = String;
356    ///     fn process(&self, _: &[u8]) -> Self::Response {
357    ///         format!(
358    ///             "user={}\x01auth=Bearer {}\x01\x01",
359    ///             self.user, self.access_token
360    ///         )
361    ///     }
362    /// }
363    ///
364    /// fn main() {
365    ///     let auth = OAuth2 {
366    ///         user: String::from("me@example.com"),
367    ///         access_token: String::from("<access_token>"),
368    ///     };
369    ///     let domain = "imap.example.com";
370    ///     let tls = TlsConnector::builder().build().unwrap();
371    ///     let client = imap::connect((domain, 993), domain, &tls).unwrap();
372    ///     match client.authenticate("XOAUTH2", &auth) {
373    ///         Ok(session) => {
374    ///             // you are successfully authenticated!
375    ///         },
376    ///         Err((e, orig_client)) => {
377    ///             eprintln!("error authenticating: {}", e);
378    ///             // prompt user and try again with orig_client here
379    ///             return;
380    ///         }
381    ///     };
382    /// }
383    /// ```
384    pub fn authenticate<A: Authenticator, S: AsRef<str>>(
385        mut self,
386        auth_type: S,
387        authenticator: &A,
388    ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
389        ok_or_unauth_client_err!(
390            self.run_command(&format!("AUTHENTICATE {}", auth_type.as_ref())),
391            self
392        );
393        self.do_auth_handshake(authenticator)
394    }
395
396    /// This func does the handshake process once the authenticate command is made.
397    fn do_auth_handshake<A: Authenticator>(
398        mut self,
399        authenticator: &A,
400    ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
401        // TODO Clean up this code
402        loop {
403            let mut line = Vec::new();
404
405            // explicit match blocks neccessary to convert error to tuple and not bind self too
406            // early (see also comment on `login`)
407            ok_or_unauth_client_err!(self.readline(&mut line), self);
408
409            // ignore server comments
410            if line.starts_with(b"* ") {
411                continue;
412            }
413
414            // Some servers will only send `+\r\n`.
415            if line.starts_with(b"+ ") || &line == b"+\r\n" {
416                let challenge = if &line == b"+\r\n" {
417                    Vec::new()
418                } else {
419                    let line_str = ok_or_unauth_client_err!(
420                        match str::from_utf8(line.as_slice()) {
421                            Ok(line_str) => Ok(line_str),
422                            Err(e) => Err(Error::Parse(ParseError::DataNotUtf8(line, e))),
423                        },
424                        self
425                    );
426                    let data =
427                        ok_or_unauth_client_err!(parse_authenticate_response(line_str), self);
428                    ok_or_unauth_client_err!(
429                        base64::decode(data).map_err(|e| Error::Parse(ParseError::Authentication(
430                            data.to_string(),
431                            Some(e)
432                        ))),
433                        self
434                    )
435                };
436
437                let raw_response = &authenticator.process(&challenge);
438                let auth_response = base64::encode(raw_response);
439                ok_or_unauth_client_err!(
440                    self.write_line(auth_response.into_bytes().as_slice()),
441                    self
442                );
443            } else {
444                ok_or_unauth_client_err!(self.read_response_onto(&mut line), self);
445                return Ok(Session::new(self.conn));
446            }
447        }
448    }
449}
450
451impl<T: Read + Write> Session<T> {
452    // not public, just to avoid duplicating the channel creation code
453    fn new(conn: Connection<T>) -> Self {
454        let (tx, rx) = mpsc::channel();
455        Session {
456            conn,
457            unsolicited_responses: rx,
458            unsolicited_responses_tx: tx,
459        }
460    }
461
462    /// Selects a mailbox
463    ///
464    /// The `SELECT` command selects a mailbox so that messages in the mailbox can be accessed.
465    /// Note that earlier versions of this protocol only required the FLAGS, EXISTS, and RECENT
466    /// untagged data; consequently, client implementations SHOULD implement default behavior for
467    /// missing data as discussed with the individual item.
468    ///
469    /// Only one mailbox can be selected at a time in a connection; simultaneous access to multiple
470    /// mailboxes requires multiple connections.  The `SELECT` command automatically deselects any
471    /// currently selected mailbox before attempting the new selection. Consequently, if a mailbox
472    /// is selected and a `SELECT` command that fails is attempted, no mailbox is selected.
473    ///
474    /// Note that the server *is* allowed to unilaterally send things to the client for messages in
475    /// a selected mailbox whose status has changed. See the note on [unilateral server responses
476    /// in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). This means that if you use
477    /// [`Connection::run_command_and_read_response`], you *may* see additional untagged `RECENT`,
478    /// `EXISTS`, `FETCH`, and `EXPUNGE` responses. You can get them from the
479    /// `unsolicited_responses` channel of the [`Session`](struct.Session.html).
480    pub fn select<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
481        // TODO: also note READ/WRITE vs READ-only mode!
482        self.run_command_and_read_response(&format!(
483            "SELECT {}",
484            validate_str(mailbox_name.as_ref())?
485        ))
486        .and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
487    }
488
489    /// The `EXAMINE` command is identical to [`Session::select`] and returns the same output;
490    /// however, the selected mailbox is identified as read-only. No changes to the permanent state
491    /// of the mailbox, including per-user state, will happen in a mailbox opened with `examine`;
492    /// in particular, messagess cannot lose [`Flag::Recent`] in an examined mailbox.
493    pub fn examine<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
494        self.run_command_and_read_response(&format!(
495            "EXAMINE {}",
496            validate_str(mailbox_name.as_ref())?
497        ))
498        .and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
499    }
500
501    /// Fetch retrieves data associated with a set of messages in the mailbox.
502    ///
503    /// Note that the server *is* allowed to unilaterally include `FETCH` responses for other
504    /// messages in the selected mailbox whose status has changed. See the note on [unilateral
505    /// server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7).
506    ///
507    /// `query` is a list of "data items" (space-separated in parentheses if `>1`). There are three
508    /// "macro items" which specify commonly-used sets of data items, and can be used instead of
509    /// data items.  A macro must be used by itself, and not in conjunction with other macros or
510    /// data items. They are:
511    ///
512    ///  - `ALL`: equivalent to: `(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)`
513    ///  - `FAST`: equivalent to: `(FLAGS INTERNALDATE RFC822.SIZE)`
514    ///
515    /// The currently defined data items that can be fetched are listen [in the
516    /// RFC](https://tools.ietf.org/html/rfc3501#section-6.4.5), but here are some common ones:
517    ///
518    ///  - `FLAGS`: The flags that are set for this message.
519    ///  - `INTERNALDATE`: The internal date of the message.
520    ///  - `BODY[<section>]`:
521    ///
522    ///     The text of a particular body section.  The section specification is a set of zero or
523    ///     more part specifiers delimited by periods.  A part specifier is either a part number
524    ///     (see RFC) or one of the following: `HEADER`, `HEADER.FIELDS`, `HEADER.FIELDS.NOT`,
525    ///     `MIME`, and `TEXT`.  An empty section specification (i.e., `BODY[]`) refers to the
526    ///     entire message, including the header.
527    ///
528    ///     The `HEADER`, `HEADER.FIELDS`, and `HEADER.FIELDS.NOT` part specifiers refer to the
529    ///     [RFC-2822](https://tools.ietf.org/html/rfc2822) header of the message or of an
530    ///     encapsulated [MIME-IMT](https://tools.ietf.org/html/rfc2046)
531    ///     MESSAGE/[RFC822](https://tools.ietf.org/html/rfc822) message. `HEADER.FIELDS` and
532    ///     `HEADER.FIELDS.NOT` are followed by a list of field-name (as defined in
533    ///     [RFC-2822](https://tools.ietf.org/html/rfc2822)) names, and return a subset of the
534    ///     header.  The subset returned by `HEADER.FIELDS` contains only those header fields with
535    ///     a field-name that matches one of the names in the list; similarly, the subset returned
536    ///     by `HEADER.FIELDS.NOT` contains only the header fields with a non-matching field-name.
537    ///     The field-matching is case-insensitive but otherwise exact.  Subsetting does not
538    ///     exclude the [RFC-2822](https://tools.ietf.org/html/rfc2822) delimiting blank line
539    ///     between the header and the body; the blank line is included in all header fetches,
540    ///     except in the case of a message which has no body and no blank line.
541    ///
542    ///     The `MIME` part specifier refers to the [MIME-IMB](https://tools.ietf.org/html/rfc2045)
543    ///     header for this part.
544    ///
545    ///     The `TEXT` part specifier refers to the text body of the message,
546    ///     omitting the [RFC-2822](https://tools.ietf.org/html/rfc2822) header.
547    ///
548    ///     [`Flag::Seen`] is implicitly set when `BODY` is fetched; if this causes the flags to
549    ///     change, they will generally be included as part of the `FETCH` responses.
550    ///  - `BODY.PEEK[<section>]`: An alternate form of `BODY[<section>]` that does not implicitly
551    ///    set [`Flag::Seen`].
552    ///  - `ENVELOPE`: The envelope structure of the message.  This is computed by the server by
553    ///    parsing the [RFC-2822](https://tools.ietf.org/html/rfc2822) header into the component
554    ///    parts, defaulting various fields as necessary.
555    ///  - `RFC822`: Functionally equivalent to `BODY[]`.
556    ///  - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`.
557    ///  - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message.
558    ///  - `UID`: The unique identifier for the message.
559    pub fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>>
560    where
561        S1: AsRef<str>,
562        S2: AsRef<str>,
563    {
564        self.run_command_and_read_response(&format!(
565            "FETCH {} {}",
566            sequence_set.as_ref(),
567            query.as_ref()
568        ))
569        .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
570    }
571
572    /// Equivalent to [`Session::fetch`], except that all identifiers in `uid_set` are
573    /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
574    pub fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>>
575    where
576        S1: AsRef<str>,
577        S2: AsRef<str>,
578    {
579        self.run_command_and_read_response(&format!(
580            "UID FETCH {} {}",
581            uid_set.as_ref(),
582            query.as_ref()
583        ))
584        .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
585    }
586
587    /// Noop always succeeds, and it does nothing.
588    pub fn noop(&mut self) -> Result<()> {
589        self.run_command_and_read_response("NOOP")
590            .and_then(|lines| parse_noop(lines, &mut self.unsolicited_responses_tx))
591    }
592
593    /// Logout informs the server that the client is done with the connection.
594    pub fn logout(&mut self) -> Result<()> {
595        self.run_command_and_check_ok("LOGOUT")
596    }
597
598    /// The [`CREATE` command](https://tools.ietf.org/html/rfc3501#section-6.3.3) creates a mailbox
599    /// with the given name.  `Ok` is returned only if a new mailbox with that name has been
600    /// created.  It is an error to attempt to create `INBOX` or a mailbox with a name that
601    /// refers to an extant mailbox.  Any error in creation will return [`Error::No`].
602    ///
603    /// If the mailbox name is suffixed with the server's hierarchy separator character (as
604    /// returned from the server by [`Session::list`]), this is a declaration that the client
605    /// intends to create mailbox names under this name in the hierarchy.  Servers that do not
606    /// require this declaration will ignore the declaration.  In any case, the name created is
607    /// without the trailing hierarchy delimiter.
608    ///
609    /// If the server's hierarchy separator character appears elsewhere in the name, the server
610    /// will generally create any superior hierarchical names that are needed for the `CREATE`
611    /// command to be successfully completed.  In other words, an attempt to create `foo/bar/zap`
612    /// on a server in which `/` is the hierarchy separator character will usually create `foo/`
613    /// and `foo/bar/` if they do not already exist.
614    ///
615    /// If a new mailbox is created with the same name as a mailbox which was deleted, its unique
616    /// identifiers will be greater than any unique identifiers used in the previous incarnation of
617    /// the mailbox UNLESS the new incarnation has a different unique identifier validity value.
618    /// See the description of the [`UID`
619    /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8) for more detail.
620    pub fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<()> {
621        self.run_command_and_check_ok(&format!("CREATE {}", validate_str(mailbox_name.as_ref())?))
622    }
623
624    /// The [`DELETE` command](https://tools.ietf.org/html/rfc3501#section-6.3.4) permanently
625    /// removes the mailbox with the given name.  `Ok` is returned only if the mailbox has been
626    /// deleted.  It is an error to attempt to delete `INBOX` or a mailbox name that does not
627    /// exist.
628    ///
629    /// The `DELETE` command will not remove inferior hierarchical names. For example, if a mailbox
630    /// `foo` has an inferior `foo.bar` (assuming `.` is the hierarchy delimiter character),
631    /// removing `foo` will not remove `foo.bar`.  It is an error to attempt to delete a name that
632    /// has inferior hierarchical names and also has [`NameAttribute::NoSelect`].
633    ///
634    /// It is permitted to delete a name that has inferior hierarchical names and does not have
635    /// [`NameAttribute::NoSelect`].  In this case, all messages in that mailbox are removed, and
636    /// the name will acquire [`NameAttribute::NoSelect`].
637    ///
638    /// The value of the highest-used unique identifier of the deleted mailbox will be preserved so
639    /// that a new mailbox created with the same name will not reuse the identifiers of the former
640    /// incarnation, UNLESS the new incarnation has a different unique identifier validity value.
641    /// See the description of the [`UID`
642    /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8) for more detail.
643    pub fn delete<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<()> {
644        self.run_command_and_check_ok(&format!("DELETE {}", validate_str(mailbox_name.as_ref())?))
645    }
646
647    /// The [`RENAME` command](https://tools.ietf.org/html/rfc3501#section-6.3.5) changes the name
648    /// of a mailbox.  `Ok` is returned only if the mailbox has been renamed.  It is an error to
649    /// attempt to rename from a mailbox name that does not exist or to a mailbox name that already
650    /// exists.  Any error in renaming will return [`Error::No`].
651    ///
652    /// If the name has inferior hierarchical names, then the inferior hierarchical names will also
653    /// be renamed.  For example, a rename of `foo` to `zap` will rename `foo/bar` (assuming `/` is
654    /// the hierarchy delimiter character) to `zap/bar`.
655    ///
656    /// If the server's hierarchy separator character appears in the name, the server will
657    /// generally create any superior hierarchical names that are needed for the `RENAME` command
658    /// to complete successfully.  In other words, an attempt to rename `foo/bar/zap` to
659    /// `baz/rag/zowie` on a server in which `/` is the hierarchy separator character will
660    /// generally create `baz/` and `baz/rag/` if they do not already exist.
661    ///
662    /// The value of the highest-used unique identifier of the old mailbox name will be preserved
663    /// so that a new mailbox created with the same name will not reuse the identifiers of the
664    /// former incarnation, UNLESS the new incarnation has a different unique identifier validity
665    /// value. See the description of the [`UID`
666    /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8) for more detail.
667    ///
668    /// Renaming `INBOX` is permitted, and has special behavior.  It moves all messages in `INBOX`
669    /// to a new mailbox with the given name, leaving `INBOX` empty.  If the server implementation
670    /// supports inferior hierarchical names of `INBOX`, these are unaffected by a rename of
671    /// `INBOX`.
672    pub fn rename<S1: AsRef<str>, S2: AsRef<str>>(&mut self, from: S1, to: S2) -> Result<()> {
673        self.run_command_and_check_ok(&format!(
674            "RENAME {} {}",
675            quote!(from.as_ref()),
676            quote!(to.as_ref())
677        ))
678    }
679
680    /// The [`SUBSCRIBE` command](https://tools.ietf.org/html/rfc3501#section-6.3.6) adds the
681    /// specified mailbox name to the server's set of "active" or "subscribed" mailboxes as
682    /// returned by [`Session::lsub`].  This command returns `Ok` only if the subscription is
683    /// successful.
684    ///
685    /// The server may validate the mailbox argument to `SUBSCRIBE` to verify that it exists.
686    /// However, it will not unilaterally remove an existing mailbox name from the subscription
687    /// list even if a mailbox by that name no longer exists.
688    pub fn subscribe<S: AsRef<str>>(&mut self, mailbox: S) -> Result<()> {
689        self.run_command_and_check_ok(&format!("SUBSCRIBE {}", quote!(mailbox.as_ref())))
690    }
691
692    /// The [`UNSUBSCRIBE` command](https://tools.ietf.org/html/rfc3501#section-6.3.7) removes the
693    /// specified mailbox name from the server's set of "active" or "subscribed" mailboxes as
694    /// returned by [`Session::lsub`].  This command returns `Ok` only if the unsubscription is
695    /// successful.
696    pub fn unsubscribe<S: AsRef<str>>(&mut self, mailbox: S) -> Result<()> {
697        self.run_command_and_check_ok(&format!("UNSUBSCRIBE {}", quote!(mailbox.as_ref())))
698    }
699
700    /// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
701    /// listing of capabilities that the server supports.  The server will include "IMAP4rev1" as
702    /// one of the listed capabilities. See [`Capabilities`] for further details.
703    pub fn capabilities(&mut self) -> ZeroCopyResult<Capabilities> {
704        self.run_command_and_read_response("CAPABILITY")
705            .and_then(|lines| parse_capabilities(lines, &mut self.unsolicited_responses_tx))
706    }
707
708    /// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently
709    /// removes all messages that have [`Flag::Deleted`] set from the currently selected mailbox.
710    /// The message sequence number of each message that is removed is returned.
711    pub fn expunge(&mut self) -> Result<Vec<Seq>> {
712        self.run_command_and_read_response("EXPUNGE")
713            .and_then(|lines| parse_expunge(lines, &mut self.unsolicited_responses_tx))
714    }
715
716    /// The [`UID EXPUNGE` command](https://tools.ietf.org/html/rfc4315#section-2.1) permanently
717    /// removes all messages that both have [`Flag::Deleted`] set and have a [`Uid`] that is
718    /// included in the specified sequence set from the currently selected mailbox.  If a message
719    /// either does not have [`Flag::Deleted`] set or has a [`Uid`] that is not included in the
720    /// specified sequence set, it is not affected.
721    ///
722    /// This command is particularly useful for disconnected use clients. By using [`uid_expunge`]
723    /// instead of [`expunge`] when resynchronizing with the server, the client can ensure that it
724    /// does not inadvertantly remove any messages that have been marked as [`Flag::Deleted`] by
725    /// other clients between the time that the client was last connected and the time the client
726    /// resynchronizes.
727    ///
728    /// This command requires that the server supports [RFC
729    /// 4315](https://tools.ietf.org/html/rfc4315) as indicated by the `UIDPLUS` capability (see
730    /// [`Session::capabilities`]). If the server does not support the `UIDPLUS` capability, the
731    /// client should fall back to using [`Session::store`] to temporarily remove [`Flag::Deleted`]
732    /// from messages it does not want to remove, then invoking [`Session::expunge`].  Finally, the
733    /// client should use [`Session::store`] to restore [`Flag::Deleted`] on the messages in which
734    /// it was temporarily removed.
735    ///
736    /// Alternatively, the client may fall back to using just [`Session::expunge`], risking the
737    /// unintended removal of some messages.
738    pub fn uid_expunge<S: AsRef<str>>(&mut self, uid_set: S) -> Result<Vec<Uid>> {
739        self.run_command_and_read_response(&format!("UID EXPUNGE {}", uid_set.as_ref()))
740            .and_then(|lines| parse_expunge(lines, &mut self.unsolicited_responses_tx))
741    }
742
743    /// The [`CHECK` command](https://tools.ietf.org/html/rfc3501#section-6.4.1) requests a
744    /// checkpoint of the currently selected mailbox.  A checkpoint refers to any
745    /// implementation-dependent housekeeping associated with the mailbox (e.g., resolving the
746    /// server's in-memory state of the mailbox with the state on its disk) that is not normally
747    /// executed as part of each command.  A checkpoint MAY take a non-instantaneous amount of real
748    /// time to complete.  If a server implementation has no such housekeeping considerations,
749    /// [`Session::check`] is equivalent to [`Session::noop`].
750    ///
751    /// There is no guarantee that an `EXISTS` untagged response will happen as a result of
752    /// `CHECK`.  [`Session::noop`] SHOULD be used for new message polling.
753    pub fn check(&mut self) -> Result<()> {
754        self.run_command_and_check_ok("CHECK")
755    }
756
757    /// The [`CLOSE` command](https://tools.ietf.org/html/rfc3501#section-6.4.2) permanently
758    /// removes all messages that have [`Flag::Deleted`] set from the currently selected mailbox,
759    /// and returns to the authenticated state from the selected state.  No `EXPUNGE` responses are
760    /// sent.
761    ///
762    /// No messages are removed, and no error is given, if the mailbox is selected by
763    /// [`Session::examine`] or is otherwise selected read-only.
764    ///
765    /// Even if a mailbox is selected, [`Session::select`], [`Session::examine`], or
766    /// [`Session::logout`] command MAY be issued without previously invoking [`Session::close`].
767    /// [`Session::select`], [`Session::examine`], and [`Session::logout`] implicitly close the
768    /// currently selected mailbox without doing an expunge.  However, when many messages are
769    /// deleted, a `CLOSE-LOGOUT` or `CLOSE-SELECT` sequence is considerably faster than an
770    /// `EXPUNGE-LOGOUT` or `EXPUNGE-SELECT` because no `EXPUNGE` responses (which the client would
771    /// probably ignore) are sent.
772    pub fn close(&mut self) -> Result<()> {
773        self.run_command_and_check_ok("CLOSE")
774    }
775
776    /// The [`STORE` command](https://tools.ietf.org/html/rfc3501#section-6.4.6) alters data
777    /// associated with a message in the mailbox.  Normally, `STORE` will return the updated value
778    /// of the data with an untagged FETCH response.  A suffix of `.SILENT` in `query` prevents the
779    /// untagged `FETCH`, and the server assumes that the client has determined the updated value
780    /// itself or does not care about the updated value.
781    ///
782    /// The currently defined data items that can be stored are:
783    ///
784    ///  - `FLAGS <flag list>`:
785    ///
786    ///    Replace the flags for the message (other than [`Flag::Recent`]) with the argument.  The
787    ///    new value of the flags is returned as if a `FETCH` of those flags was done.
788    ///
789    ///  - `FLAGS.SILENT <flag list>`: Equivalent to `FLAGS`, but without returning a new value.
790    ///
791    ///  - `+FLAGS <flag list>`
792    ///
793    ///    Add the argument to the flags for the message.  The new value of the flags is returned
794    ///    as if a `FETCH` of those flags was done.
795    ///  - `+FLAGS.SILENT <flag list>`: Equivalent to `+FLAGS`, but without returning a new value.
796    ///
797    ///  - `-FLAGS <flag list>`
798    ///
799    ///    Remove the argument from the flags for the message.  The new value of the flags is
800    ///    returned as if a `FETCH` of those flags was done.
801    ///
802    ///  - `-FLAGS.SILENT <flag list>`: Equivalent to `-FLAGS`, but without returning a new value.
803    ///
804    /// In all cases, `<flag list>` is a space-separated list enclosed in parentheses.
805    ///
806    /// # Examples
807    ///
808    /// Delete a message:
809    ///
810    /// ```rust,no_run
811    /// # extern crate imap;
812    /// # use imap::{self, Session};
813    /// # use std::net::TcpStream;
814    /// fn delete(seq: imap::types::Seq, s: &mut Session<TcpStream>) -> imap::error::Result<()> {
815    ///     s.store(format!("{}", seq), "+FLAGS (\\Deleted)")?;
816    ///     s.expunge()?;
817    ///     Ok(())
818    /// }
819    /// ```
820    pub fn store<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>>
821    where
822        S1: AsRef<str>,
823        S2: AsRef<str>,
824    {
825        self.run_command_and_read_response(&format!(
826            "STORE {} {}",
827            sequence_set.as_ref(),
828            query.as_ref()
829        ))
830        .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
831    }
832
833    /// Equivalent to [`Session::store`], except that all identifiers in `sequence_set` are
834    /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
835    pub fn uid_store<S1, S2>(&mut self, uid_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>>
836    where
837        S1: AsRef<str>,
838        S2: AsRef<str>,
839    {
840        self.run_command_and_read_response(&format!(
841            "UID STORE {} {}",
842            uid_set.as_ref(),
843            query.as_ref()
844        ))
845        .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
846    }
847
848    /// The [`COPY` command](https://tools.ietf.org/html/rfc3501#section-6.4.7) copies the
849    /// specified message(s) to the end of the specified destination mailbox.  The flags and
850    /// internal date of the message(s) will generally be preserved, and [`Flag::Recent`] will
851    /// generally be set, in the copy.
852    ///
853    /// If the `COPY` command is unsuccessful for any reason, the server restores the destination
854    /// mailbox to its state before the `COPY` attempt.
855    pub fn copy<S1: AsRef<str>, S2: AsRef<str>>(
856        &mut self,
857        sequence_set: S1,
858        mailbox_name: S2,
859    ) -> Result<()> {
860        self.run_command_and_check_ok(&format!(
861            "COPY {} {}",
862            sequence_set.as_ref(),
863            mailbox_name.as_ref()
864        ))
865    }
866
867    /// Equivalent to [`Session::copy`], except that all identifiers in `sequence_set` are
868    /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
869    pub fn uid_copy<S1: AsRef<str>, S2: AsRef<str>>(
870        &mut self,
871        uid_set: S1,
872        mailbox_name: S2,
873    ) -> Result<()> {
874        self.run_command_and_check_ok(&format!(
875            "UID COPY {} {}",
876            uid_set.as_ref(),
877            mailbox_name.as_ref()
878        ))
879    }
880
881    /// The [`MOVE` command](https://tools.ietf.org/html/rfc6851#section-3.1) takes two
882    /// arguments: a sequence set and a named mailbox. Each message included in the set is moved,
883    /// rather than copied, from the selected (source) mailbox to the named (target) mailbox.
884    ///
885    /// This means that a new message is created in the target mailbox with a
886    /// new [`Uid`], the original message is removed from the source mailbox, and
887    /// it appears to the client as a single action.  This has the same
888    /// effect for each message as this sequence:
889    ///
890    ///   1. COPY
891    ///   2. STORE +FLAGS.SILENT \DELETED
892    ///   3. EXPUNGE
893    ///
894    /// This command requires that the server supports [RFC
895    /// 6851](https://tools.ietf.org/html/rfc6851) as indicated by the `MOVE` capability (see
896    /// [`Session::capabilities`]).
897    ///
898    /// Although the effect of the `MOVE` is the same as the preceding steps, the semantics are not
899    /// identical: The intermediate states produced by those steps do not occur, and the response
900    /// codes are different.  In particular, though the `COPY` and `EXPUNGE` response codes will be
901    /// returned, response codes for a `store` will not be generated and [`Flag::Deleted`] will not
902    /// be set for any message.
903    ///
904    /// Because a `MOVE` applies to a set of messages, it might fail partway through the set.
905    /// Regardless of whether the command is successful in moving the entire set, each individual
906    /// message will either be moved or unaffected.  The server will leave each message in a state
907    /// where it is in at least one of the source or target mailboxes (no message can be lost or
908    /// orphaned).  The server will generally not leave any message in both mailboxes (it would be
909    /// bad for a partial failure to result in a bunch of duplicate messages).  This is true even
910    /// if the server returns with [`Error::No`].
911    pub fn mv<S1: AsRef<str>, S2: AsRef<str>>(
912        &mut self,
913        sequence_set: S1,
914        mailbox_name: S2,
915    ) -> Result<()> {
916        self.run_command_and_check_ok(&format!(
917            "MOVE {} {}",
918            sequence_set.as_ref(),
919            validate_str(mailbox_name.as_ref())?
920        ))
921    }
922
923    /// Equivalent to [`Session::copy`], except that all identifiers in `sequence_set` are
924    /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8)
925    /// and the [semantics of `MOVE` and `UID
926    /// MOVE`](https://tools.ietf.org/html/rfc6851#section-3.3).
927    pub fn uid_mv<S1: AsRef<str>, S2: AsRef<str>>(
928        &mut self,
929        uid_set: S1,
930        mailbox_name: S2,
931    ) -> Result<()> {
932        self.run_command_and_check_ok(&format!(
933            "UID MOVE {} {}",
934            uid_set.as_ref(),
935            validate_str(mailbox_name.as_ref())?
936        ))
937    }
938
939    /// The [`LIST` command](https://tools.ietf.org/html/rfc3501#section-6.3.8) returns a subset of
940    /// names from the complete set of all names available to the client.  It returns the name
941    /// attributes, hierarchy delimiter, and name of each such name; see [`Name`] for more detail.
942    ///
943    /// If `reference_name` is `None` (or `""`), the currently selected mailbox is used.
944    /// The returned mailbox names must match the supplied `mailbox_pattern`.  A non-empty
945    /// reference name argument is the name of a mailbox or a level of mailbox hierarchy, and
946    /// indicates the context in which the mailbox name is interpreted.
947    ///
948    /// If `mailbox_pattern` is `None` (or `""`), it is a special request to return the hierarchy
949    /// delimiter and the root name of the name given in the reference.  The value returned as the
950    /// root MAY be the empty string if the reference is non-rooted or is an empty string.  In all
951    /// cases, a hierarchy delimiter (or `NIL` if there is no hierarchy) is returned.  This permits
952    /// a client to get the hierarchy delimiter (or find out that the mailbox names are flat) even
953    /// when no mailboxes by that name currently exist.
954    ///
955    /// The reference and mailbox name arguments are interpreted into a canonical form that
956    /// represents an unambiguous left-to-right hierarchy.  The returned mailbox names will be in
957    /// the interpreted form.
958    ///
959    /// The character `*` is a wildcard, and matches zero or more characters at this position.  The
960    /// character `%` is similar to `*`, but it does not match a hierarchy delimiter.  If the `%`
961    /// wildcard is the last character of a mailbox name argument, matching levels of hierarchy are
962    /// also returned.  If these levels of hierarchy are not also selectable mailboxes, they are
963    /// returned with [`NameAttribute::NoSelect`].
964    ///
965    /// The special name `INBOX` is included if `INBOX` is supported by this server for this user
966    /// and if the uppercase string `INBOX` matches the interpreted reference and mailbox name
967    /// arguments with wildcards.  The criteria for omitting `INBOX` is whether `SELECT INBOX` will
968    /// return failure; it is not relevant whether the user's real `INBOX` resides on this or some
969    /// other server.
970    pub fn list(
971        &mut self,
972        reference_name: Option<&str>,
973        mailbox_pattern: Option<&str>,
974    ) -> ZeroCopyResult<Vec<Name>> {
975        self.run_command_and_read_response(&format!(
976            "LIST {} {}",
977            quote!(reference_name.unwrap_or("")),
978            mailbox_pattern.unwrap_or("\"\"")
979        ))
980        .and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx))
981    }
982
983    /// The [`LSUB` command](https://tools.ietf.org/html/rfc3501#section-6.3.9) returns a subset of
984    /// names from the set of names that the user has declared as being "active" or "subscribed".
985    /// The arguments to this method the same as for [`Session::list`].
986    ///
987    /// The returned [`Name`]s MAY contain different mailbox flags from response to
988    /// [`Session::list`].  If this should happen, the flags returned by [`Session::list`] are
989    /// considered more authoritative.
990    ///
991    /// A special situation occurs when invoking `lsub` with the `%` wildcard. Consider what
992    /// happens if `foo/bar` (with a hierarchy delimiter of `/`) is subscribed but `foo` is not.  A
993    /// `%` wildcard to `lsub` must return `foo`, not `foo/bar`, and it will be flagged with
994    /// [`NameAttribute::NoSelect`].
995    ///
996    /// The server will not unilaterally remove an existing mailbox name from the subscription list
997    /// even if a mailbox by that name no longer exists.
998    pub fn lsub(
999        &mut self,
1000        reference_name: Option<&str>,
1001        mailbox_pattern: Option<&str>,
1002    ) -> ZeroCopyResult<Vec<Name>> {
1003        self.run_command_and_read_response(&format!(
1004            "LSUB {} {}",
1005            quote!(reference_name.unwrap_or("")),
1006            mailbox_pattern.unwrap_or("")
1007        ))
1008        .and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx))
1009    }
1010
1011    /// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the
1012    /// status of the indicated mailbox. It does not change the currently selected mailbox, nor
1013    /// does it affect the state of any messages in the queried mailbox (in particular, `status`
1014    /// will not cause messages to lose [`Flag::Recent`]).
1015    ///
1016    /// `status` provides an alternative to opening a second [`Session`] and using
1017    /// [`Session::examine`] on a mailbox to query that mailbox's status without deselecting the
1018    /// current mailbox in the first `Session`.
1019    ///
1020    /// Unlike [`Session::list`], `status` is not guaranteed to be fast in its response.  Under
1021    /// certain circumstances, it can be quite slow.  In some implementations, the server is
1022    /// obliged to open the mailbox read-only internally to obtain certain status information.
1023    /// Also unlike [`Session::list`], `status` does not accept wildcards.
1024    ///
1025    /// > Note: `status` is intended to access the status of mailboxes other than the currently
1026    /// > selected mailbox.  Because `status` can cause the mailbox to be opened internally, and
1027    /// > because this information is available by other means on the selected mailbox, `status`
1028    /// > SHOULD NOT be used on the currently selected mailbox.
1029    ///
1030    /// The STATUS command MUST NOT be used as a "check for new messages in the selected mailbox"
1031    /// operation (refer to sections [7](https://tools.ietf.org/html/rfc3501#section-7),
1032    /// [7.3.1](https://tools.ietf.org/html/rfc3501#section-7.3.1), and
1033    /// [7.3.2](https://tools.ietf.org/html/rfc3501#section-7.3.2) for more information about the
1034    /// proper method for new message checking).
1035    ///
1036    /// The currently defined status data items that can be requested are:
1037    ///
1038    ///  - `MESSAGES`: The number of messages in the mailbox.
1039    ///  - `RECENT`: The number of messages with [`Flag::Recent`] set.
1040    ///  - `UIDNEXT`: The next [`Uid`] of the mailbox.
1041    ///  - `UIDVALIDITY`: The unique identifier validity value of the mailbox (see [`Uid`]).
1042    ///  - `UNSEEN`: The number of messages which do not have [`Flag::Seen`] set.
1043    ///
1044    /// `data_items` is a space-separated list enclosed in parentheses.
1045    pub fn status<S1: AsRef<str>, S2: AsRef<str>>(
1046        &mut self,
1047        mailbox_name: S1,
1048        data_items: S2,
1049    ) -> Result<Mailbox> {
1050        self.run_command_and_read_response(&format!(
1051            "STATUS {} {}",
1052            validate_str(mailbox_name.as_ref())?,
1053            data_items.as_ref()
1054        ))
1055        .and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
1056    }
1057
1058    /// This method returns a handle that lets you use the [`IDLE`
1059    /// command](https://tools.ietf.org/html/rfc2177#section-3) to listen for changes to the
1060    /// currently selected mailbox.
1061    ///
1062    /// It's often more desirable to have the server transmit updates to the client in real time.
1063    /// This allows a user to see new mail immediately.  It also helps some real-time applications
1064    /// based on IMAP, which might otherwise need to poll extremely often (such as every few
1065    /// seconds).  While the spec actually does allow a server to push `EXISTS` responses
1066    /// aysynchronously, a client can't expect this behaviour and must poll.  This method provides
1067    /// you with such a mechanism.
1068    ///
1069    /// `idle` may be used with any server that returns `IDLE` as one of the supported capabilities
1070    /// (see [`Session::capabilities`]). If the server does not advertise the `IDLE` capability,
1071    /// the client MUST NOT use `idle` and must instead poll for mailbox updates.  In particular,
1072    /// the client MUST continue to be able to accept unsolicited untagged responses to ANY
1073    /// command, as specified in the base IMAP specification.
1074    ///
1075    /// See [`extensions::idle::Handle`] for details.
1076    pub fn idle(&mut self) -> Result<extensions::idle::Handle<'_, T>> {
1077        extensions::idle::Handle::make(self)
1078    }
1079
1080    /// The [`APPEND` command](https://tools.ietf.org/html/rfc3501#section-6.3.11) appends
1081    /// `content` as a new message to the end of the specified destination `mailbox`.  This
1082    /// argument SHOULD be in the format of an [RFC-2822](https://tools.ietf.org/html/rfc2822)
1083    /// message.
1084    ///
1085    /// > Note: There MAY be exceptions, e.g., draft messages, in which required RFC-2822 header
1086    /// > lines are omitted in the message literal argument to `append`.  The full implications of
1087    /// > doing so MUST be understood and carefully weighed.
1088    ///
1089    /// If the append is unsuccessful for any reason, the mailbox is restored to its state before
1090    /// the append attempt; no partial appending will happen.
1091    ///
1092    /// If the destination `mailbox` does not exist, the server returns an error, and does not
1093    /// automatically create the mailbox.
1094    ///
1095    /// If the mailbox is currently selected, the normal new message actions will generally occur.
1096    /// Specifically, the server will generally notify the client immediately via an untagged
1097    /// `EXISTS` response.  If the server does not do so, the client MAY issue a `NOOP` command (or
1098    /// failing that, a `CHECK` command) after one or more `APPEND` commands.
1099    pub fn append<S: AsRef<str>, B: AsRef<[u8]>>(&mut self, mailbox: S, content: B) -> Result<()> {
1100        let content = content.as_ref();
1101        self.run_command(&format!(
1102            "APPEND \"{}\" {{{}}}",
1103            mailbox.as_ref(),
1104            content.len()
1105        ))?;
1106        let mut v = Vec::new();
1107        self.readline(&mut v)?;
1108        if !v.starts_with(b"+") {
1109            return Err(Error::Append);
1110        }
1111        self.stream.write_all(content)?;
1112        self.stream.write_all(b"\r\n")?;
1113        self.stream.flush()?;
1114        self.read_response().map(|_| ())
1115    }
1116
1117    /// The [`SEARCH` command](https://tools.ietf.org/html/rfc3501#section-6.4.4) searches the
1118    /// mailbox for messages that match the given `query`.  `query` consist of one or more search
1119    /// keys separated by spaces.  The response from the server contains a listing of [`Seq`]s
1120    /// corresponding to those messages that match the searching criteria.
1121    ///
1122    /// When multiple search keys are specified, the result is the intersection of all the messages
1123    /// that match those keys.  Or, in other words, only messages that match *all* the keys. For
1124    /// example, the criteria
1125    ///
1126    /// ```text
1127    /// DELETED FROM "SMITH" SINCE 1-Feb-1994
1128    /// ```
1129    ///
1130    /// refers to all deleted messages from Smith that were placed in the mailbox since February 1,
1131    /// 1994.  A search key can also be a parenthesized list of one or more search keys (e.g., for
1132    /// use with the `OR` and `NOT` keys).
1133    ///
1134    /// In all search keys that use strings, a message matches the key if the string is a substring
1135    /// of the field.  The matching is case-insensitive.
1136    ///
1137    /// Below is a selection of common search keys.  The full list can be found in the
1138    /// specification of the [`SEARCH command`](https://tools.ietf.org/html/rfc3501#section-6.4.4).
1139    ///
1140    ///  - `NEW`: Messages that have [`Flag::Recent`] set but not [`Flag::Seen`]. This is functionally equivalent to `(RECENT UNSEEN)`.
1141    ///  - `OLD`: Messages that do not have [`Flag::Recent`] set.  This is functionally equivalent to `NOT RECENT` (as opposed to `NOT NEW`).
1142    ///  - `RECENT`: Messages that have [`Flag::Recent`] set.
1143    ///  - `ANSWERED`: Messages with [`Flag::Answered`] set.
1144    ///  - `DELETED`: Messages with [`Flag::Deleted`] set.
1145    ///  - `DRAFT`: Messages with [`Flag::Draft`] set.
1146    ///  - `FLAGGED`: Messages with [`Flag::Flagged`] set.
1147    ///  - `SEEN`: Messages that have [`Flag::Seen`] set.
1148    ///  - `<sequence set>`: Messages with message sequence numbers corresponding to the specified message sequence number set.
1149    ///  - `UID <sequence set>`: Messages with [`Uid`] corresponding to the specified unique identifier set.  Sequence set ranges are permitted.
1150    ///
1151    ///  - `SUBJECT <string>`: Messages that contain the specified string in the envelope structure's `SUBJECT` field.
1152    ///  - `BODY <string>`: Messages that contain the specified string in the body of the message.
1153    ///  - `FROM <string>`: Messages that contain the specified string in the envelope structure's `FROM` field.
1154    ///  - `TO <string>`: Messages that contain the specified string in the envelope structure's `TO` field.
1155    ///
1156    ///  - `NOT <search-key>`: Messages that do not match the specified search key.
1157    ///  - `OR <search-key1> <search-key2>`: Messages that match either search key.
1158    ///
1159    ///  - `BEFORE <date>`: Messages whose internal date (disregarding time and timezone) is earlier than the specified date.
1160    ///  - `SINCE <date>`: Messages whose internal date (disregarding time and timezone) is within or later than the specified date.
1161    pub fn search<S: AsRef<str>>(&mut self, query: S) -> Result<HashSet<Seq>> {
1162        self.run_command_and_read_response(&format!("SEARCH {}", query.as_ref()))
1163            .and_then(|lines| parse_ids(&lines, &mut self.unsolicited_responses_tx))
1164    }
1165
1166    /// Equivalent to [`Session::search`], except that the returned identifiers
1167    /// are [`Uid`] instead of [`Seq`]. See also the [`UID`
1168    /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
1169    pub fn uid_search<S: AsRef<str>>(&mut self, query: S) -> Result<HashSet<Uid>> {
1170        self.run_command_and_read_response(&format!("UID SEARCH {}", query.as_ref()))
1171            .and_then(|lines| parse_ids(&lines, &mut self.unsolicited_responses_tx))
1172    }
1173
1174    // these are only here because they are public interface, the rest is in `Connection`
1175    /// Runs a command and checks if it returns OK.
1176    pub fn run_command_and_check_ok<S: AsRef<str>>(&mut self, command: S) -> Result<()> {
1177        self.run_command_and_read_response(command).map(|_| ())
1178    }
1179
1180    /// Runs any command passed to it.
1181    pub fn run_command<S: AsRef<str>>(&mut self, untagged_command: S) -> Result<()> {
1182        self.conn.run_command(untagged_command.as_ref())
1183    }
1184
1185    /// Run a raw IMAP command and read back its response.
1186    ///
1187    /// Note that the server *is* allowed to unilaterally send things to the client for messages in
1188    /// a selected mailbox whose status has changed. See the note on [unilateral server responses
1189    /// in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). This means that you *may* see
1190    /// additional untagged `RECENT`, `EXISTS`, `FETCH`, and `EXPUNGE` responses!
1191    pub fn run_command_and_read_response<S: AsRef<str>>(
1192        &mut self,
1193        untagged_command: S,
1194    ) -> Result<Vec<u8>> {
1195        self.conn
1196            .run_command_and_read_response(untagged_command.as_ref())
1197    }
1198}
1199
1200impl<T: Read + Write> Connection<T> {
1201    /// Read the greeting from the connection. Needs to be done after `connect`ing.
1202    ///
1203    /// Panics if called more than once on the same `Connection`.
1204    pub fn read_greeting(&mut self) -> Result<Vec<u8>> {
1205        assert!(!self.greeting_read, "Greeting can only be read once");
1206
1207        let mut v = Vec::new();
1208        self.readline(&mut v)?;
1209        self.greeting_read = true;
1210
1211        Ok(v)
1212    }
1213
1214    fn run_command_and_check_ok(&mut self, command: &str) -> Result<()> {
1215        self.run_command_and_read_response(command).map(|_| ())
1216    }
1217
1218    /// PATCH_FOR_ASYNC_IMAP_LITE [pub]
1219    pub fn run_command(&mut self, untagged_command: &str) -> Result<()> {
1220        let command = self.create_command(untagged_command);
1221        self.write_line(command.into_bytes().as_slice())
1222    }
1223
1224    fn run_command_and_read_response(&mut self, untagged_command: &str) -> Result<Vec<u8>> {
1225        self.run_command(untagged_command)?;
1226        self.read_response()
1227    }
1228
1229    pub(crate) fn read_response(&mut self) -> Result<Vec<u8>> {
1230        let mut v = Vec::new();
1231        self.read_response_onto(&mut v)?;
1232        Ok(v)
1233    }
1234
1235    /// PATCH_FOR_ASYNC_IMAP_LITE [pub]
1236    pub fn read_response_onto(&mut self, data: &mut Vec<u8>) -> Result<()> {
1237        let mut continue_from = None;
1238        let mut try_first = !data.is_empty();
1239        let match_tag = format!("{}{}", TAG_PREFIX, self.tag);
1240        loop {
1241            let line_start = if try_first {
1242                try_first = false;
1243                0
1244            } else {
1245                let start_new = data.len();
1246                self.readline(data)?;
1247                continue_from.take().unwrap_or(start_new)
1248            };
1249
1250            let break_with = {
1251                use imap_proto::{parse_response, Response, Status};
1252                let line = &data[line_start..];
1253
1254                match parse_response(line) {
1255                    Ok((
1256                        _,
1257                        Response::Done {
1258                            tag,
1259                            status,
1260                            information,
1261                            ..
1262                        },
1263                    )) => {
1264                        assert_eq!(tag.as_bytes(), match_tag.as_bytes());
1265                        Some(match status {
1266                            Status::Bad | Status::No => {
1267                                Err((status, information.map(ToString::to_string)))
1268                            }
1269                            Status::Ok => Ok(()),
1270                            status => Err((status, None)),
1271                        })
1272                    }
1273                    Ok((..)) => None,
1274                    Err(nom::Err::Incomplete(..)) => {
1275                        continue_from = Some(line_start);
1276                        None
1277                    }
1278                    _ => Some(Err((Status::Bye, None))),
1279                }
1280            };
1281
1282            match break_with {
1283                Some(Ok(_)) => {
1284                    data.truncate(line_start);
1285                    break Ok(());
1286                }
1287                Some(Err((status, expl))) => {
1288                    use imap_proto::Status;
1289                    match status {
1290                        Status::Bad => {
1291                            break Err(Error::Bad(
1292                                expl.unwrap_or_else(|| "no explanation given".to_string()),
1293                            ));
1294                        }
1295                        Status::No => {
1296                            break Err(Error::No(
1297                                expl.unwrap_or_else(|| "no explanation given".to_string()),
1298                            ));
1299                        }
1300                        _ => break Err(Error::Parse(ParseError::Invalid(data.split_off(0)))),
1301                    }
1302                }
1303                None => {}
1304            }
1305        }
1306    }
1307
1308    /// PATCH_FOR_ASYNC_IMAP_LITE [pub]
1309    pub fn readline(&mut self, into: &mut Vec<u8>) -> Result<usize> {
1310        use std::io::BufRead;
1311        let read = self.stream.read_until(LF, into)?;
1312        if read == 0 {
1313            return Err(Error::ConnectionLost);
1314        }
1315
1316        if self.debug {
1317            // Remove CRLF
1318            let len = into.len();
1319            let line = &into[(len - read)..(len - 2)];
1320            eprint!("S: {}\n", String::from_utf8_lossy(line));
1321        }
1322
1323        Ok(read)
1324    }
1325
1326    fn create_command(&mut self, command: &str) -> String {
1327        self.tag += 1;
1328        format!("{}{} {}", TAG_PREFIX, self.tag, command)
1329    }
1330
1331    pub(crate) fn write_line(&mut self, buf: &[u8]) -> Result<()> {
1332        self.stream.write_all(buf)?;
1333        self.stream.write_all(&[CR, LF])?;
1334        self.stream.flush()?;
1335        if self.debug {
1336            eprint!("C: {}\n", String::from_utf8(buf.to_vec()).unwrap());
1337        }
1338        Ok(())
1339    }
1340}
1341
1342#[cfg(test)]
1343mod tests {
1344    use super::super::error::Result;
1345    use super::super::mock_stream::MockStream;
1346    use super::*;
1347
1348    macro_rules! mock_session {
1349        ($s:expr) => {
1350            Session::new(Client::new($s).conn)
1351        };
1352    }
1353
1354    #[test]
1355    fn read_response() {
1356        let response = "a0 OK Logged in.\r\n";
1357        let mock_stream = MockStream::new(response.as_bytes().to_vec());
1358        let mut client = Client::new(mock_stream);
1359        let actual_response = client.read_response().unwrap();
1360        assert_eq!(Vec::<u8>::new(), actual_response);
1361    }
1362
1363    #[test]
1364    fn fetch_body() {
1365        let response = "a0 OK Logged in.\r\n\
1366                        * 2 FETCH (BODY[TEXT] {3}\r\nfoo)\r\n\
1367                        a0 OK FETCH completed\r\n";
1368        let mock_stream = MockStream::new(response.as_bytes().to_vec());
1369        let mut session = mock_session!(mock_stream);
1370        session.read_response().unwrap();
1371        session.read_response().unwrap();
1372    }
1373
1374    #[test]
1375    fn read_greeting() {
1376        let greeting = "* OK Dovecot ready.\r\n";
1377        let mock_stream = MockStream::new(greeting.as_bytes().to_vec());
1378        let mut client = Client::new(mock_stream);
1379        client.read_greeting().unwrap();
1380    }
1381
1382    #[test]
1383    fn readline_delay_read() {
1384        let greeting = "* OK Dovecot ready.\r\n";
1385        let expected_response: String = greeting.to_string();
1386        let mock_stream = MockStream::default()
1387            .with_buf(greeting.as_bytes().to_vec())
1388            .with_delay();
1389        let mut client = Client::new(mock_stream);
1390        let mut v = Vec::new();
1391        client.readline(&mut v).unwrap();
1392        let actual_response = String::from_utf8(v).unwrap();
1393        assert_eq!(expected_response, actual_response);
1394    }
1395
1396    #[test]
1397    fn readline_eof() {
1398        let mock_stream = MockStream::default().with_eof();
1399        let mut client = Client::new(mock_stream);
1400        let mut v = Vec::new();
1401        if let Err(Error::ConnectionLost) = client.readline(&mut v) {
1402        } else {
1403            unreachable!("EOF read did not return connection lost");
1404        }
1405    }
1406
1407    #[test]
1408    #[should_panic]
1409    fn readline_err() {
1410        // TODO Check the error test
1411        let mock_stream = MockStream::default().with_err();
1412        let mut client = Client::new(mock_stream);
1413        let mut v = Vec::new();
1414        client.readline(&mut v).unwrap();
1415    }
1416
1417    #[test]
1418    fn create_command() {
1419        let base_command = "CHECK";
1420        let mock_stream = MockStream::default();
1421        let mut imap_stream = Client::new(mock_stream);
1422
1423        let expected_command = format!("a1 {}", base_command);
1424        let command = imap_stream.create_command(&base_command);
1425        assert!(
1426            command == expected_command,
1427            "expected command doesn't equal actual command"
1428        );
1429
1430        let expected_command2 = format!("a2 {}", base_command);
1431        let command2 = imap_stream.create_command(&base_command);
1432        assert!(
1433            command2 == expected_command2,
1434            "expected command doesn't equal actual command"
1435        );
1436    }
1437
1438    #[test]
1439    fn authenticate() {
1440        let response = b"+ YmFy\r\n\
1441                         a1 OK Logged in\r\n"
1442            .to_vec();
1443        let command = "a1 AUTHENTICATE PLAIN\r\n\
1444                       Zm9v\r\n";
1445        let mock_stream = MockStream::new(response);
1446        let client = Client::new(mock_stream);
1447        enum Authenticate {
1448            Auth,
1449        };
1450        impl Authenticator for Authenticate {
1451            type Response = Vec<u8>;
1452            fn process(&self, challenge: &[u8]) -> Self::Response {
1453                assert!(challenge == b"bar", "Invalid authenticate challenge");
1454                b"foo".to_vec()
1455            }
1456        }
1457        let session = client.authenticate("PLAIN", &Authenticate::Auth).unwrap();
1458        assert!(
1459            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1460            "Invalid authenticate command"
1461        );
1462    }
1463
1464    #[test]
1465    fn login() {
1466        let response = b"a1 OK Logged in\r\n".to_vec();
1467        let username = "username";
1468        let password = "password";
1469        let command = format!("a1 LOGIN {} {}\r\n", quote!(username), quote!(password));
1470        let mock_stream = MockStream::new(response);
1471        let client = Client::new(mock_stream);
1472        let session = client.login(username, password).unwrap();
1473        assert!(
1474            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1475            "Invalid login command"
1476        );
1477    }
1478
1479    #[test]
1480    fn logout() {
1481        let response = b"a1 OK Logout completed.\r\n".to_vec();
1482        let command = format!("a1 LOGOUT\r\n");
1483        let mock_stream = MockStream::new(response);
1484        let mut session = mock_session!(mock_stream);
1485        session.logout().unwrap();
1486        assert!(
1487            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1488            "Invalid logout command"
1489        );
1490    }
1491
1492    #[test]
1493    fn rename() {
1494        let response = b"a1 OK RENAME completed\r\n".to_vec();
1495        let current_mailbox_name = "INBOX";
1496        let new_mailbox_name = "NEWINBOX";
1497        let command = format!(
1498            "a1 RENAME {} {}\r\n",
1499            quote!(current_mailbox_name),
1500            quote!(new_mailbox_name)
1501        );
1502        let mock_stream = MockStream::new(response);
1503        let mut session = mock_session!(mock_stream);
1504        session
1505            .rename(current_mailbox_name, new_mailbox_name)
1506            .unwrap();
1507        assert!(
1508            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1509            "Invalid rename command"
1510        );
1511    }
1512
1513    #[test]
1514    fn subscribe() {
1515        let response = b"a1 OK SUBSCRIBE completed\r\n".to_vec();
1516        let mailbox = "INBOX";
1517        let command = format!("a1 SUBSCRIBE {}\r\n", quote!(mailbox));
1518        let mock_stream = MockStream::new(response);
1519        let mut session = mock_session!(mock_stream);
1520        session.subscribe(mailbox).unwrap();
1521        assert!(
1522            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1523            "Invalid subscribe command"
1524        );
1525    }
1526
1527    #[test]
1528    fn unsubscribe() {
1529        let response = b"a1 OK UNSUBSCRIBE completed\r\n".to_vec();
1530        let mailbox = "INBOX";
1531        let command = format!("a1 UNSUBSCRIBE {}\r\n", quote!(mailbox));
1532        let mock_stream = MockStream::new(response);
1533        let mut session = mock_session!(mock_stream);
1534        session.unsubscribe(mailbox).unwrap();
1535        assert!(
1536            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1537            "Invalid unsubscribe command"
1538        );
1539    }
1540
1541    #[test]
1542    fn expunge() {
1543        let response = b"a1 OK EXPUNGE completed\r\n".to_vec();
1544        let mock_stream = MockStream::new(response);
1545        let mut session = mock_session!(mock_stream);
1546        session.expunge().unwrap();
1547        assert!(
1548            session.stream.get_ref().written_buf == b"a1 EXPUNGE\r\n".to_vec(),
1549            "Invalid expunge command"
1550        );
1551    }
1552
1553    #[test]
1554    fn uid_expunge() {
1555        let response = b"* 2 EXPUNGE\r\n\
1556            * 3 EXPUNGE\r\n\
1557            * 4 EXPUNGE\r\n\
1558            a1 OK UID EXPUNGE completed\r\n"
1559            .to_vec();
1560        let mock_stream = MockStream::new(response);
1561        let mut session = mock_session!(mock_stream);
1562        session.uid_expunge("2:4").unwrap();
1563        assert!(
1564            session.stream.get_ref().written_buf == b"a1 UID EXPUNGE 2:4\r\n".to_vec(),
1565            "Invalid expunge command"
1566        );
1567    }
1568
1569    #[test]
1570    fn check() {
1571        let response = b"a1 OK CHECK completed\r\n".to_vec();
1572        let mock_stream = MockStream::new(response);
1573        let mut session = mock_session!(mock_stream);
1574        session.check().unwrap();
1575        assert!(
1576            session.stream.get_ref().written_buf == b"a1 CHECK\r\n".to_vec(),
1577            "Invalid check command"
1578        );
1579    }
1580
1581    #[test]
1582    fn examine() {
1583        let response = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n\
1584            * OK [PERMANENTFLAGS ()] Read-only mailbox.\r\n\
1585            * 1 EXISTS\r\n\
1586            * 1 RECENT\r\n\
1587            * OK [UNSEEN 1] First unseen.\r\n\
1588            * OK [UIDVALIDITY 1257842737] UIDs valid\r\n\
1589            * OK [UIDNEXT 2] Predicted next UID\r\n\
1590            a1 OK [READ-ONLY] Select completed.\r\n"
1591            .to_vec();
1592        let expected_mailbox = Mailbox {
1593            flags: vec![
1594                Flag::Answered,
1595                Flag::Flagged,
1596                Flag::Deleted,
1597                Flag::Seen,
1598                Flag::Draft,
1599            ],
1600            exists: 1,
1601            recent: 1,
1602            unseen: Some(1),
1603            permanent_flags: vec![],
1604            uid_next: Some(2),
1605            uid_validity: Some(1257842737),
1606        };
1607        let mailbox_name = "INBOX";
1608        let command = format!("a1 EXAMINE {}\r\n", quote!(mailbox_name));
1609        let mock_stream = MockStream::new(response);
1610        let mut session = mock_session!(mock_stream);
1611        let mailbox = session.examine(mailbox_name).unwrap();
1612        assert!(
1613            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1614            "Invalid examine command"
1615        );
1616        assert_eq!(mailbox, expected_mailbox);
1617    }
1618
1619    #[test]
1620    fn select() {
1621        let response = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n\
1622            * OK [PERMANENTFLAGS (\\* \\Answered \\Flagged \\Deleted \\Draft \\Seen)] \
1623              Read-only mailbox.\r\n\
1624            * 1 EXISTS\r\n\
1625            * 1 RECENT\r\n\
1626            * OK [UNSEEN 1] First unseen.\r\n\
1627            * OK [UIDVALIDITY 1257842737] UIDs valid\r\n\
1628            * OK [UIDNEXT 2] Predicted next UID\r\n\
1629            a1 OK [READ-ONLY] Select completed.\r\n"
1630            .to_vec();
1631        let expected_mailbox = Mailbox {
1632            flags: vec![
1633                Flag::Answered,
1634                Flag::Flagged,
1635                Flag::Deleted,
1636                Flag::Seen,
1637                Flag::Draft,
1638            ],
1639            exists: 1,
1640            recent: 1,
1641            unseen: Some(1),
1642            permanent_flags: vec![
1643                Flag::MayCreate,
1644                Flag::Answered,
1645                Flag::Flagged,
1646                Flag::Deleted,
1647                Flag::Draft,
1648                Flag::Seen,
1649            ],
1650            uid_next: Some(2),
1651            uid_validity: Some(1257842737),
1652        };
1653        let mailbox_name = "INBOX";
1654        let command = format!("a1 SELECT {}\r\n", quote!(mailbox_name));
1655        let mock_stream = MockStream::new(response);
1656        let mut session = mock_session!(mock_stream);
1657        let mailbox = session.select(mailbox_name).unwrap();
1658        assert!(
1659            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1660            "Invalid select command"
1661        );
1662        assert_eq!(mailbox, expected_mailbox);
1663    }
1664
1665    #[test]
1666    fn search() {
1667        let response = b"* SEARCH 1 2 3 4 5\r\n\
1668            a1 OK Search completed\r\n"
1669            .to_vec();
1670        let mock_stream = MockStream::new(response);
1671        let mut session = mock_session!(mock_stream);
1672        let ids = session.search("Unseen").unwrap();
1673        let ids: HashSet<u32> = ids.iter().cloned().collect();
1674        assert!(
1675            session.stream.get_ref().written_buf == b"a1 SEARCH Unseen\r\n".to_vec(),
1676            "Invalid search command"
1677        );
1678        assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
1679    }
1680
1681    #[test]
1682    fn uid_search() {
1683        let response = b"* SEARCH 1 2 3 4 5\r\n\
1684            a1 OK Search completed\r\n"
1685            .to_vec();
1686        let mock_stream = MockStream::new(response);
1687        let mut session = mock_session!(mock_stream);
1688        let ids = session.uid_search("Unseen").unwrap();
1689        let ids: HashSet<Uid> = ids.iter().cloned().collect();
1690        assert!(
1691            session.stream.get_ref().written_buf == b"a1 UID SEARCH Unseen\r\n".to_vec(),
1692            "Invalid search command"
1693        );
1694        assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
1695    }
1696
1697    #[test]
1698    fn capability() {
1699        let response = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n\
1700            a1 OK CAPABILITY completed\r\n"
1701            .to_vec();
1702        let expected_capabilities = vec!["IMAP4rev1", "STARTTLS", "AUTH=GSSAPI", "LOGINDISABLED"];
1703        let mock_stream = MockStream::new(response);
1704        let mut session = mock_session!(mock_stream);
1705        let capabilities = session.capabilities().unwrap();
1706        assert!(
1707            session.stream.get_ref().written_buf == b"a1 CAPABILITY\r\n".to_vec(),
1708            "Invalid capability command"
1709        );
1710        assert_eq!(capabilities.len(), 4);
1711        for e in expected_capabilities {
1712            assert!(capabilities.has_str(e));
1713        }
1714    }
1715
1716    #[test]
1717    fn create() {
1718        let response = b"a1 OK CREATE completed\r\n".to_vec();
1719        let mailbox_name = "INBOX";
1720        let command = format!("a1 CREATE {}\r\n", quote!(mailbox_name));
1721        let mock_stream = MockStream::new(response);
1722        let mut session = mock_session!(mock_stream);
1723        session.create(mailbox_name).unwrap();
1724        assert!(
1725            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1726            "Invalid create command"
1727        );
1728    }
1729
1730    #[test]
1731    fn delete() {
1732        let response = b"a1 OK DELETE completed\r\n".to_vec();
1733        let mailbox_name = "INBOX";
1734        let command = format!("a1 DELETE {}\r\n", quote!(mailbox_name));
1735        let mock_stream = MockStream::new(response);
1736        let mut session = mock_session!(mock_stream);
1737        session.delete(mailbox_name).unwrap();
1738        assert!(
1739            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1740            "Invalid delete command"
1741        );
1742    }
1743
1744    #[test]
1745    fn noop() {
1746        let response = b"a1 OK NOOP completed\r\n".to_vec();
1747        let mock_stream = MockStream::new(response);
1748        let mut session = mock_session!(mock_stream);
1749        session.noop().unwrap();
1750        assert!(
1751            session.stream.get_ref().written_buf == b"a1 NOOP\r\n".to_vec(),
1752            "Invalid noop command"
1753        );
1754    }
1755
1756    #[test]
1757    fn close() {
1758        let response = b"a1 OK CLOSE completed\r\n".to_vec();
1759        let mock_stream = MockStream::new(response);
1760        let mut session = mock_session!(mock_stream);
1761        session.close().unwrap();
1762        assert!(
1763            session.stream.get_ref().written_buf == b"a1 CLOSE\r\n".to_vec(),
1764            "Invalid close command"
1765        );
1766    }
1767
1768    #[test]
1769    fn store() {
1770        generic_store(" ", |c, set, query| c.store(set, query));
1771    }
1772
1773    #[test]
1774    fn uid_store() {
1775        generic_store(" UID ", |c, set, query| c.uid_store(set, query));
1776    }
1777
1778    fn generic_store<F, T>(prefix: &str, op: F)
1779    where
1780        F: FnOnce(&mut Session<MockStream>, &str, &str) -> Result<T>,
1781    {
1782        let res = "* 2 FETCH (FLAGS (\\Deleted \\Seen))\r\n\
1783                   * 3 FETCH (FLAGS (\\Deleted))\r\n\
1784                   * 4 FETCH (FLAGS (\\Deleted \\Flagged \\Seen))\r\n\
1785                   a1 OK STORE completed\r\n";
1786
1787        generic_with_uid(res, "STORE", "2.4", "+FLAGS (\\Deleted)", prefix, op);
1788    }
1789
1790    #[test]
1791    fn copy() {
1792        generic_copy(" ", |c, set, query| c.copy(set, query))
1793    }
1794
1795    #[test]
1796    fn uid_copy() {
1797        generic_copy(" UID ", |c, set, query| c.uid_copy(set, query))
1798    }
1799
1800    fn generic_copy<F, T>(prefix: &str, op: F)
1801    where
1802        F: FnOnce(&mut Session<MockStream>, &str, &str) -> Result<T>,
1803    {
1804        generic_with_uid(
1805            "OK COPY completed\r\n",
1806            "COPY",
1807            "2:4",
1808            "MEETING",
1809            prefix,
1810            op,
1811        );
1812    }
1813
1814    #[test]
1815    fn mv() {
1816        let response = b"* OK [COPYUID 1511554416 142,399 41:42] Moved UIDs.\r\n\
1817            * 2 EXPUNGE\r\n\
1818            * 1 EXPUNGE\r\n\
1819            a1 OK Move completed\r\n"
1820            .to_vec();
1821        let mailbox_name = "MEETING";
1822        let command = format!("a1 MOVE 1:2 {}\r\n", quote!(mailbox_name));
1823        let mock_stream = MockStream::new(response);
1824        let mut session = mock_session!(mock_stream);
1825        session.mv("1:2", mailbox_name).unwrap();
1826        assert!(
1827            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1828            "Invalid move command"
1829        );
1830    }
1831
1832    #[test]
1833    fn uid_mv() {
1834        let response = b"* OK [COPYUID 1511554416 142,399 41:42] Moved UIDs.\r\n\
1835            * 2 EXPUNGE\r\n\
1836            * 1 EXPUNGE\r\n\
1837            a1 OK Move completed\r\n"
1838            .to_vec();
1839        let mailbox_name = "MEETING";
1840        let command = format!("a1 UID MOVE 41:42 {}\r\n", quote!(mailbox_name));
1841        let mock_stream = MockStream::new(response);
1842        let mut session = mock_session!(mock_stream);
1843        session.uid_mv("41:42", mailbox_name).unwrap();
1844        assert!(
1845            session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1846            "Invalid uid move command"
1847        );
1848    }
1849
1850    #[test]
1851    fn fetch() {
1852        generic_fetch(" ", |c, seq, query| c.fetch(seq, query))
1853    }
1854
1855    #[test]
1856    fn uid_fetch() {
1857        generic_fetch(" UID ", |c, seq, query| c.uid_fetch(seq, query))
1858    }
1859
1860    fn generic_fetch<F, T>(prefix: &str, op: F)
1861    where
1862        F: FnOnce(&mut Session<MockStream>, &str, &str) -> Result<T>,
1863    {
1864        generic_with_uid("OK FETCH completed\r\n", "FETCH", "1", "BODY[]", prefix, op);
1865    }
1866
1867    fn generic_with_uid<F, T>(res: &str, cmd: &str, seq: &str, query: &str, prefix: &str, op: F)
1868    where
1869        F: FnOnce(&mut Session<MockStream>, &str, &str) -> Result<T>,
1870    {
1871        let resp = format!("a1 {}\r\n", res).as_bytes().to_vec();
1872        let line = format!("a1{}{} {} {}\r\n", prefix, cmd, seq, query);
1873        let mut session = mock_session!(MockStream::new(resp));
1874        let _ = op(&mut session, seq, query);
1875        assert!(
1876            session.stream.get_ref().written_buf == line.as_bytes().to_vec(),
1877            "Invalid command"
1878        );
1879    }
1880
1881    #[test]
1882    fn quote_backslash() {
1883        assert_eq!("\"test\\\\text\"", quote!(r"test\text"));
1884    }
1885
1886    #[test]
1887    fn quote_dquote() {
1888        assert_eq!("\"test\\\"text\"", quote!("test\"text"));
1889    }
1890
1891    #[test]
1892    fn validate_random() {
1893        assert_eq!(
1894            "\"~iCQ_k;>[&\\\"sVCvUW`e<<P!wJ\"",
1895            &validate_str("~iCQ_k;>[&\"sVCvUW`e<<P!wJ").unwrap()
1896        );
1897    }
1898
1899    #[test]
1900    fn validate_newline() {
1901        if let Err(ref e) = validate_str("test\nstring") {
1902            if let &Error::Validate(ref ve) = e {
1903                if ve.0 == '\n' {
1904                    return;
1905                }
1906            }
1907            panic!("Wrong error: {:?}", e);
1908        }
1909        panic!("No error");
1910    }
1911
1912    #[test]
1913    #[allow(unreachable_patterns)]
1914    fn validate_carriage_return() {
1915        if let Err(ref e) = validate_str("test\rstring") {
1916            if let &Error::Validate(ref ve) = e {
1917                if ve.0 == '\r' {
1918                    return;
1919                }
1920            }
1921            panic!("Wrong error: {:?}", e);
1922        }
1923        panic!("No error");
1924    }
1925}