From 0c3ce7943dbaae422211ea12bd8c4a7d8e7d6fa9 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Mon, 18 May 2020 14:12:10 -0400 Subject: [PATCH 01/21] Expose unilateral mailbox flag changes This is a backwards incompatible change, since it adds a variant to a public enum. --- src/parse.rs | 11 +++++++++-- src/types/mod.rs | 9 +++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/parse.rs b/src/parse.rs index b2dc002..b7418a9 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -322,8 +322,15 @@ fn handle_unilateral<'a>( Response::MailboxData(MailboxDatum::Recent(n)) => { unsolicited.send(UnsolicitedResponse::Recent(n)).unwrap(); } - Response::MailboxData(MailboxDatum::Flags(_)) => { - // TODO: next breaking change: + Response::MailboxData(MailboxDatum::Flags(flags)) => { + unsolicited + .send(UnsolicitedResponse::Flags( + flags + .into_iter() + .map(|s| Flag::from(s.to_string())) + .collect(), + )) + .unwrap(); } Response::MailboxData(MailboxDatum::Exists(n)) => { unsolicited.send(UnsolicitedResponse::Exists(n)).unwrap(); diff --git a/src/types/mod.rs b/src/types/mod.rs index 4a789e5..9757680 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -262,6 +262,15 @@ pub enum UnsolicitedResponse { /// sequence numbers 9, 8, 7, 6, and 5. // TODO: the spec doesn't seem to say anything about when these may be received as unsolicited? Expunge(Seq), + + /// An unsolicited [`FLAGS` response](https://tools.ietf.org/html/rfc3501#section-7.2.6) that + /// identifies the flags (at a minimum, the system-defined flags) that are applicable in the + /// mailbox. Flags other than the system flags can also exist, depending on server + /// implementation. + /// + /// See [`Flag`] for details. + // TODO: the spec doesn't seem to say anything about when these may be received as unsolicited? + Flags(Vec>), } /// This type wraps an input stream and a type that was constructed by parsing that input stream, From cf4aed569b62dfcc9dea0b54778bba4886de06d3 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Mon, 18 May 2020 14:12:34 -0400 Subject: [PATCH 02/21] Make UnsolicitedResponse non_exhaustive That way, as we discover additional unilateral responses in the future, we won't have to make a breaking change to add them. --- src/types/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/mod.rs b/src/types/mod.rs index 9757680..8a13903 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -211,6 +211,7 @@ pub use imap_proto::StatusAttribute; /// Note that `Recent`, `Exists` and `Expunge` responses refer to the currently `SELECT`ed folder, /// so the user must take care when interpreting these. #[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum UnsolicitedResponse { /// An unsolicited [`STATUS response`](https://tools.ietf.org/html/rfc3501#section-7.2.4). Status { From 02ce8cb5118e77e0ff6287418074a954ba9acfc6 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Mon, 18 May 2020 14:28:08 -0400 Subject: [PATCH 03/21] Bump MSRV for non_exhaustive --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5cc11d8..f9e126c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -47,12 +47,12 @@ jobs: - job: msrv pool: vmImage: ubuntu-latest - displayName: "Minimum supported Rust version: 1.36.0" + displayName: "Minimum supported Rust version: 1.40.0" dependsOn: [] steps: - template: install-rust.yml@templates parameters: - rust: 1.36.0 + rust: 1.40.0 - script: cargo check displayName: cargo check - job: integration From 8be583a9f71371d970cdbd03ce87c578dccda6b6 Mon Sep 17 00:00:00 2001 From: mordak Date: Mon, 7 Dec 2020 23:43:19 -0500 Subject: [PATCH 04/21] CI: Only install components when needed. (#173) --- azure-pipelines.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index de1917f..c0b9b84 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,9 +25,6 @@ jobs: - template: install-rust.yml@templates parameters: rust: $(rust) - components: - - rustfmt - - clippy - script: cargo check --all-targets displayName: cargo check - script: cargo test --examples @@ -36,10 +33,16 @@ jobs: displayName: cargo test --doc - script: cargo test --lib displayName: cargo test --lib - - script: cargo fmt --all -- --check + - script: | + set -e + rustup component add rustfmt + cargo fmt --all -- --check displayName: cargo fmt --check condition: and(eq( variables['rust'], 'beta' ), eq( variables['Agent.OS'], 'Linux' )) - - script: cargo clippy -- -D warnings + - script: | + set -e + rustup component add clippy + cargo clippy -- -D warnings displayName: cargo clippy condition: and(eq( variables['rust'], 'beta' ), eq( variables['Agent.OS'], 'Linux' )) # This represents the minimum Rust version supported. From ee56c8e42ba2fafd0270d16c248eb2dce3899248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Wed, 16 Dec 2020 00:25:38 +0800 Subject: [PATCH 05/21] feat: allow setting sent date on APPEND (#174) Fixes #60 --- src/client.rs | 34 ++++++++++++++++++++++++- tests/imap_integration.rs | 53 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index a47c401..26a1432 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,6 @@ use base64; use bufstream::BufStream; +use chrono::{DateTime, FixedOffset}; #[cfg(feature = "tls")] use native_tls::{TlsConnector, TlsStream}; use nom; @@ -1096,6 +1097,32 @@ impl Session { mailbox: S, content: B, flags: &[Flag<'_>], + ) -> Result<()> { + self.append_with_flags_and_date(mailbox, content, flags, None) + } + + /// The [`APPEND` command](https://tools.ietf.org/html/rfc3501#section-6.3.11) can take + /// an optional FLAGS parameter to set the flags on the new message. + /// + /// > If a flag parenthesized list is specified, the flags SHOULD be set + /// > in the resulting message; otherwise, the flag list of the + /// > resulting message is set to empty by default. In either case, the + /// > Recent flag is also set. + /// + /// The [`\Recent` flag](https://tools.ietf.org/html/rfc3501#section-2.3.2) is not + /// allowed as an argument to `APPEND` and will be filtered out if present in `flags`. + /// + /// Pass a date in order to set the date that the message was originally sent. + /// + /// > If a date-time is specified, the internal date SHOULD be set in + /// > the resulting message; otherwise, the internal date of the + /// > resulting message is set to the current date and time by default. + pub fn append_with_flags_and_date, B: AsRef<[u8]>>( + &mut self, + mailbox: S, + content: B, + flags: &[Flag<'_>], + date: impl Into>>, ) -> Result<()> { let content = content.as_ref(); let flagstr = flags @@ -1104,11 +1131,16 @@ impl Session { .map(|f| f.to_string()) .collect::>() .join(" "); + let datestr = match date.into() { + Some(date) => format!(" \"{}\"", date.format("%d-%h-%Y %T %z")), + None => "".to_string(), + }; self.run_command(&format!( - "APPEND \"{}\" ({}) {{{}}}", + "APPEND \"{}\" ({}){} {{{}}}", mailbox.as_ref(), flagstr, + datestr, content.len() ))?; let mut v = Vec::new(); diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index 0a8bf76..b37f3f3 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -1,8 +1,10 @@ +extern crate chrono; extern crate imap; extern crate lettre; extern crate lettre_email; extern crate native_tls; +use chrono::{FixedOffset, TimeZone}; use lettre::Transport; use std::net::TcpStream; @@ -330,3 +332,54 @@ fn append_with_flags() { let inbox = c.search("ALL").unwrap(); assert_eq!(inbox.len(), 0); } + +#[test] +fn append_with_flags_and_date() { + use imap::types::Flag; + + let to = "inbox-append3@localhost"; + + // make a message to append + let e: lettre::SendableEmail = lettre_email::Email::builder() + .from("sender@localhost") + .to(to) + .subject("My third e-mail") + .text("Hello world") + .build() + .unwrap() + .into(); + + // connect + let mut c = session(to); + let mbox = "INBOX"; + c.select(mbox).unwrap(); + // append + let flags: &[Flag] = &[Flag::Seen, Flag::Flagged]; + let date = FixedOffset::east(8 * 3600) + .ymd(2020, 12, 13) + .and_hms(13, 36, 36); + c.append_with_flags_and_date(mbox, e.message_to_string().unwrap(), flags, Some(date)) + .unwrap(); + + // now we should see the e-mail! + let inbox = c.uid_search("ALL").unwrap(); + // and the one message should have the first message sequence number + assert_eq!(inbox.len(), 1); + let uid = inbox.into_iter().next().unwrap(); + + // fetch the e-mail + let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap(); + assert_eq!(fetch.len(), 1); + let fetch = &fetch[0]; + assert_eq!(fetch.uid, Some(uid)); + assert_eq!(fetch.internal_date(), Some(date)); + + // and let's delete it to clean up + c.uid_store(format!("{}", uid), "+FLAGS (\\Deleted)") + .unwrap(); + c.expunge().unwrap(); + + // the e-mail should be gone now + let inbox = c.search("ALL").unwrap(); + assert_eq!(inbox.len(), 0); +} From 3386c267116605c53a402e98e72948ecf277f9a1 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Tue, 15 Dec 2020 08:29:57 -0800 Subject: [PATCH 06/21] Bump version for append_with_date --- CHANGELOG.md | 14 +++++++++++++- Cargo.toml | 6 +++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b48ebcb..ebfdbe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +## [2.4.0] - 2020-12-15 +### Added + + - `append_with_flags_and_date` (#174) + +## [2.3.0] - 2020-08-23 +### Added + + - `append_with_flags` (#171) + ## [2.2.0] - 2020-07-27 ### Added @@ -24,5 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - MSRV increased - Better documentation of server greeting handling (#168) -[Unreleased]: https://github.com/jonhoo/rust-imap/compare/v2.2.0...HEAD +[Unreleased]: https://github.com/jonhoo/rust-imap/compare/v2.4.0...HEAD +[2.4.0]: https://github.com/jonhoo/rust-imap/compare/v2.2.0...v2.4.0 +[2.3.0]: https://github.com/jonhoo/rust-imap/compare/v2.2.0...v2.3.0 [2.2.0]: https://github.com/jonhoo/rust-imap/compare/v2.1.2...v2.2.0 diff --git a/Cargo.toml b/Cargo.toml index e4f5388..1dd1d68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "imap" -version = "2.3.0" +version = "2.4.0" authors = ["Matt McCoy ", "Jon Gjengset "] documentation = "https://docs.rs/imap/" @@ -31,14 +31,14 @@ regex = "1.0" bufstream = "0.1" imap-proto = "0.10.0" nom = "5.0" -base64 = "0.12" +base64 = "0.13" chrono = "0.4" lazy_static = "1.4" [dev-dependencies] lettre = "0.9" lettre_email = "0.9" -rustls-connector = "0.12.0" +rustls-connector = "0.13.0" [[example]] name = "basic" From 469d338d5da0cb8c3996614b42677428abbb9aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Thu, 17 Dec 2020 00:20:48 +0800 Subject: [PATCH 07/21] refactor: combine all append_* + introduce AppendOptions --- src/client.rs | 54 ++++++++++++++++++--------------------- tests/imap_integration.rs | 25 ++++++++++++++---- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/client.rs b/src/client.rs index 26a1432..167ea70 100644 --- a/src/client.rs +++ b/src/client.rs @@ -84,6 +84,15 @@ pub struct Connection { pub greeting_read: bool, } +/// A set of options for the append command +#[derive(Default)] +pub struct AppendOptions<'a> { + /// Optional list of flags + pub flags: Option<&'a [Flag<'a>]>, + /// Optional internal date + pub date: Option>, +} + // `Deref` instances are so we can make use of the same underlying primitives in `Client` and // `Session` impl Deref for Client { @@ -1078,10 +1087,9 @@ impl Session { /// 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, B: AsRef<[u8]>>(&mut self, mailbox: S, content: B) -> Result<()> { - self.append_with_flags(mailbox, content, &[]) - } - + /// + /// -- TODO merge docs possibly move to AppendOptions + /// /// The [`APPEND` command](https://tools.ietf.org/html/rfc3501#section-6.3.11) can take /// an optional FLAGS parameter to set the flags on the new message. /// @@ -1092,48 +1100,36 @@ impl Session { /// /// The [`\Recent` flag](https://tools.ietf.org/html/rfc3501#section-2.3.2) is not /// allowed as an argument to `APPEND` and will be filtered out if present in `flags`. - pub fn append_with_flags, B: AsRef<[u8]>>( - &mut self, - mailbox: S, - content: B, - flags: &[Flag<'_>], - ) -> Result<()> { - self.append_with_flags_and_date(mailbox, content, flags, None) - } - - /// The [`APPEND` command](https://tools.ietf.org/html/rfc3501#section-6.3.11) can take - /// an optional FLAGS parameter to set the flags on the new message. /// - /// > If a flag parenthesized list is specified, the flags SHOULD be set - /// > in the resulting message; otherwise, the flag list of the - /// > resulting message is set to empty by default. In either case, the - /// > Recent flag is also set. - /// - /// The [`\Recent` flag](https://tools.ietf.org/html/rfc3501#section-2.3.2) is not - /// allowed as an argument to `APPEND` and will be filtered out if present in `flags`. + /// -- TODO merge docs possibly move to AppendOptions /// /// Pass a date in order to set the date that the message was originally sent. /// /// > If a date-time is specified, the internal date SHOULD be set in /// > the resulting message; otherwise, the internal date of the /// > resulting message is set to the current date and time by default. - pub fn append_with_flags_and_date, B: AsRef<[u8]>>( + pub fn append<'a, S: AsRef, B: AsRef<[u8]>>( &mut self, mailbox: S, content: B, - flags: &[Flag<'_>], - date: impl Into>>, + options: impl Into>>, ) -> Result<()> { let content = content.as_ref(); - let flagstr = flags + let options_ = options.into().unwrap_or(AppendOptions::default()); + + let flagstr = options_ + .flags + .unwrap_or(&[]) .iter() .filter(|f| **f != Flag::Recent) .map(|f| f.to_string()) .collect::>() .join(" "); - let datestr = match date.into() { - Some(date) => format!(" \"{}\"", date.format("%d-%h-%Y %T %z")), - None => "".to_string(), + + let datestr = if let Some(date) = options_.date { + format!(" \"{}\"", date.format("%d-%h-%Y %T %z")) + } else { + "".to_string() }; self.run_command(&format!( diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index b37f3f3..18dd3e8 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -253,7 +253,8 @@ fn append() { let mbox = "INBOX"; c.select(mbox).unwrap(); //append - c.append(mbox, e.message_to_string().unwrap()).unwrap(); + c.append(mbox, e.message_to_string().unwrap(), None) + .unwrap(); // now we should see the e-mail! let inbox = c.uid_search("ALL").unwrap(); @@ -301,8 +302,15 @@ fn append_with_flags() { c.select(mbox).unwrap(); //append let flags: &[Flag] = &[Flag::Seen, Flag::Flagged]; - c.append_with_flags(mbox, e.message_to_string().unwrap(), flags) - .unwrap(); + c.append( + mbox, + e.message_to_string().unwrap(), + imap::AppendOptions { + flags: Some(flags), + date: None, + }, + ) + .unwrap(); // now we should see the e-mail! let inbox = c.uid_search("ALL").unwrap(); @@ -358,8 +366,15 @@ fn append_with_flags_and_date() { let date = FixedOffset::east(8 * 3600) .ymd(2020, 12, 13) .and_hms(13, 36, 36); - c.append_with_flags_and_date(mbox, e.message_to_string().unwrap(), flags, Some(date)) - .unwrap(); + c.append( + mbox, + e.message_to_string().unwrap(), + imap::AppendOptions { + flags: Some(flags), + date: Some(date), + }, + ) + .unwrap(); // now we should see the e-mail! let inbox = c.uid_search("ALL").unwrap(); From 24445c5c652b6098d8c859411da74264105506dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Thu, 17 Dec 2020 00:41:26 +0800 Subject: [PATCH 08/21] feat: add AppendCmd builder --- src/client.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/client.rs b/src/client.rs index 167ea70..1e33ab5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -93,6 +93,41 @@ pub struct AppendOptions<'a> { pub date: Option>, } +/// A builder for the append command +#[derive(Default)] +pub struct AppendCmd<'a> { + flags: Vec<&'a Flag<'a>>, + date: Option>, +} + +impl<'a> AppendCmd<'a> { + /// Create a new AppendCmd builder + pub fn create() -> Self { + Self::default() + } + + /// Append a flag + pub fn flag(&mut self, flag: &'a Flag<'a>) -> &mut Self { + self.flags.push(flag); + self + } + + /// Set the internal date + pub fn internal_date(&mut self, date: DateTime) -> &mut Self { + self.date = Some(date); + self + } +} + +impl<'a> Into> for AppendCmd<'a> { + fn into(self) -> AppendOptions<'a> { + AppendOptions { + flags: Some(&self.flags[..]), + date: self.date, + } + } +} + // `Deref` instances are so we can make use of the same underlying primitives in `Client` and // `Session` impl Deref for Client { From cdf320fb0c87009bd3856220d263b62180637b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Thu, 17 Dec 2020 11:02:48 +0800 Subject: [PATCH 09/21] refactor: use AppendCmd instead of AppendOptions --- src/client.rs | 102 ++++++++++++++++---------------------- tests/imap_integration.rs | 32 +++++------- 2 files changed, 57 insertions(+), 77 deletions(-) diff --git a/src/client.rs b/src/client.rs index 1e33ab5..6bbe119 100644 --- a/src/client.rs +++ b/src/client.rs @@ -84,28 +84,16 @@ pub struct Connection { pub greeting_read: bool, } -/// A set of options for the append command -#[derive(Default)] -pub struct AppendOptions<'a> { - /// Optional list of flags - pub flags: Option<&'a [Flag<'a>]>, - /// Optional internal date - pub date: Option>, -} - /// A builder for the append command -#[derive(Default)] -pub struct AppendCmd<'a> { +pub struct AppendCmd<'a, T: Read + Write> { + session: &'a Session, + content: &'a [u8], + mailbox: &'a str, flags: Vec<&'a Flag<'a>>, date: Option>, } -impl<'a> AppendCmd<'a> { - /// Create a new AppendCmd builder - pub fn create() -> Self { - Self::default() - } - +impl<'a, T: Read + Write> AppendCmd<'a, T> { /// Append a flag pub fn flag(&mut self, flag: &'a Flag<'a>) -> &mut Self { self.flags.push(flag); @@ -117,14 +105,40 @@ impl<'a> AppendCmd<'a> { self.date = Some(date); self } -} -impl<'a> Into> for AppendCmd<'a> { - fn into(self) -> AppendOptions<'a> { - AppendOptions { - flags: Some(&self.flags[..]), - date: self.date, + /// Run command when set up + #[must_use] + pub fn run(&self) -> Result<()> { + let flagstr = self + .flags + .into_iter() + .filter(|f| **f != Flag::Recent) + .map(|f| f.to_string()) + .collect::>() + .join(" "); + + let datestr = if let Some(date) = self.date { + format!(" \"{}\"", date.format("%d-%h-%Y %T %z")) + } else { + "".to_string() + }; + + self.session.run_command(&format!( + "APPEND \"{}\" ({}){} {{{}}}", + self.mailbox, + flagstr, + datestr, + self.content.len() + ))?; + let mut v = Vec::new(); + self.session.readline(&mut v)?; + if !v.starts_with(b"+") { + return Err(Error::Append); } + self.session.stream.write_all(self.content)?; + self.session.stream.write_all(b"\r\n")?; + self.session.stream.flush()?; + self.session.read_response().map(|_| ()) } } @@ -1147,42 +1161,14 @@ impl Session { &mut self, mailbox: S, content: B, - options: impl Into>>, - ) -> Result<()> { - let content = content.as_ref(); - let options_ = options.into().unwrap_or(AppendOptions::default()); - - let flagstr = options_ - .flags - .unwrap_or(&[]) - .iter() - .filter(|f| **f != Flag::Recent) - .map(|f| f.to_string()) - .collect::>() - .join(" "); - - let datestr = if let Some(date) = options_.date { - format!(" \"{}\"", date.format("%d-%h-%Y %T %z")) - } else { - "".to_string() - }; - - self.run_command(&format!( - "APPEND \"{}\" ({}){} {{{}}}", - mailbox.as_ref(), - flagstr, - datestr, - content.len() - ))?; - let mut v = Vec::new(); - self.readline(&mut v)?; - if !v.starts_with(b"+") { - return Err(Error::Append); + ) -> AppendCmd<'a, T> { + AppendCmd { + session: &self, + content: content.as_ref(), + mailbox: mailbox.as_ref(), + flags: Vec::new(), + date: None, } - self.stream.write_all(content)?; - self.stream.write_all(b"\r\n")?; - self.stream.flush()?; - self.read_response().map(|_| ()) } /// The [`SEARCH` command](https://tools.ietf.org/html/rfc3501#section-6.4.4) searches the diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index 18dd3e8..abecb0f 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -253,7 +253,8 @@ fn append() { let mbox = "INBOX"; c.select(mbox).unwrap(); //append - c.append(mbox, e.message_to_string().unwrap(), None) + c.append(mbox, e.message_to_string().unwrap()) + .run() .unwrap(); // now we should see the e-mail! @@ -302,15 +303,11 @@ fn append_with_flags() { c.select(mbox).unwrap(); //append let flags: &[Flag] = &[Flag::Seen, Flag::Flagged]; - c.append( - mbox, - e.message_to_string().unwrap(), - imap::AppendOptions { - flags: Some(flags), - date: None, - }, - ) - .unwrap(); + c.append(mbox, e.message_to_string().unwrap()) + .flag(Flag::Seen) + .flag(Flag::Flagged) + .run() + .unwrap(); // now we should see the e-mail! let inbox = c.uid_search("ALL").unwrap(); @@ -366,15 +363,12 @@ fn append_with_flags_and_date() { let date = FixedOffset::east(8 * 3600) .ymd(2020, 12, 13) .and_hms(13, 36, 36); - c.append( - mbox, - e.message_to_string().unwrap(), - imap::AppendOptions { - flags: Some(flags), - date: Some(date), - }, - ) - .unwrap(); + c.append(mbox, e.message_to_string().unwrap()) + .flag(Flag::Seen) + .flag(Flag::Flagged) + .internal_date(date) + .run() + .unwrap(); // now we should see the e-mail! let inbox = c.uid_search("ALL").unwrap(); From e6341ccfc0af9e617f15b1f59545ad3312bbe599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Thu, 17 Dec 2020 11:37:01 +0800 Subject: [PATCH 10/21] fix: pass session as &mut --- src/client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index 6bbe119..c5cfc82 100644 --- a/src/client.rs +++ b/src/client.rs @@ -86,7 +86,7 @@ pub struct Connection { /// A builder for the append command pub struct AppendCmd<'a, T: Read + Write> { - session: &'a Session, + session: &'a mut Session, content: &'a [u8], mailbox: &'a str, flags: Vec<&'a Flag<'a>>, @@ -1163,7 +1163,7 @@ impl Session { content: B, ) -> AppendCmd<'a, T> { AppendCmd { - session: &self, + session: self, content: content.as_ref(), mailbox: mailbox.as_ref(), flags: Vec::new(), From b7bc84297979b7ad3f48e33300a149b406e8fb2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Sun, 20 Dec 2020 13:03:54 +0800 Subject: [PATCH 11/21] fix: correct lifetimes and types for append --- src/client.rs | 19 ++++++++----------- tests/imap_integration.rs | 7 +++---- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/client.rs b/src/client.rs index c5cfc82..4fc4c25 100644 --- a/src/client.rs +++ b/src/client.rs @@ -89,13 +89,13 @@ pub struct AppendCmd<'a, T: Read + Write> { session: &'a mut Session, content: &'a [u8], mailbox: &'a str, - flags: Vec<&'a Flag<'a>>, + flags: Vec>, date: Option>, } impl<'a, T: Read + Write> AppendCmd<'a, T> { /// Append a flag - pub fn flag(&mut self, flag: &'a Flag<'a>) -> &mut Self { + pub fn flag(&mut self, flag: Flag<'a>) -> &mut Self { self.flags.push(flag); self } @@ -108,11 +108,12 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> { /// Run command when set up #[must_use] - pub fn run(&self) -> Result<()> { + pub fn run(&mut self) -> Result<()> { let flagstr = self .flags + .clone() .into_iter() - .filter(|f| **f != Flag::Recent) + .filter(|f| *f != Flag::Recent) .map(|f| f.to_string()) .collect::>() .join(" "); @@ -1157,15 +1158,11 @@ impl Session { /// > If a date-time is specified, the internal date SHOULD be set in /// > the resulting message; otherwise, the internal date of the /// > resulting message is set to the current date and time by default. - pub fn append<'a, S: AsRef, B: AsRef<[u8]>>( - &mut self, - mailbox: S, - content: B, - ) -> AppendCmd<'a, T> { + pub fn append<'a>(&'a mut self, mailbox: &'a str, content: &'a [u8]) -> AppendCmd<'a, T> { AppendCmd { session: self, - content: content.as_ref(), - mailbox: mailbox.as_ref(), + content, + mailbox, flags: Vec::new(), date: None, } diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index abecb0f..b32a0c0 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -253,7 +253,7 @@ fn append() { let mbox = "INBOX"; c.select(mbox).unwrap(); //append - c.append(mbox, e.message_to_string().unwrap()) + c.append(mbox, e.message_to_string().unwrap().as_bytes()) .run() .unwrap(); @@ -303,7 +303,7 @@ fn append_with_flags() { c.select(mbox).unwrap(); //append let flags: &[Flag] = &[Flag::Seen, Flag::Flagged]; - c.append(mbox, e.message_to_string().unwrap()) + c.append(mbox, e.message_to_string().unwrap().as_bytes()) .flag(Flag::Seen) .flag(Flag::Flagged) .run() @@ -359,11 +359,10 @@ fn append_with_flags_and_date() { let mbox = "INBOX"; c.select(mbox).unwrap(); // append - let flags: &[Flag] = &[Flag::Seen, Flag::Flagged]; let date = FixedOffset::east(8 * 3600) .ymd(2020, 12, 13) .and_hms(13, 36, 36); - c.append(mbox, e.message_to_string().unwrap()) + c.append(mbox, e.message_to_string().unwrap().as_bytes()) .flag(Flag::Seen) .flag(Flag::Flagged) .internal_date(date) From 5053cfbb3ec2037e0cc59684e24d31499759f1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Sun, 20 Dec 2020 13:04:31 +0800 Subject: [PATCH 12/21] feat: add 'flags' method to add multiple flags at once --- src/client.rs | 6 ++++++ tests/imap_integration.rs | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index 4fc4c25..74127ef 100644 --- a/src/client.rs +++ b/src/client.rs @@ -100,6 +100,12 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> { self } + /// Append an array of flags + pub fn flags(&mut self, flags: &'a [Flag<'a>]) -> &mut Self { + self.flags.append(&mut flags.to_vec()); + self + } + /// Set the internal date pub fn internal_date(&mut self, date: DateTime) -> &mut Self { self.date = Some(date); diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index b32a0c0..536efd2 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -304,8 +304,7 @@ fn append_with_flags() { //append let flags: &[Flag] = &[Flag::Seen, Flag::Flagged]; c.append(mbox, e.message_to_string().unwrap().as_bytes()) - .flag(Flag::Seen) - .flag(Flag::Flagged) + .flags(flags) .run() .unwrap(); From 74ef623fc589bc20ce1773f1ab9ef4729fe6729d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Sun, 20 Dec 2020 13:09:57 +0800 Subject: [PATCH 13/21] docs: move flag + date documentation to AppendCmd --- src/client.rs | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/client.rs b/src/client.rs index 74127ef..aa5623f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -94,7 +94,16 @@ pub struct AppendCmd<'a, T: Read + Write> { } impl<'a, T: Read + Write> AppendCmd<'a, T> { - /// Append a flag + /// The [`APPEND` command](https://tools.ietf.org/html/rfc3501#section-6.3.11) can take + /// an optional FLAGS parameter to set the flags on the new message. + /// + /// > If a flag parenthesized list is specified, the flags SHOULD be set + /// > in the resulting message; otherwise, the flag list of the + /// > resulting message is set to empty by default. In either case, the + /// > Recent flag is also set. + /// + /// The [`\Recent` flag](https://tools.ietf.org/html/rfc3501#section-2.3.2) is not + /// allowed as an argument to `APPEND` and will be filtered out if present in `flags`. pub fn flag(&mut self, flag: Flag<'a>) -> &mut Self { self.flags.push(flag); self @@ -106,14 +115,18 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> { self } - /// Set the internal date + /// Pass a date in order to set the date that the message was originally sent. + /// + /// > If a date-time is specified, the internal date SHOULD be set in + /// > the resulting message; otherwise, the internal date of the + /// > resulting message is set to the current date and time by default. pub fn internal_date(&mut self, date: DateTime) -> &mut Self { self.date = Some(date); self } - /// Run command when set up - #[must_use] + /// Run command + #[must_use = "always run a command once options are configured"] pub fn run(&mut self) -> Result<()> { let flagstr = self .flags @@ -1144,26 +1157,6 @@ impl Session { /// `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. /// - /// -- TODO merge docs possibly move to AppendOptions - /// - /// The [`APPEND` command](https://tools.ietf.org/html/rfc3501#section-6.3.11) can take - /// an optional FLAGS parameter to set the flags on the new message. - /// - /// > If a flag parenthesized list is specified, the flags SHOULD be set - /// > in the resulting message; otherwise, the flag list of the - /// > resulting message is set to empty by default. In either case, the - /// > Recent flag is also set. - /// - /// The [`\Recent` flag](https://tools.ietf.org/html/rfc3501#section-2.3.2) is not - /// allowed as an argument to `APPEND` and will be filtered out if present in `flags`. - /// - /// -- TODO merge docs possibly move to AppendOptions - /// - /// Pass a date in order to set the date that the message was originally sent. - /// - /// > If a date-time is specified, the internal date SHOULD be set in - /// > the resulting message; otherwise, the internal date of the - /// > resulting message is set to the current date and time by default. pub fn append<'a>(&'a mut self, mailbox: &'a str, content: &'a [u8]) -> AppendCmd<'a, T> { AppendCmd { session: self, From 19af971c9a435bcb4f9d389b0deb33ac1d62249c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Mon, 21 Dec 2020 10:37:18 +0800 Subject: [PATCH 14/21] refactor: use extend instead of append Co-authored-by: Jon Gjengset --- src/client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index aa5623f..e7833e4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -110,8 +110,8 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> { } /// Append an array of flags - pub fn flags(&mut self, flags: &'a [Flag<'a>]) -> &mut Self { - self.flags.append(&mut flags.to_vec()); + pub fn flags(&mut self, flags: impl IntoIterator>) -> &mut Self { + self.flags.extend(flags); self } From 029da6fd5226c1aa607881a66907a8cfc4878325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Mon, 21 Dec 2020 11:19:42 +0800 Subject: [PATCH 15/21] refactor: move must_use to AppendCmd --- src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index e7833e4..5d4ddd4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -85,6 +85,7 @@ pub struct Connection { } /// A builder for the append command +#[must_use] pub struct AppendCmd<'a, T: Read + Write> { session: &'a mut Session, content: &'a [u8], @@ -126,7 +127,6 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> { } /// Run command - #[must_use = "always run a command once options are configured"] pub fn run(&mut self) -> Result<()> { let flagstr = self .flags From 8cd8a210085e6d86ec3a3859b96dbcf149a9094f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Mon, 21 Dec 2020 11:21:22 +0800 Subject: [PATCH 16/21] refactor: rename run to finish --- src/client.rs | 2 +- tests/imap_integration.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client.rs b/src/client.rs index 5d4ddd4..8b94376 100644 --- a/src/client.rs +++ b/src/client.rs @@ -127,7 +127,7 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> { } /// Run command - pub fn run(&mut self) -> Result<()> { + pub fn finish(&mut self) -> Result<()> { let flagstr = self .flags .clone() diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index 536efd2..9d443cd 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -254,7 +254,7 @@ fn append() { c.select(mbox).unwrap(); //append c.append(mbox, e.message_to_string().unwrap().as_bytes()) - .run() + .finish() .unwrap(); // now we should see the e-mail! @@ -305,7 +305,7 @@ fn append_with_flags() { let flags: &[Flag] = &[Flag::Seen, Flag::Flagged]; c.append(mbox, e.message_to_string().unwrap().as_bytes()) .flags(flags) - .run() + .finish() .unwrap(); // now we should see the e-mail! @@ -365,7 +365,7 @@ fn append_with_flags_and_date() { .flag(Flag::Seen) .flag(Flag::Flagged) .internal_date(date) - .run() + .finish() .unwrap(); // now we should see the e-mail! From b2f2e297c2f7d9cf04005bf5c0a76f923b1d4140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Mon, 21 Dec 2020 11:21:40 +0800 Subject: [PATCH 17/21] docs: improve docs --- src/client.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index 8b94376..0637514 100644 --- a/src/client.rs +++ b/src/client.rs @@ -110,7 +110,7 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> { self } - /// Append an array of flags + /// Set multiple flags at once. pub fn flags(&mut self, flags: impl IntoIterator>) -> &mut Self { self.flags.extend(flags); self @@ -126,7 +126,10 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> { self } - /// Run command + /// Finishes up the command and executes it. + /// + /// Note: be sure to set flags and optional date before you + /// finish the command. pub fn finish(&mut self) -> Result<()> { let flagstr = self .flags From 80f54b1e81e339e67bcebe1edc276402056b0679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B8cker-Larsen?= Date: Tue, 22 Dec 2020 20:31:55 +0800 Subject: [PATCH 18/21] test: correct test for append_with_flags --- tests/imap_integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index 9d443cd..997b2f5 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -302,7 +302,7 @@ fn append_with_flags() { let mbox = "INBOX"; c.select(mbox).unwrap(); //append - let flags: &[Flag] = &[Flag::Seen, Flag::Flagged]; + let flags = vec![Flag::Seen, Flag::Flagged]; c.append(mbox, e.message_to_string().unwrap().as_bytes()) .flags(flags) .finish() From 9b6ff70e3b387432c4ba3ab2ca1a84259df8e40b Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Tue, 12 Jan 2021 23:30:38 -0500 Subject: [PATCH 19/21] Avoid trying to FETCH an empty set of messages (#177) Also, apply correct validation to FETCH arguments. --- CHANGELOG.md | 2 + src/client.rs | 107 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 89 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebfdbe4..1df8120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed + - Handle empty-set inputs to `fetch` and `uid_fetch` (#177) + ### Removed ## [2.4.0] - 2020-12-15 diff --git a/src/client.rs b/src/client.rs index 26a1432..2438a86 100644 --- a/src/client.rs +++ b/src/client.rs @@ -28,15 +28,74 @@ macro_rules! quote { }; } +trait OptionExt { + fn err(self) -> std::result::Result<(), E>; +} + +impl OptionExt for Option { + fn err(self) -> std::result::Result<(), E> { + match self { + Some(e) => Err(e), + None => Ok(()), + } + } +} + +/// Convert the input into what [the IMAP +/// grammar](https://tools.ietf.org/html/rfc3501#section-9) +/// calls "quoted", which is reachable from "string" et al. +/// Also ensure it doesn't contain a colliding command-delimiter (newline). fn validate_str(value: &str) -> Result { - let quoted = quote!(value); - if quoted.find('\n').is_some() { - return Err(Error::Validate(ValidateError('\n'))); - } - if quoted.find('\r').is_some() { - return Err(Error::Validate(ValidateError('\r'))); - } - Ok(quoted) + validate_str_noquote(value)?; + Ok(quote!(value)) +} + +/// Ensure the input doesn't contain a command-terminator (newline), but don't quote it like +/// `validate_str`. +/// This is helpful for things like the FETCH attributes, which, +/// per [the IMAP grammar](https://tools.ietf.org/html/rfc3501#section-9) may not be quoted: +/// +/// > fetch = "FETCH" SP sequence-set SP ("ALL" / "FULL" / "FAST" / +/// > fetch-att / "(" fetch-att *(SP fetch-att) ")") +/// > +/// > fetch-att = "ENVELOPE" / "FLAGS" / "INTERNALDATE" / +/// > "RFC822" [".HEADER" / ".SIZE" / ".TEXT"] / +/// > "BODY" ["STRUCTURE"] / "UID" / +/// > "BODY" section ["<" number "." nz-number ">"] / +/// > "BODY.PEEK" section ["<" number "." nz-number ">"] +/// +/// Note the lack of reference to any of the string-like rules or the quote characters themselves. +fn validate_str_noquote(value: &str) -> Result<&str> { + value + .matches(|c| c == '\n' || c == '\r') + .next() + .and_then(|s| s.chars().next()) + .map(|offender| Error::Validate(ValidateError(offender))) + .err()?; + Ok(value) +} + +/// This ensures the input doesn't contain a command-terminator or any other whitespace +/// while leaving it not-quoted. +/// This is needed because, per [the formal grammer given in RFC +/// 3501](https://tools.ietf.org/html/rfc3501#section-9), a sequence set consists of the following: +/// +/// > sequence-set = (seq-number / seq-range) *("," sequence-set) +/// > seq-range = seq-number ":" seq-number +/// > seq-number = nz-number / "*" +/// > nz-number = digit-nz *DIGIT +/// > digit-nz = %x31-39 +/// +/// Note the lack of reference to SP or any other such whitespace terminals. +/// Per this grammar, in theory we ought to be even more restrictive than "no whitespace". +fn validate_sequence_set(value: &str) -> Result<&str> { + value + .matches(|c: char| c.is_ascii_whitespace()) + .next() + .and_then(|s| s.chars().next()) + .map(|offender| Error::Validate(ValidateError(offender))) + .err()?; + Ok(value) } /// An authenticated IMAP session providing the usual IMAP commands. This type is what you get from @@ -543,12 +602,16 @@ impl Session { S1: AsRef, S2: AsRef, { - self.run_command_and_read_response(&format!( - "FETCH {} {}", - sequence_set.as_ref(), - query.as_ref() - )) - .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) + if sequence_set.as_ref().is_empty() { + parse_fetches(vec![], &mut self.unsolicited_responses_tx) + } else { + self.run_command_and_read_response(&format!( + "FETCH {} {}", + validate_sequence_set(sequence_set.as_ref())?, + validate_str_noquote(query.as_ref())? + )) + .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) + } } /// Equivalent to [`Session::fetch`], except that all identifiers in `uid_set` are @@ -558,12 +621,16 @@ impl Session { S1: AsRef, S2: AsRef, { - self.run_command_and_read_response(&format!( - "UID FETCH {} {}", - uid_set.as_ref(), - query.as_ref() - )) - .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) + if uid_set.as_ref().is_empty() { + parse_fetches(vec![], &mut self.unsolicited_responses_tx) + } else { + self.run_command_and_read_response(&format!( + "UID FETCH {} {}", + validate_sequence_set(uid_set.as_ref())?, + validate_str_noquote(query.as_ref())? + )) + .and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx)) + } } /// Noop always succeeds, and it does nothing. From bf9191527f5d5fe97a1de181123d4526558ccd51 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Tue, 12 Jan 2021 20:32:23 -0800 Subject: [PATCH 20/21] Release 2.4.1 --- CHANGELOG.md | 12 ++++++++---- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1df8120..27fbd5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - - Handle empty-set inputs to `fetch` and `uid_fetch` (#177) - ### Removed +## [2.4.1] - 2021-01-12 +### Changed + + - Handle empty-set inputs to `fetch` and `uid_fetch` (#177) + ## [2.4.0] - 2020-12-15 ### Added @@ -36,7 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - MSRV increased - Better documentation of server greeting handling (#168) -[Unreleased]: https://github.com/jonhoo/rust-imap/compare/v2.4.0...HEAD -[2.4.0]: https://github.com/jonhoo/rust-imap/compare/v2.2.0...v2.4.0 +[Unreleased]: https://github.com/jonhoo/rust-imap/compare/v2.4.1...HEAD +[2.4.1]: https://github.com/jonhoo/rust-imap/compare/v2.4.0...v2.4.1 +[2.4.0]: https://github.com/jonhoo/rust-imap/compare/v2.3.0...v2.4.0 [2.3.0]: https://github.com/jonhoo/rust-imap/compare/v2.2.0...v2.3.0 [2.2.0]: https://github.com/jonhoo/rust-imap/compare/v2.1.2...v2.2.0 diff --git a/Cargo.toml b/Cargo.toml index 1dd1d68..f56fb92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "imap" -version = "2.4.0" +version = "2.4.1" authors = ["Matt McCoy ", "Jon Gjengset "] documentation = "https://docs.rs/imap/" From 7e2ab19409156411ee77b788ac76f0975f052a87 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Sat, 6 Mar 2021 09:36:43 -0800 Subject: [PATCH 21/21] Prepare for 3.0.0 alpha --- Cargo.toml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f56fb92..05ad84b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,26 +1,18 @@ [package] name = "imap" -version = "2.4.1" -authors = ["Matt McCoy ", - "Jon Gjengset "] +version = "3.0.0-alpha.1" +authors = ["Jon Gjengset ", + "Matt McCoy "] documentation = "https://docs.rs/imap/" repository = "https://github.com/jonhoo/rust-imap" homepage = "https://github.com/jonhoo/rust-imap" description = "IMAP client for Rust" -readme = "README.md" -license = "Apache-2.0/MIT" +license = "Apache-2.0 OR MIT" edition = "2018" keywords = ["email", "imap"] categories = ["email", "network-programming"] -[badges] -azure-devops = { project = "jonhoo/jonhoo", pipeline = "imap", build = "11" } -codecov = { repository = "jonhoo/rust-imap", branch = "master", service = "github" } -maintenance = { status = "actively-developed" } -is-it-maintained-issue-resolution = { repository = "jonhoo/rust-imap" } -is-it-maintained-open-issues = { repository = "jonhoo/rust-imap" } - [features] tls = ["native-tls"] default = ["tls"]