Skip to content

feat: allow setting sent date on APPEND #174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use base64;
use bufstream::BufStream;
use chrono::{DateTime, FixedOffset};
#[cfg(feature = "tls")]
use native_tls::{TlsConnector, TlsStream};
use nom;
Expand Down Expand Up @@ -1096,6 +1097,32 @@ impl<T: Read + Write> Session<T> {
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<S: AsRef<str>, B: AsRef<[u8]>>(
&mut self,
mailbox: S,
content: B,
flags: &[Flag<'_>],
date: impl Into<Option<DateTime<FixedOffset>>>,
) -> Result<()> {
let content = content.as_ref();
let flagstr = flags
Expand All @@ -1104,11 +1131,16 @@ impl<T: Read + Write> Session<T> {
.map(|f| f.to_string())
.collect::<Vec<String>>()
.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();
Expand Down
53 changes: 53 additions & 0 deletions tests/imap_integration.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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);
}