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}