diff --git a/examples/basic.rs b/examples/basic.rs index 35b64c9..b79ff44 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -35,7 +35,7 @@ fn fetch_inbox_top() -> imap::error::Result> { }; // extract the message's body - let body = message.rfc822().expect("message did not have a body!"); + let body = message.body().expect("message did not have a body!"); let body = std::str::from_utf8(body) .expect("message was not valid utf-8") .to_string(); diff --git a/src/client.rs b/src/client.rs index 181258d..afd722b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,10 +11,7 @@ use std::sync::mpsc; use super::authenticator::Authenticator; use super::error::{Error, ParseError, Result, ValidateError}; use super::extensions; -use super::parse::{ - parse_authenticate_response, parse_capabilities, parse_fetches, parse_ids, parse_mailbox, - parse_names, -}; +use super::parse::*; use super::types::*; static TAG_PREFIX: &'static str = "a"; @@ -41,20 +38,26 @@ fn validate_str(value: &str) -> Result { /// An authenticated IMAP session providing the usual IMAP commands. This type is what you get from /// a succesful login attempt. +/// +/// Note that the server *is* allowed to unilaterally send things to the client for messages in +/// a selected mailbox whose status has changed. See the note on [unilateral server responses +/// in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). Any such messages are parsed out +/// and sent on `Session::unsolicited_responses`. // Both `Client` and `Session` deref to [`Connection`](struct.Connection.html), the underlying // primitives type. #[derive(Debug)] pub struct Session { conn: Connection, + unsolicited_responses_tx: mpsc::Sender, + /// Server responses that are not related to the current command. See also the note on /// [unilateral server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). pub unsolicited_responses: mpsc::Receiver, - unsolicited_responses_tx: mpsc::Sender, } /// An (unauthenticated) handle to talk to an IMAP server. This is what you get when first -/// connecting. A succesfull call to [`login`](struct.Client.html#method.login) will return a -/// [`Session`](struct.Session.html) instance, providing the usual IMAP methods. +/// connecting. A succesfull call to [`Client::login`] or [`Client::authenticate`] will return a +/// [`Session`] instance that provides the usual IMAP methods. // Both `Client` and `Session` deref to [`Connection`](struct.Connection.html), the underlying // primitives type. #[derive(Debug)] @@ -399,8 +402,8 @@ impl Session { /// The `EXAMINE` command is identical to [`Session::select`] and returns the same output; /// however, the selected mailbox is identified as read-only. No changes to the permanent state - /// of the mailbox, including per-user state, are permitted; in particular, `EXAMINE` MUST NOT - /// cause messages to lose [`Flag::Recent`]. + /// of the mailbox, including per-user state, will happen in a mailbox opened with `examine`; + /// in particular, messagess cannot lose [`Flag::Recent`] in an examined mailbox. pub fn examine(&mut self, mailbox_name: &str) -> Result { self.run_command_and_read_response(&format!("EXAMINE {}", validate_str(mailbox_name)?)) .and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx)) @@ -411,16 +414,74 @@ impl Session { /// Note that the server *is* allowed to unilaterally include `FETCH` responses for other /// messages in the selected mailbox whose status has changed. See the note on [unilateral /// server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). + /// + /// `query` is a list of "data items" (space-separated in parentheses if `>1`). There are three + /// "macro items" which specify commonly-used sets of data items, and can be used instead of + /// data items. A macro must be used by itself, and not in conjunction with other macros or + /// data items. They are: + /// + /// - `ALL`: equivalent to: `(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)` + /// - `FAST`: equivalent to: `(FLAGS INTERNALDATE RFC822.SIZE)` + /// - `FULL`: equivalent to: `(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)` + /// + /// The currently defined data items that can be fetched are listen [in the + /// RFC](https://tools.ietf.org/html/rfc3501#section-6.4.5), but here are some common ones: + /// + /// - `FLAGS`: The flags that are set for this message. + /// - `INTERNALDATE`: The internal date of the message. + /// - `BODY`: Non-extensible form of BODYSTRUCTURE. + /// - `BODY[
]`: + /// + /// The text of a particular body section. The section specification is a set of zero or + /// more part specifiers delimited by periods. A part specifier is either a part number + /// (see RFC) or one of the following: `HEADER`, `HEADER.FIELDS`, `HEADER.FIELDS.NOT`, + /// `MIME`, and `TEXT`. An empty section specification refers to the entire message, + /// including the header. + /// + /// The `HEADER`, `HEADER.FIELDS`, and `HEADER.FIELDS.NOT` part specifiers refer to the + /// [RFC-2822](https://tools.ietf.org/html/rfc2822) header of the message or of an + /// encapsulated [MIME-IMT](https://tools.ietf.org/html/rfc2046) + /// MESSAGE/[RFC822](https://tools.ietf.org/html/rfc822) message. `HEADER.FIELDS` and + /// `HEADER.FIELDS.NOT` are followed by a list of field-name (as defined in + /// [RFC-2822](https://tools.ietf.org/html/rfc2822)) names, and return a subset of the + /// header. The subset returned by `HEADER.FIELDS` contains only those header fields with + /// a field-name that matches one of the names in the list; similarly, the subset returned + /// by `HEADER.FIELDS.NOT` contains only the header fields with a non-matching field-name. + /// The field-matching is case-insensitive but otherwise exact. Subsetting does not + /// exclude the [RFC-2822](https://tools.ietf.org/html/rfc2822) delimiting blank line + /// between the header and the body; the blank line is included in all header fetches, + /// except in the case of a message which has no body and no blank line. + /// + /// The `MIME` part specifier refers to the [MIME-IMB](https://tools.ietf.org/html/rfc2045) + /// header for this part. + /// + /// The `TEXT` part specifier refers to the text body of the message, + /// omitting the [RFC-2822](https://tools.ietf.org/html/rfc2822) header. + /// + /// [`Flag::Seen`] is implicitly set when `BODY` is fetched; if this causes the flags to + /// change, they will generally be included as part of the `FETCH` responses. + /// - `BODY.PEEK[
]`: An alternate form of `BODY[
]` that does not implicitly + /// set [`Flag::Seen`]. + /// - `BODYSTRUCTURE`: The [MIME-IMB](https://tools.ietf.org/html/rfc2045) body structure of + /// the message. This is computed by the server by parsing the + /// [MIME-IMB](https://tools.ietf.org/html/rfc2045) header fields in the [RFC-2822] header + /// and [MIME-IMB](https://tools.ietf.org/html/rfc2045) headers. + /// - `ENVELOPE`: The envelope structure of the message. This is computed by the server by + /// parsing the [RFC-2822](https://tools.ietf.org/html/rfc2822) header into the component + /// parts, defaulting various fields as necessary. + /// - `RFC822`: Functionally equivalent to `BODY[]`. + /// - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`. + /// - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message. + /// - `RFC822.TEXT`: Functionally equivalent to `BODY[TEXT]`, differing in the syntax + /// of the resulting untagged FETCH data (RFC822.TEXT is returned). + /// - `UID`: The unique identifier for the message. pub fn fetch(&mut self, sequence_set: &str, query: &str) -> ZeroCopyResult> { self.run_command_and_read_response(&format!("FETCH {} {}", sequence_set, query)) .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) } - /// Fetch retreives data associated with a set of messages by UID in the mailbox. - /// - /// Note that the server *is* allowed to unilaterally include `FETCH` responses for other - /// messages in the selected mailbox whose status has changed. See the note on [unilateral - /// server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). + /// Equivalent to [`Session::fetch`], except that all identifiers in `sequence_set` are + /// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8). pub fn uid_fetch(&mut self, uid_set: &str, query: &str) -> ZeroCopyResult> { self.run_command_and_read_response(&format!("UID FETCH {} {}", uid_set, query)) .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) @@ -436,63 +497,176 @@ impl Session { self.run_command_and_check_ok("LOGOUT") } - /// Create creates a mailbox with the given name. + /// The [`CREATE` command](https://tools.ietf.org/html/rfc3501#section-6.3.3) creates a mailbox + /// with the given name. `Ok` is returned only if a new mailbox with that name has been + /// created. It is an error to attempt to create `INBOX` or a mailbox with a name that + /// refers to an extant mailbox. Any error in creation will return [`Error::No`]. + /// + /// If the mailbox name is suffixed with the server's hierarchy separator character (as + /// returned from the server by [`Session::list`]), this is a declaration that the client + /// intends to create mailbox names under this name in the hierarchy. Servers that do not + /// require this declaration will ignore the declaration. In any case, the name created is + /// without the trailing hierarchy delimiter. + /// + /// If the server's hierarchy separator character appears elsewhere in the name, the server + /// will generally create any superior hierarchical names that are needed for the `CREATE` + /// command to be successfully completed. In other words, an attempt to create `foo/bar/zap` + /// on a server in which `/` is the hierarchy separator character will usually create `foo/` + /// and `foo/bar/` if they do not already exist. + /// + /// If a new mailbox is created with the same name as a mailbox which was deleted, its unique + /// identifiers will be greater than any unique identifiers used in the previous incarnation of + /// the mailbox UNLESS the new incarnation has a different unique identifier validity value. + /// See the description of the [`UID` + /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8) for more detail. pub fn create(&mut self, mailbox_name: &str) -> Result<()> { self.run_command_and_check_ok(&format!("CREATE {}", validate_str(mailbox_name)?)) } - /// Delete permanently removes the mailbox with the given name. + /// The [`DELETE` command](https://tools.ietf.org/html/rfc3501#section-6.3.4) permanently + /// removes the mailbox with the given name. `Ok` is returned only if the mailbox has been + /// deleted. It is an error to attempt to delete `INBOX` or a mailbox name that does not + /// exist. + /// + /// The `DELETE` command will not remove inferior hierarchical names. For example, if a mailbox + /// `foo` has an inferior `foo.bar` (assuming `.` is the hierarchy delimiter character), + /// removing `foo` will not remove `foo.bar`. It is an error to attempt to delete a name that + /// has inferior hierarchical names and also has [`NameAttribute::NoSelect`]. + /// + /// It is permitted to delete a name that has inferior hierarchical names and does not have + /// [`NameAttribute::NoSelect`]. In this case, all messages in that mailbox are removed, and + /// the name will acquire [`NameAttribute::NoSelect`]. + /// + /// The value of the highest-used unique identifier of the deleted mailbox will be preserved so + /// that a new mailbox created with the same name will not reuse the identifiers of the former + /// incarnation, UNLESS the new incarnation has a different unique identifier validity value. + /// See the description of the [`UID` + /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8) for more detail. pub fn delete(&mut self, mailbox_name: &str) -> Result<()> { self.run_command_and_check_ok(&format!("DELETE {}", validate_str(mailbox_name)?)) } - /// Rename changes the name of a mailbox. - pub fn rename(&mut self, current_mailbox_name: &str, new_mailbox_name: &str) -> Result<()> { - self.run_command_and_check_ok(&format!( - "RENAME {} {}", - quote!(current_mailbox_name), - quote!(new_mailbox_name) - )) + /// The [`RENAME` command](https://tools.ietf.org/html/rfc3501#section-6.3.5) changes the name + /// of a mailbox. `Ok` is returned only if the mailbox has been renamed. It is an error to + /// attempt to rename from a mailbox name that does not exist or to a mailbox name that already + /// exists. Any error in renaming will return [`Error::No`]. + /// + /// If the name has inferior hierarchical names, then the inferior hierarchical names will also + /// be renamed. For example, a rename of `foo` to `zap` will rename `foo/bar` (assuming `/` is + /// the hierarchy delimiter character) to `zap/bar`. + /// + /// If the server's hierarchy separator character appears in the name, the server will + /// generally create any superior hierarchical names that are needed for the `RENAME` command + /// to complete successfully. In other words, an attempt to rename `foo/bar/zap` to + /// `baz/rag/zowie` on a server in which `/` is the hierarchy separator character will + /// generally create `baz/` and `baz/rag/` if they do not already exist. + /// + /// The value of the highest-used unique identifier of the old mailbox name will be preserved + /// so that a new mailbox created with the same name will not reuse the identifiers of the + /// former incarnation, UNLESS the new incarnation has a different unique identifier validity + /// value. See the description of the [`UID` + /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8) for more detail. + /// + /// Renaming `INBOX` is permitted, and has special behavior. It moves all messages in `INBOX` + /// to a new mailbox with the given name, leaving `INBOX` empty. If the server implementation + /// supports inferior hierarchical names of `INBOX`, these are unaffected by a rename of + /// `INBOX`. + pub fn rename(&mut self, from: &str, to: &str) -> Result<()> { + self.run_command_and_check_ok(&format!("RENAME {} {}", quote!(from), quote!(to))) } - /// Subscribe adds the specified mailbox name to the server's set of "active" or "subscribed" - /// mailboxes as returned by the LSUB command. + /// The [`SUBSCRIBE` command](https://tools.ietf.org/html/rfc3501#section-6.3.6) adds the + /// specified mailbox name to the server's set of "active" or "subscribed" mailboxes as + /// returned by [`Session::lsub`]. This command returns `Ok` only if the subscription is + /// successful. + /// + /// The server may validate the mailbox argument to `SUBSCRIBE` to verify that it exists. + /// However, it will not unilaterally remove an existing mailbox name from the subscription + /// list even if a mailbox by that name no longer exists. pub fn subscribe(&mut self, mailbox: &str) -> Result<()> { self.run_command_and_check_ok(&format!("SUBSCRIBE {}", quote!(mailbox))) } - /// Unsubscribe removes the specified mailbox name from the server's set of - /// "active" or "subscribed mailboxes as returned by the LSUB command. + /// The [`UNSUBSCRIBE` command](https://tools.ietf.org/html/rfc3501#section-6.3.7) removes the + /// specified mailbox name from the server's set of "active" or "subscribed" mailboxes as + /// returned by [`Session::lsub`]. This command returns `Ok` only if the unsubscription is + /// successful. pub fn unsubscribe(&mut self, mailbox: &str) -> Result<()> { self.run_command_and_check_ok(&format!("UNSUBSCRIBE {}", quote!(mailbox))) } - /// Capability requests a listing of capabilities that the server supports. + /// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a + /// listing of capabilities that the server supports. The server will include "IMAP4rev1" as + /// one of the listed capabilities. See [`Capabilities`] for further details. pub fn capabilities(&mut self) -> ZeroCopyResult { self.run_command_and_read_response("CAPABILITY") .and_then(|lines| parse_capabilities(lines, &mut self.unsolicited_responses_tx)) } - /// Expunge permanently removes all messages that have the \Deleted flag set from the currently - /// selected mailbox. - pub fn expunge(&mut self) -> Result<()> { - self.run_command_and_check_ok("EXPUNGE") + /// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently + /// removes all messages that have [`Flag::Deleted`] set from the currently selected mailbox. + /// The message sequence number of each message that is removed is returned. + pub fn expunge(&mut self) -> Result> { + self.run_command_and_read_response("EXPUNGE") + .and_then(|lines| parse_expunge(lines, &mut self.unsolicited_responses_tx)) } - /// Permanently removes all messages that have both the \Deleted flag set and have a UID that is - /// included in the specified message set. - /// The UID EXPUNGE command is defined in [RFC 4315 - "Internet Message Access Protocol (IMAP) - UIDPLUS extension"](https://tools.ietf.org/html/rfc4315#section-2.1). - pub fn uid_expunge(&mut self, uid_set: &str) -> Result<()> { - self.run_command_and_check_ok(&format!("UID EXPUNGE {}", uid_set)) + /// The [`UID EXPUNGE` command](https://tools.ietf.org/html/rfc4315#section-2.1) permanently + /// removes all messages that both have [`Flag::Deleted`] set and have a [`Uid`] that is + /// included in the specified sequence set from the currently selected mailbox. If a message + /// either does not have [`Flag::Deleted`] set or has a [`Uid`] that is not included in the + /// specified sequence set, it is not affected. + /// + /// This command is particularly useful for disconnected use clients. By using [`uid_expunge`] + /// instead of [`expunge`] when resynchronizing with the server, the client can ensure that it + /// does not inadvertantly remove any messages that have been marked as [`Flag::Deleted`] by + /// other clients between the time that the client was last connected and the time the client + /// resynchronizes. + /// + /// This command requires that the server supports [RFC + /// 4315](https://tools.ietf.org/html/rfc4315) as indicated by the `UIDPLUS` capability (see + /// [`Session::capabilities`]). If the server does not support the `UIDPLUS` capability, the + /// client should fall back to using [`Session::store`] to temporarily remove [`Flag::Deleted`] + /// from messages it does not want to remove, then invoking [`Session::expunge`]. Finally, the + /// client should use [`Session::store`] to restore [`Flag::Deleted`] on the messages in which + /// it was temporarily removed. + /// + /// Alternatively, the client may fall back to using just [`Session::expunge`], risking the + /// unintended removal of some messages. + pub fn uid_expunge(&mut self, uid_set: &str) -> Result> { + self.run_command_and_read_response(&format!("UID EXPUNGE {}", uid_set)) + .and_then(|lines| parse_expunge(lines, &mut self.unsolicited_responses_tx)) } - /// Check requests a checkpoint of the currently selected mailbox. + /// The [`CHECK` command](https://tools.ietf.org/html/rfc3501#section-6.4.1) requests a + /// checkpoint of the currently selected mailbox. A checkpoint refers to any + /// implementation-dependent housekeeping associated with the mailbox (e.g., resolving the + /// server's in-memory state of the mailbox with the state on its disk) that is not normally + /// executed as part of each command. A checkpoint MAY take a non-instantaneous amount of real + /// time to complete. If a server implementation has no such housekeeping considerations, + /// [`Session::check`] is equivalent to [`Session::noop`]. + /// + /// There is no guarantee that an `EXISTS` untagged response will happen as a result of + /// `CHECK`. [`Session::noop`] SHOULD be used for new message polling. pub fn check(&mut self) -> Result<()> { self.run_command_and_check_ok("CHECK") } - /// Close permanently removes all messages that have the \Deleted flag set from the currently - /// selected mailbox, and returns to the authenticated state from the selected state. + /// The [`CLOSE` command](https://tools.ietf.org/html/rfc3501#section-6.4.2) permanently + /// removes all messages that have [`Flag::Deleted`] set from the currently selected mailbox, + /// and returns to the authenticated state from the selected state. No `EXPUNGE` responses are + /// sent. + /// + /// No messages are removed, and no error is given, if the mailbox is selected by + /// [`Session::examine`] or is otherwise selected read-only. + /// + /// Even if a mailbox is selected, [`Session::select`], [`Session::examine`], or + /// [`Session::logout`] command MAY be issued without previously invoking [`Session::close`]. + /// [`Session::select`], [`Session::examine`], and [`Session::logout`] implicitly close the + /// currently selected mailbox without doing an expunge. However, when many messages are + /// deleted, a `CLOSE-LOGOUT` or `CLOSE-SELECT` sequence is considerably faster than an + /// `EXPUNGE-LOGOUT` or `EXPUNGE-SELECT` because no `EXPUNGE` responses (which the client would + /// probably ignore) are sent. pub fn close(&mut self) -> Result<()> { self.run_command_and_check_ok("CLOSE") } @@ -500,35 +674,32 @@ impl Session { /// The [`STORE` command](https://tools.ietf.org/html/rfc3501#section-6.4.6) alters data /// associated with a message in the mailbox. Normally, `STORE` will return the updated value /// of the data with an untagged FETCH response. A suffix of `.SILENT` in `query` prevents the - /// untagged `FETCH`, and the server SHOULD assume that the client has determined the updated - /// value itself or does not care about the updated value. + /// untagged `FETCH`, and the server assumes that the client has determined the updated value + /// itself or does not care about the updated value. /// /// The currently defined data items that can be stored are: /// - /// ```text - /// FLAGS - /// Replace the flags for the message (other than \Recent) with the - /// argument. The new value of the flags is returned as if a FETCH - /// of those flags was done. + /// - `FLAGS `: /// - /// FLAGS.SILENT - /// Equivalent to FLAGS, but without returning a new value. + /// Replace the flags for the message (other than [`Flag::Recent`]) with the argument. The + /// new value of the flags is returned as if a `FETCH` of those flags was done. /// - /// +FLAGS - /// Add the argument to the flags for the message. The new value - /// of the flags is returned as if a FETCH of those flags was done. + /// - `FLAGS.SILENT `: Equivalent to `FLAGS`, but without returning a new value. /// - /// +FLAGS.SILENT - /// Equivalent to +FLAGS, but without returning a new value. + /// - `+FLAGS ` /// - /// -FLAGS - /// Remove the argument from the flags for the message. The new - /// value of the flags is returned as if a FETCH of those flags was - /// done. + /// Add the argument to the flags for the message. The new value of the flags is returned + /// as if a `FETCH` of those flags was done. + /// - `+FLAGS.SILENT `: Equivalent to `+FLAGS`, but without returning a new value. /// - /// -FLAGS.SILENT - /// Equivalent to -FLAGS, but without returning a new value. - /// ``` + /// - `-FLAGS ` + /// + /// Remove the argument from the flags for the message. The new value of the flags is + /// returned as if a `FETCH` of those flags was done. + /// + /// - `-FLAGS.SILENT `: Equivalent to `-FLAGS`, but without returning a new value. + /// + /// In all cases, `` is a space-separated list enclosed in parentheses. pub fn store(&mut self, sequence_set: &str, query: &str) -> ZeroCopyResult> { self.run_command_and_read_response(&format!("STORE {} {}", sequence_set, query)) .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) @@ -543,11 +714,11 @@ impl Session { /// The [`COPY` command](https://tools.ietf.org/html/rfc3501#section-6.4.7) copies the /// specified message(s) to the end of the specified destination mailbox. The flags and - /// internal date of the message(s) SHOULD be preserved, and [`Flag::Recent`] SHOULD be set, in - /// the copy. + /// internal date of the message(s) will generally be preserved, and [`Flag::Recent`] will + /// generally be set, in the copy. /// - /// If the `COPY` command is unsuccessful for any reason, server implementations MUST restore - /// the destination mailbox to its state before the `COPY` attempt. + /// If the `COPY` command is unsuccessful for any reason, the server restores the destination + /// mailbox to its state before the `COPY` attempt. pub fn copy(&mut self, sequence_set: &str, mailbox_name: &str) -> Result<()> { self.run_command_and_check_ok(&format!("COPY {} {}", sequence_set, mailbox_name)) } @@ -558,13 +729,7 @@ impl Session { self.run_command_and_check_ok(&format!("UID COPY {} {}", uid_set, mailbox_name)) } - /// Moves each message in the sequence into the destination mailbox. This function is - /// named `mv` instead of `move` due to it being a reserved keyword. - /// The MOVE command is defined in [RFC 6851 - "Internet Message Access Protocol (IMAP) - /// - MOVE Extension"](https://tools.ietf.org/html/rfc6851#section-3). - /// - /// The [`MOVE` command](https://tools.ietf.org/html/rfc6851#section-3.1) is an IMAP extension - /// outlined in [RFC 6851](https://tools.ietf.org/html/rfc6851#section-3.1). It takes two + /// The [`MOVE` command](https://tools.ietf.org/html/rfc6851#section-3.1) takes two /// arguments: a sequence set and a named mailbox. Each message included in the set is moved, /// rather than copied, from the selected (source) mailbox to the named (target) mailbox. /// @@ -577,19 +742,23 @@ impl Session { /// 2. STORE +FLAGS.SILENT \DELETED /// 3. EXPUNGE /// + /// This command requires that the server supports [RFC + /// 6851](https://tools.ietf.org/html/rfc6851) as indicated by the `MOVE` capability (see + /// [`Session::capabilities`]). + /// /// Although the effect of the `MOVE` is the same as the preceding steps, the semantics are not /// identical: The intermediate states produced by those steps do not occur, and the response /// codes are different. In particular, though the `COPY` and `EXPUNGE` response codes will be - /// returned, response codes for a STORE MUST NOT be generated and [`Flag::Deleted`] MUST NOT + /// returned, response codes for a `store` will not be generated and [`Flag::Deleted`] will not /// be set for any message. /// /// Because a `MOVE` applies to a set of messages, it might fail partway through the set. /// Regardless of whether the command is successful in moving the entire set, each individual - /// message SHOULD either be moved or unaffected. The server MUST leave each message in a - /// state where it is in at least one of the source or target mailboxes (no message can be lost - /// or orphaned). The server SHOULD NOT leave any message in both mailboxes (it would be bad - /// for a partial failure to result in a bunch of duplicate messages). This is true even if - /// the server returns with [`Error::No`]. + /// message will either be moved or unaffected. The server will leave each message in a state + /// where it is in at least one of the source or target mailboxes (no message can be lost or + /// orphaned). The server will generally not leave any message in both mailboxes (it would be + /// bad for a partial failure to result in a bunch of duplicate messages). This is true even + /// if the server returns with [`Error::No`]. pub fn mv(&mut self, sequence_set: &str, mailbox_name: &str) -> Result<()> { self.run_command_and_check_ok(&format!( "MOVE {} {}", @@ -610,55 +779,164 @@ impl Session { )) } - /// The LIST command returns a subset of names from the complete set - /// of all names available to the client. + /// The [`LIST` command](https://tools.ietf.org/html/rfc3501#section-6.3.8) returns a subset of + /// names from the complete set of all names available to the client. It returns the name + /// attributes, hierarchy delimiter, and name of each such name; see [`Name`] for more detail. + /// + /// If `reference_name` is `None` (or `""`), the mailbox name is interpreted as by `SELECT`. + /// The returned mailbox names must match the supplied mailbox name pattern. A non-empty + /// reference name argument is the name of a mailbox or a level of mailbox hierarchy, and + /// indicates the context in which the mailbox name is interpreted. + /// + /// If `mailbox_pattern` is `None` (or `""`), it is a special request to return the hierarchy + /// delimiter and the root name of the name given in the reference. The value returned as the + /// root MAY be the empty string if the reference is non-rooted or is an empty string. In all + /// cases, a hierarchy delimiter (or `NIL` if there is no hierarchy) is returned. This permits + /// a client to get the hierarchy delimiter (or find out that the mailbox names are flat) even + /// when no mailboxes by that name currently exist. + /// + /// The reference and mailbox name arguments are interpreted into a canonical form that + /// represents an unambiguous left-to-right hierarchy. The returned mailbox names will be in + /// the interpreted form. + /// + /// The character `*` is a wildcard, and matches zero or more characters at this position. The + /// character `%` is similar to `*`, but it does not match a hierarchy delimiter. If the `%` + /// wildcard is the last character of a mailbox name argument, matching levels of hierarchy are + /// also returned. If these levels of hierarchy are not also selectable mailboxes, they are + /// returned with [`NameAttribute::NoSelect`]. + /// + /// The special name `INBOX` is included if `INBOX` is supported by this server for this user + /// and if the uppercase string `INBOX` matches the interpreted reference and mailbox name + /// arguments with wildcards. The criteria for omitting `INBOX` is whether `SELECT INBOX` will + /// return failure; it is not relevant whether the user's real `INBOX` resides on this or some + /// other server. pub fn list( &mut self, - reference_name: &str, - mailbox_search_pattern: &str, + reference_name: Option<&str>, + mailbox_pattern: Option<&str>, ) -> ZeroCopyResult> { self.run_command_and_read_response(&format!( "LIST {} {}", - quote!(reference_name), - mailbox_search_pattern + quote!(reference_name.unwrap_or("")), + mailbox_pattern.unwrap_or("") )) .and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx)) } - /// The LSUB command returns a subset of names from the set of names - /// that the user has declared as being "active" or "subscribed". + /// The [`LSUB` command](https://tools.ietf.org/html/rfc3501#section-6.3.9) returns a subset of + /// names from the set of names that the user has declared as being "active" or "subscribed". + /// The arguments to this method the same as for [`Session::list`]. + /// + /// The returned [`Name`]s MAY contain different mailbox flags from response to + /// [`Session::list`]. If this should happen, the flags returned by [`Session::list`] are + /// considered more authoritative. + /// + /// A special situation occurs when invoking `lsub` with the `%` wildcard. Consider what + /// happens if `foo/bar` (with a hierarchy delimiter of `/`) is subscribed but `foo` is not. A + /// `%` wildcard to `lsub` must return `foo`, not `foo/bar`, and it will be flagged with + /// [`NameAttribute::NoSelect`]. + /// + /// The server will not unilaterally remove an existing mailbox name from the subscription list + /// even if a mailbox by that name no longer exists. pub fn lsub( &mut self, - reference_name: &str, - mailbox_search_pattern: &str, + reference_name: Option<&str>, + mailbox_pattern: Option<&str>, ) -> ZeroCopyResult> { self.run_command_and_read_response(&format!( "LSUB {} {}", - quote!(reference_name), - mailbox_search_pattern + quote!(reference_name.unwrap_or("")), + mailbox_pattern.unwrap_or("") )) .and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx)) } - /// The STATUS command requests the status of the indicated mailbox. - pub fn status(&mut self, mailbox_name: &str, status_data_items: &str) -> Result { + /// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the + /// status of the indicated mailbox. It does not change the currently selected mailbox, nor + /// does it affect the state of any messages in the queried mailbox (in particular, `status` + /// will not cause messages to lose [`Flag::Recent`]). + /// + /// `status` provides an alternative to opening a second [`Session`] and using + /// [`Session::examine`] on a mailbox to query that mailbox's status without deselecting the + /// current mailbox in the first `Session`. + /// + /// Unlike [`Session::list`], `status` is not guaranteed to be fast in its response. Under + /// certain circumstances, it can be quite slow. In some implementations, the server is + /// obliged to open the mailbox read-only internally to obtain certain status information. + /// Also unlike [`Session::list`], `status` does not accept wildcards. + /// + /// > Note: `status` is intended to access the status of mailboxes other than the currently + /// > selected mailbox. Because `status` can cause the mailbox to be opened internally, and + /// > because this information is available by other means on the selected mailbox, `status` + /// > SHOULD NOT be used on the currently selected mailbox. + /// + /// The STATUS command MUST NOT be used as a "check for new messages in the selected mailbox" + /// operation (refer to sections [7](https://tools.ietf.org/html/rfc3501#section-7), + /// [7.3.1](https://tools.ietf.org/html/rfc3501#section-7.3.1), and + /// [7.3.2](https://tools.ietf.org/html/rfc3501#section-7.3.2) for more information about the + /// proper method for new message checking). + /// + /// The currently defined status data items that can be requested are: + /// + /// - `MESSAGES`: The number of messages in the mailbox. + /// - `RECENT`: The number of messages with [`Flag::Recent`] set. + /// - `UIDNEXT`: The next [`Uid`] of the mailbox. + /// - `UIDVALIDITY`: The unique identifier validity value of the mailbox (see [`Uid`]). + /// - `UNSEEN`: The number of messages which do not have [`Flag::Seen`] set. + /// + /// `data_times` is a space-separated list enclosed in parentheses. + pub fn status(&mut self, mailbox_name: &str, data_items: &str) -> Result { self.run_command_and_read_response(&format!( "STATUS {} {}", validate_str(mailbox_name)?, - status_data_items + data_items )) .and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx)) } - /// Returns a handle that can be used to block until the state of the currently selected - /// mailbox changes. + /// This method returns a handle that lets you use the [`IDLE` + /// command](https://tools.ietf.org/html/rfc2177#section-3) to listen for changes to the + /// currently selected mailbox. + /// + /// It's often more desirable to have the server transmit updates to the client in real time. + /// This allows a user to see new mail immediately. It also helps some real-time applications + /// based on IMAP, which might otherwise need to poll extremely often (such as every few + /// seconds). While the spec actually does allow a server to push `EXISTS` responses + /// aysynchronously, a client can't expect this behaviour and must poll. This method provides + /// you with such a mechanism. + /// + /// `idle` may be used with any server that returns `IDLE` as one of the supported capabilities + /// (see [`Session::capabilities`]). If the server does not advertise the `IDLE` capability, + /// the client MUST NOT use `idle` and must instead poll for mailbox updates. In particular, + /// the client MUST continue to be able to accept unsolicited untagged responses to ANY + /// command, as specified in the base IMAP specification. + /// + /// See [`extensions::idle::Handle`] for details. pub fn idle(&mut self) -> Result> { extensions::idle::Handle::new(self) } - /// The APPEND command adds a mail to a mailbox. - pub fn append(&mut self, folder: &str, content: &[u8]) -> Result<()> { - self.run_command(&format!("APPEND \"{}\" {{{}}}", folder, content.len()))?; + /// The [`APPEND` command](https://tools.ietf.org/html/rfc3501#section-6.3.11) appends + /// `content` as a new message to the end of the specified destination `mailbox`. This + /// argument SHOULD be in the format of an [RFC-2822](https://tools.ietf.org/html/rfc2822) + /// message. + /// + /// > Note: There MAY be exceptions, e.g., draft messages, in which required RFC-2822 header + /// > lines are omitted in the message literal argument to `append`. The full implications of + /// > doing so MUST be understood and carefully weighed. + /// + /// If the append is unsuccessful for any reason, the mailbox is restored to its state before + /// the append attempt; no partial appending will happen. + /// + /// If the destination `mailbox` does not exist, the server returns an error, and does not + /// automatically create the mailbox. + /// + /// If the mailbox is currently selected, the normal new message actions will generally occur. + /// Specifically, the server will generally notify the client immediately via an untagged + /// `EXISTS` response. If the server does not do so, the client MAY issue a `NOOP` command (or + /// failing that, a `CHECK` command) after one or more `APPEND` commands. + pub fn append(&mut self, mailbox: &str, content: &[u8]) -> Result<()> { + self.run_command(&format!("APPEND \"{}\" {{{}}}", mailbox, content.len()))?; let mut v = Vec::new(); self.readline(&mut v)?; if !v.starts_with(b"+") { @@ -670,15 +948,58 @@ impl Session { self.read_response().map(|_| ()) } - /// Searches the mailbox for messages that match the given criteria and returns - /// the list of message sequence numbers of those messages. - pub fn search(&mut self, query: &str) -> Result> { + /// The [`SEARCH` command](https://tools.ietf.org/html/rfc3501#section-6.4.4) searches the + /// mailbox for messages that match the given `query`. `query` consist of one or more search + /// keys separated by spaces. The response from the server contains a listing of [`Seq`]s + /// corresponding to those messages that match the searching criteria. + /// + /// When multiple search keys are specified, the result is the intersection of all the messages + /// that match those keys. Or, in other words, only messages that match *all* the keys. For + /// example, the criteria + /// + /// ```text + /// DELETED FROM "SMITH" SINCE 1-Feb-1994 + /// ``` + /// + /// refers to all deleted messages from Smith that were placed in the mailbox since February 1, + /// 1994. A search key can also be a parenthesized list of one or more search keys (e.g., for + /// use with the `OR` and `NOT` keys). + /// + /// In all search keys that use strings, a message matches the key if the string is a substring + /// of the field. The matching is case-insensitive. + /// + /// Below is a selection of common search keys. The full list can be found in the + /// specification of the [`SEARCH command`](https://tools.ietf.org/html/rfc3501#section-6.4.4). + /// + /// - `NEW`: Messages that have [`Flag::Recent`] set but not [`Flag::Seen`]. This is functionally equivalent to `(RECENT UNSEEN)`. + /// - `OLD`: Messages that do not have [`Flag::Recent`] set. This is functionally equivalent to `NOT RECENT` (as opposed to `NOT NEW`). + /// - `RECENT`: Messages that have [`Flag::Recent`] set. + /// - `ANSWERED`: Messages with [`Flag::Answered`] set. + /// - `DELETED`: Messages with [`Flag::Deleted`] set. + /// - `DRAFT`: Messages with [`Flag::Draft`] set. + /// - `FLAGGED`: Messages with [`Flag::Flagged`] set. + /// - `SEEN`: Messages that have [`Flag::Seen`] set. + /// - ``: Messages with message sequence numbers corresponding to the specified message sequence number set. + /// - `UID `: Messages with [`Uid`] corresponding to the specified unique identifier set. Sequence set ranges are permitted. + /// + /// - `SUBJECT `: Messages that contain the specified string in the envelope structure's `SUBJECT` field. + /// - `BODY `: Messages that contain the specified string in the body of the message. + /// - `FROM `: Messages that contain the specified string in the envelope structure's `FROM` field. + /// - `TO `: Messages that contain the specified string in the envelope structure's `TO` field. + /// + /// - `NOT `: Messages that do not match the specified search key. + /// - `OR `: Messages that match either search key. + /// + /// - `BEFORE `: Messages whose internal date (disregarding time and timezone) is earlier than the specified date. + /// - `SINCE `: Messages whose internal date (disregarding time and timezone) is within or later than the specified date. + pub fn search(&mut self, query: &str) -> Result> { self.run_command_and_read_response(&format!("SEARCH {}", query)) .and_then(|lines| parse_ids(lines, &mut self.unsolicited_responses_tx)) } - /// Searches the mailbox for messages that match the given criteria and returns - /// the list of unique identifier numbers of those messages. + /// Equivalent to [`Session::search`], except that the returned identifiers + /// are [`Uid`] instead of [`Seq`]. See also the [`UID` + /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8). pub fn uid_search(&mut self, query: &str) -> Result> { self.run_command_and_read_response(&format!("UID SEARCH {}", query)) .and_then(|lines| parse_ids(lines, &mut self.unsolicited_responses_tx)) @@ -788,12 +1109,12 @@ impl Connection { use imap_proto::Status; match status { Status::Bad => { - break Err(Error::BadResponse( + break Err(Error::Bad( expl.unwrap_or_else(|| "no explanation given".to_string()), )) } Status::No => { - break Err(Error::NoResponse( + break Err(Error::No( expl.unwrap_or_else(|| "no explanation given".to_string()), )) } diff --git a/src/error.rs b/src/error.rs index 028e74c..6891406 100644 --- a/src/error.rs +++ b/src/error.rs @@ -26,9 +26,9 @@ pub enum Error { /// An error from the `native_tls` library while managing the socket. Tls(TlsError), /// A BAD response from the IMAP server. - BadResponse(String), + Bad(String), /// A NO response from the IMAP server. - NoResponse(String), + No(String), /// The connection was terminated unexpectedly. ConnectionLost, /// Error parsing a server response. @@ -83,7 +83,7 @@ impl fmt::Display for Error { Error::Tls(ref e) => fmt::Display::fmt(e, f), Error::TlsHandshake(ref e) => fmt::Display::fmt(e, f), Error::Validate(ref e) => fmt::Display::fmt(e, f), - Error::NoResponse(ref data) | Error::BadResponse(ref data) => { + Error::No(ref data) | Error::Bad(ref data) => { write!(f, "{}: {}", &String::from(self.description()), data) } ref e => f.write_str(e.description()), @@ -99,8 +99,8 @@ impl StdError for Error { Error::TlsHandshake(ref e) => e.description(), Error::Parse(ref e) => e.description(), Error::Validate(ref e) => e.description(), - Error::BadResponse(_) => "Bad Response", - Error::NoResponse(_) => "No Response", + Error::Bad(_) => "Bad Response", + Error::No(_) => "No Response", Error::ConnectionLost => "Connection lost", Error::Append => "Could not append mail to mailbox", } diff --git a/src/extensions/idle.rs b/src/extensions/idle.rs index 35a623d..6fa570f 100644 --- a/src/extensions/idle.rs +++ b/src/extensions/idle.rs @@ -10,10 +10,19 @@ use std::time::Duration; /// `Handle` allows a client to block waiting for changes to the remote mailbox. /// -/// The handle blocks using the IMAP IDLE command specificed in [RFC -/// 2177](https://tools.ietf.org/html/rfc2177). +/// The handle blocks using the [`IDLE` command](https://tools.ietf.org/html/rfc2177#section-3) +/// specificed in [RFC 2177](https://tools.ietf.org/html/rfc2177) until the underlying server state +/// changes in some way. While idling does inform the client what changes happened on the server, +/// this implementation will currently just block until _anything_ changes, and then notify the /// -/// As long a the handle is active, the mailbox cannot be otherwise accessed. +/// Note that the server MAY consider a client inactive if it has an IDLE command running, and if +/// such a server has an inactivity timeout it MAY log the client off implicitly at the end of its +/// timeout period. Because of that, clients using IDLE are advised to terminate the IDLE and +/// re-issue it at least every 29 minutes to avoid being logged off. [`Handle::wait_keepalive`] +/// does this. This still allows a client to receive immediate mailbox updates even though it need +/// only "poll" at half hour intervals. +/// +/// As long as a [`Handle`] is active, the mailbox cannot be otherwise accessed. #[derive(Debug)] pub struct Handle<'a, T: Read + Write + 'a> { session: &'a mut Session, @@ -112,10 +121,10 @@ impl<'a, T: SetReadTimeout + Read + Write + 'a> Handle<'a, T> { /// Block until the selected mailbox changes. /// - /// This method differs from `Handle::wait` in that it will periodically refresh the IDLE + /// This method differs from [`Handle::wait`] in that it will periodically refresh the IDLE /// connection, to prevent the server from timing out our connection. The keepalive interval is /// set to 29 minutes by default, as dictated by RFC 2177, but can be changed using - /// `set_keepalive`. + /// [`Handle::set_keepalive`]. /// /// This is the recommended method to use for waiting. pub fn wait_keepalive(self) -> Result<()> { diff --git a/src/lib.rs b/src/lib.rs index 22bd75e..6cf00bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,17 @@ //! This crate lets you connect to and interact with servers that implement the IMAP protocol ([RFC -//! 3501](https://tools.ietf.org/html/rfc3501)). After authenticating with the server, IMAP lets -//! you list, fetch, and search for e-mails, as well as monitor mailboxes for changes. +//! 3501](https://tools.ietf.org/html/rfc3501) and various extensions). After authenticating with +//! the server, IMAP lets you list, fetch, and search for e-mails, as well as monitor mailboxes for +//! changes. //! //! To connect, use the [`connect`] function. This gives you an unauthenticated [`Client`]. You can //! then use [`Client::login`] or [`Client::authenticate`] to perform username/password or //! challenge/response authentication respectively. This in turn gives you an authenticated //! [`Session`], which lets you access the mailboxes at the server. //! +//! The documentation within this crate borrows heavily from the various RFCs, but should not be +//! considered a complete reference. If anything is unclear, follow the links to the RFCs embedded +//! in the documentation for the various types and methods and read the raw text there! +//! //! Below is a basic client example. See the `examples/` directory for more. //! //! ```no_run @@ -40,7 +45,7 @@ //! }; //! //! // extract the message's body -//! let body = message.rfc822().expect("message did not have a body!"); +//! let body = message.body().expect("message did not have a body!"); //! let body = std::str::from_utf8(body) //! .expect("message was not valid utf-8") //! .to_string(); diff --git a/src/parse.rs b/src/parse.rs index 39d0005..ac6a315 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -122,21 +122,21 @@ pub fn parse_fetches( message: num, flags: vec![], uid: None, - rfc822_header: None, - rfc822: None, - body: None, + size: None, + fetch: attrs, }; - for attr in attrs { + // set some common fields eaglery + for attr in &fetch.fetch { use imap_proto::AttributeValue; match attr { AttributeValue::Flags(flags) => { - fetch.flags.extend(flags.into_iter().map(Flag::from)); + fetch + .flags + .extend(flags.into_iter().cloned().map(Flag::from)); } - AttributeValue::Uid(uid) => fetch.uid = Some(uid), - AttributeValue::Rfc822(rfc) => fetch.rfc822 = rfc, - AttributeValue::Rfc822Header(rfc) => fetch.rfc822_header = rfc, - AttributeValue::BodySection { data, .. } => fetch.body = data, + AttributeValue::Uid(uid) => fetch.uid = Some(*uid), + AttributeValue::Rfc822Size(sz) => fetch.size = Some(*sz), _ => {} } } @@ -149,6 +149,18 @@ pub fn parse_fetches( unsafe { parse_many(lines, f, unsolicited) } } +pub fn parse_expunge( + lines: Vec, + unsolicited: &mut mpsc::Sender, +) -> Result> { + let f = |resp| match resp { + Response::Expunge(id) => Ok(MapOrNot::Map(id)), + resp => Ok(MapOrNot::Not(resp)), + }; + + unsafe { parse_many(lines, f, unsolicited).map(|ids| ids.take()) } +} + pub fn parse_capabilities( lines: Vec, unsolicited: &mut mpsc::Sender, @@ -392,11 +404,13 @@ mod tests { assert_eq!(fetches[0].message, 24); assert_eq!(fetches[0].flags(), &[Flag::Seen]); assert_eq!(fetches[0].uid, Some(4827943)); - assert_eq!(fetches[0].rfc822(), None); + assert_eq!(fetches[0].body(), None); + assert_eq!(fetches[0].header(), None); assert_eq!(fetches[1].message, 25); assert_eq!(fetches[1].flags(), &[Flag::Seen]); assert_eq!(fetches[1].uid, None); - assert_eq!(fetches[1].rfc822(), None); + assert_eq!(fetches[1].body(), None); + assert_eq!(fetches[1].header(), None); } #[test] diff --git a/src/types/capabilities.rs b/src/types/capabilities.rs index 2d59e94..166bfa8 100644 --- a/src/types/capabilities.rs +++ b/src/types/capabilities.rs @@ -6,29 +6,25 @@ use std::hash::Hash; /// From [section 7.2.1 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-7.2.1). /// /// A list of capabilities that the server supports. -/// The capability list MUST include the atom "IMAP4rev1". +/// The capability list will include the atom "IMAP4rev1". /// -/// In addition, client and server implementations MUST implement the `STARTTLS`, `LOGINDISABLED`, -/// and `AUTH=PLAIN` (described in [IMAP-TLS](https://tools.ietf.org/html/rfc2595)) capabilities. -/// See the [Security Considerations section of the -/// RFC](https://tools.ietf.org/html/rfc3501#section-11) for important information. +/// In addition, all servers implement the `STARTTLS`, `LOGINDISABLED`, and `AUTH=PLAIN` (described +/// in [IMAP-TLS](https://tools.ietf.org/html/rfc2595)) capabilities. See the [Security +/// Considerations section of the RFC](https://tools.ietf.org/html/rfc3501#section-11) for +/// important information. /// /// A capability name which begins with `AUTH=` indicates that the server supports that particular /// authentication mechanism. /// /// The `LOGINDISABLED` capability indicates that the `LOGIN` command is disabled, and that the -/// server will respond with a [`Error::No`] response to any attempt to use the `LOGIN` command -/// even if the user name and password are valid. An IMAP client MUST NOT issue the `LOGIN` -/// command if the server advertises the `LOGINDISABLED` capability. +/// server will respond with a [`super::Error::No`] response to any attempt to use the `LOGIN` +/// command even if the user name and password are valid. An IMAP client MUST NOT issue the +/// `LOGIN` command if the server advertises the `LOGINDISABLED` capability. /// /// Other capability names indicate that the server supports an extension, revision, or amendment -/// to the IMAP4rev1 protocol. Server responses MUST conform to this document until the client -/// issues a command that uses the associated capability. -/// -/// Capability names MUST either begin with `X` or be standard or standards-track IMAP4rev1 -/// extensions, revisions, or amendments registered with IANA. A server MUST NOT offer -/// unregistered or non-standard capability names, unless such names are prefixed with -/// an `X`. +/// to the IMAP4rev1 protocol. Capability names either begin with `X` or they are standard or +/// standards-track [RFC 3501](https://tools.ietf.org/html/rfc3501) extensions, revisions, or +/// amendments registered with IANA. /// /// Client implementations SHOULD NOT require any capability name other than `IMAP4rev1`, and MUST /// ignore any unknown capability names. diff --git a/src/types/fetch.rs b/src/types/fetch.rs index 278bb0f..e343757 100644 --- a/src/types/fetch.rs +++ b/src/types/fetch.rs @@ -1,4 +1,5 @@ use super::{Flag, Seq, Uid}; +use imap_proto::types::{AttributeValue, Envelope, MessageSection, SectionPath}; /// An IMAP [`FETCH` response](https://tools.ietf.org/html/rfc3501#section-7.4.2) that contains /// data about a particular message. This response occurs as the result of a `FETCH` or `STORE` @@ -9,41 +10,113 @@ pub struct Fetch { pub message: Seq, /// A number expressing the unique identifier of the message. + /// Only present if `UID` was specified in the query argument to `FETCH` and the server + /// supports UIDs. pub uid: Option, + /// A number expressing the [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message. + /// Only present if `RFC822.SIZE` was specified in the query argument to `FETCH`. + pub size: Option, + // Note that none of these fields are *actually* 'static. Rather, they are tied to the lifetime // of the `ZeroCopy` that contains this `Name`. That's also why they can't be public -- we can // only return them with a lifetime tied to self. + pub(crate) fetch: Vec>, pub(crate) flags: Vec>, - pub(crate) rfc822_header: Option<&'static [u8]>, - pub(crate) rfc822: Option<&'static [u8]>, - pub(crate) body: Option<&'static [u8]>, } impl Fetch { /// A list of flags that are set for this message. - pub fn flags(&self) -> &[Flag] { + pub fn flags<'a>(&'a self) -> &'a [Flag<'a>] { &self.flags[..] } - /// The bytes that make up the RFC822 header of this message, if `RFC822.HEADER` was included - /// in the flags argument to `FETCH`. - pub fn rfc822_header(&self) -> Option<&[u8]> { - self.rfc822_header + /// The bytes that make up the header of this message, if `BODY[HEADER]`, `BODY.PEEK[HEADER]`, + /// or `RFC822.HEADER` was included in the `query` argument to `FETCH`. + pub fn header(&self) -> Option<&[u8]> { + self.fetch + .iter() + .filter_map(|av| match av { + AttributeValue::BodySection { + section: Some(SectionPath::Full(MessageSection::Header)), + data: Some(hdr), + .. + } + | AttributeValue::Rfc822Header(Some(hdr)) => Some(*hdr), + _ => None, + }) + .next() } - /// The entire body of this message, if `RFC822` was included in the flags argument to `FETCH`. - /// The bytes SHOULD be interpreted by the client according to the content transfer encoding, - /// body type, and subtype. - pub fn rfc822(&self) -> Option<&[u8]> { - self.rfc822 - } - - /// An [MIME-IMB](https://tools.ietf.org/html/rfc2045) representation of this message, included - /// if `BODY` was included in the flags argument to `FETCH`. See also the documentation for - /// `BODYSTRUCTURE` in the documentation for [`FETCH - /// responses`](https://tools.ietf.org/html/rfc3501#section-7.4.2). + /// The bytes that make up this message, included if `BODY[]` or `RFC822` was included in the + /// `query` argument to `FETCH`. The bytes SHOULD be interpreted by the client according to the + /// content transfer encoding, body type, and subtype. pub fn body(&self) -> Option<&[u8]> { - self.body + self.fetch + .iter() + .filter_map(|av| match av { + AttributeValue::BodySection { + section: None, + data: Some(body), + .. + } + | AttributeValue::Rfc822(Some(body)) => Some(*body), + _ => None, + }) + .next() + } + + /// The bytes that make up the text of this message, included if `BODY[TEXT]` or + /// `BODY.PEEK[TEXT]` was included in the `query` argument to `FETCH`. The bytes SHOULD be + /// interpreted by the client according to the content transfer encoding, body type, and + /// subtype. + pub fn text(&self) -> Option<&[u8]> { + // TODO: https://github.com/djc/tokio-imap/issues/32 + self.fetch + .iter() + .filter_map(|av| match av { + AttributeValue::BodySection { + section: Some(SectionPath::Full(MessageSection::Text)), + data: Some(body), + .. + } => Some(*body), + _ => None, + }) + .next() + } + + /// The envelope of this message, if `ENVELOPE` was included in the `query` argument to + /// `FETCH`. This is computed by the server by parsing the + /// [RFC-2822](https://tools.ietf.org/html/rfc2822) header into the component parts, defaulting + /// various fields as necessary. + /// + /// The full description of the format of the envelope is given in [RFC 3501 section + /// 7.4.2](https://tools.ietf.org/html/rfc3501#section-7.4.2). + pub fn envelope(&self) -> Option<&Envelope> { + self.fetch + .iter() + .filter_map(|av| match av { + AttributeValue::Envelope(env) => Some(&**env), + _ => None, + }) + .next() + } + + /// Extract the bytes that makes up the given `BOD[
]` of a `FETCH` response. + /// + /// See [section 7.4.2 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-7.4.2) for + /// details. + pub fn section(&self, path: SectionPath) -> Option<&[u8]> { + self.fetch + .iter() + .filter_map(|av| match av { + AttributeValue::BodySection { + section: Some(sp), + data: Some(data), + .. + } if sp == &path => Some(*data), + _ => None, + }) + .next() } } diff --git a/src/types/mod.rs b/src/types/mod.rs index b554c52..73b3f10 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -5,17 +5,17 @@ use std::borrow::Cow; /// From section [2.3.1.1 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.1.1). /// /// A 32-bit value assigned to each message, which when used with the unique identifier validity -/// value (see below) forms a 64-bit value that MUST NOT refer to any other message in the mailbox +/// value (see below) forms a 64-bit value that will not refer to any other message in the mailbox /// or any subsequent mailbox with the same name forever. Unique identifiers are assigned in a /// strictly ascending fashion in the mailbox; as each message is added to the mailbox it is /// assigned a higher UID than the message(s) which were added previously. Unlike message sequence /// numbers, unique identifiers are not necessarily contiguous. /// -/// The unique identifier of a message MUST NOT change during the session, and SHOULD NOT change -/// between sessions. Any change of unique identifiers between sessions MUST be detectable using -/// the `UIDVALIDITY` mechanism discussed below. Persistent unique identifiers are required for a -/// client to resynchronize its state from a previous session with the server (e.g., disconnected -/// or offline access clients); this is discussed further in +/// The unique identifier of a message will not change during the session, and will generally not +/// change between sessions. Any change of unique identifiers between sessions will be detectable +/// using the `UIDVALIDITY` mechanism discussed below. Persistent unique identifiers are required +/// for a client to resynchronize its state from a previous session with the server (e.g., +/// disconnected or offline access clients); this is discussed further in /// [`IMAP-DISC`](https://tools.ietf.org/html/rfc3501#ref-IMAP-DISC). /// /// Associated with every mailbox are two values which aid in unique identifier handling: the next @@ -23,9 +23,9 @@ use std::borrow::Cow; /// /// The next unique identifier value is the predicted value that will be assigned to a new message /// in the mailbox. Unless the unique identifier validity also changes (see below), the next -/// unique identifier value MUST have the following two characteristics. First, the next unique -/// identifier value MUST NOT change unless new messages are added to the mailbox; and second, the -/// next unique identifier value MUST change whenever new messages are added to the mailbox, even +/// unique identifier value will have the following two characteristics. First, the next unique +/// identifier value will not change unless new messages are added to the mailbox; and second, the +/// next unique identifier value will change whenever new messages are added to the mailbox, even /// if those new messages are subsequently expunged. /// /// > Note: The next unique identifier value is intended to provide a means for a client to @@ -37,17 +37,17 @@ use std::borrow::Cow; /// /// The unique identifier validity value is sent in a `UIDVALIDITY` response code in an `OK` /// untagged response at mailbox selection time. If unique identifiers from an earlier session fail -/// to persist in this session, the unique identifier validity value MUST be greater than the one +/// to persist in this session, the unique identifier validity value will be greater than the one /// used in the earlier session. /// -/// > Note: Ideally, unique identifiers SHOULD persist at all +/// > Note: Ideally, unique identifiers will persist at all /// > times. Although this specification recognizes that failure /// > to persist can be unavoidable in certain server /// > environments, it STRONGLY ENCOURAGES message store /// > implementation techniques that avoid this problem. For /// > example: /// > -/// > 1. Unique identifiers MUST be strictly ascending in the +/// > 1. Unique identifiers are strictly ascending in the /// > mailbox at all times. If the physical message store is /// > re-ordered by a non-IMAP agent, this requires that the /// > unique identifiers in the mailbox be regenerated, since @@ -82,7 +82,7 @@ pub type Uid = u32; /// From section [2.3.1.2 of RFC 3501](https://tools.ietf.org/html/rfc3501#section-2.3.1.2). /// /// A relative position from 1 to the number of messages in the mailbox. -/// This position MUST be ordered by ascending unique identifier. As +/// This position is ordered by ascending unique identifier. As /// each new message is added, it is assigned a message sequence number /// that is 1 higher than the number of messages in the mailbox before /// that new message was added. @@ -139,7 +139,7 @@ pub enum Flag<'a> { /// not see `\Recent` set for this message. This flag can not be altered by the client. /// /// If it is not possible to determine whether or not this session is the first session to be - /// notified about a message, then that message SHOULD be considered recent. + /// notified about a message, then that message will generally be considered recent. /// /// If multiple connections have the same mailbox selected simultaneously, it is undefined /// which of these connections will see newly-arrived messages with `\Recent` set and which @@ -304,6 +304,14 @@ impl ZeroCopy { derived: derive(static_owned_ref)?, }) } + + /// Take out the derived value of this `ZeroCopy`. + /// + /// Only safe if `D` contains no references into the underlying input stream (i.e., the `owned` + /// passed to `ZeroCopy::new`). + pub(crate) unsafe fn take(self) -> D { + self.derived + } } use super::error::Error; diff --git a/src/types/name.rs b/src/types/name.rs index 076b5aa..51d2101 100644 --- a/src/types/name.rs +++ b/src/types/name.rs @@ -74,17 +74,17 @@ impl Name { /// The hierarchy delimiter is a character used to delimit levels of hierarchy in a mailbox /// name. A client can use it to create child mailboxes, and to search higher or lower levels - /// of naming hierarchy. All children of a top-level hierarchy node MUST use the same + /// of naming hierarchy. All children of a top-level hierarchy node use the same /// separator character. A NIL hierarchy delimiter means that no hierarchy exists; the name is /// a "flat" name. pub fn delimiter(&self) -> &str { self.delimiter } - /// The name represents an unambiguous left-to-right hierarchy, and MUST be valid for use as a + /// The name represents an unambiguous left-to-right hierarchy, and are valid for use as a /// reference in `LIST` and `LSUB` commands. Unless [`NameAttribute::NoSelect`] is indicated, - /// the name MUST also be valid as an argument for commands, such as `SELECT`, that accept - /// mailbox names. + /// the name is also valid as an argument for commands, such as `SELECT`, that accept mailbox + /// names. pub fn name(&self) -> &str { self.name }