From ae07c9f31c8c53fec1d0cc175a9353ade182df6b Mon Sep 17 00:00:00 2001 From: mordak Date: Sun, 23 Aug 2020 11:02:57 -0500 Subject: [PATCH] Add append_with_flags (#171) Allows a client to set flags on a new message as it is appended to a mailbox. --- src/client.rs | 29 +++++++++++- src/types/mod.rs | 15 ++++++ tests/imap_integration.rs | 99 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index 33f5d1a..a47c401 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1078,10 +1078,37 @@ 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. pub fn append, B: AsRef<[u8]>>(&mut self, mailbox: S, content: B) -> Result<()> { + self.append_with_flags(mailbox, content, &[]) + } + + /// 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 append_with_flags, B: AsRef<[u8]>>( + &mut self, + mailbox: S, + content: B, + flags: &[Flag<'_>], + ) -> Result<()> { let content = content.as_ref(); + let flagstr = flags + .iter() + .filter(|f| **f != Flag::Recent) + .map(|f| f.to_string()) + .collect::>() + .join(" "); + self.run_command(&format!( - "APPEND \"{}\" {{{}}}", + "APPEND \"{}\" ({}) {{{}}}", mailbox.as_ref(), + flagstr, content.len() ))?; let mut v = Vec::new(); diff --git a/src/types/mod.rs b/src/types/mod.rs index 4a789e5..fb57e4e 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -169,6 +169,21 @@ impl Flag<'static> { } } +impl<'a> fmt::Display for Flag<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Flag::Seen => write!(f, "{}", "\\Seen"), + Flag::Answered => write!(f, "{}", "\\Answered"), + Flag::Flagged => write!(f, "{}", "\\Flagged"), + Flag::Deleted => write!(f, "{}", "\\Deleted"), + Flag::Draft => write!(f, "{}", "\\Draft"), + Flag::Recent => write!(f, "{}", "\\Recent"), + Flag::MayCreate => write!(f, "{}", "\\*"), + Flag::Custom(ref s) => write!(f, "{}", s), + } + } +} + impl<'a> From for Flag<'a> { fn from(s: String) -> Self { if let Some(f) = Flag::system(&s) { diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index f639684..0a8bf76 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -231,3 +231,102 @@ fn list() { // TODO: make a subdir } + +#[test] +fn append() { + let to = "inbox-append1@localhost"; + + // make a message to append + let e: lettre::SendableEmail = lettre_email::Email::builder() + .from("sender@localhost") + .to(to) + .subject("My second e-mail") + .text("Hello world") + .build() + .unwrap() + .into(); + + // connect + let mut c = session(to); + let mbox = "INBOX"; + c.select(mbox).unwrap(); + //append + c.append(mbox, e.message_to_string().unwrap()).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)); + let e = fetch.envelope().unwrap(); + assert_eq!(e.subject, Some(&b"My second e-mail"[..])); + + // 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); +} + +#[test] +fn append_with_flags() { + use imap::types::Flag; + + let to = "inbox-append2@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]; + c.append_with_flags(mbox, e.message_to_string().unwrap(), flags) + .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)); + let e = fetch.envelope().unwrap(); + assert_eq!(e.subject, Some(&b"My third e-mail"[..])); + + // check the flags + let setflags = fetch.flags(); + assert!(setflags.contains(&Flag::Seen)); + assert!(setflags.contains(&Flag::Flagged)); + + // 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); +}