imap 2.4.1

IMAP client for Rust
Documentation
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;

fn tls() -> native_tls::TlsConnector {
    native_tls::TlsConnector::builder()
        .danger_accept_invalid_certs(true)
        .danger_accept_invalid_hostnames(true)
        .build()
        .unwrap()
}

fn session(user: &str) -> imap::Session<native_tls::TlsStream<TcpStream>> {
    let mut s = imap::connect(
        &format!(
            "{}:3993",
            std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string())
        ),
        "imap.example.com",
        &tls(),
    )
    .unwrap()
    .login(user, user)
    .unwrap();
    s.debug = true;
    s
}

fn smtp(user: &str) -> lettre::SmtpTransport {
    let creds = lettre::smtp::authentication::Credentials::new(user.to_string(), user.to_string());
    lettre::SmtpClient::new(
        &format!(
            "{}:3465",
            std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string())
        ),
        lettre::ClientSecurity::Wrapper(lettre::ClientTlsParameters {
            connector: tls(),
            domain: "smpt.example.com".to_string(),
        }),
    )
    .unwrap()
    .credentials(creds)
    .transport()
}

#[test]
#[ignore]
fn connect_insecure_then_secure() {
    let host = std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string());
    let stream = TcpStream::connect((host.as_ref(), 3143)).unwrap();

    // ignored because of https://github.com/greenmail-mail-test/greenmail/issues/135
    imap::Client::new(stream)
        .secure("imap.example.com", &tls())
        .unwrap();
}

#[test]
fn connect_secure() {
    imap::connect(
        &format!(
            "{}:3993",
            std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string())
        ),
        "imap.example.com",
        &tls(),
    )
    .unwrap();
}

#[test]
fn login() {
    session("readonly-test@localhost");
}

#[test]
fn logout() {
    let mut s = session("readonly-test@localhost");
    s.logout().unwrap();
}

#[test]
fn inbox_zero() {
    // https://github.com/greenmail-mail-test/greenmail/issues/265
    let mut s = session("readonly-test@localhost");
    s.select("INBOX").unwrap();
    let inbox = s.search("ALL").unwrap();
    assert_eq!(inbox.len(), 0);
}

#[test]
fn inbox() {
    let to = "inbox@localhost";

    // first log in so we'll see the unsolicited e-mails
    let mut c = session(to);
    c.select("INBOX").unwrap();

    // then send the e-mail
    let mut s = smtp(to);
    let e = lettre_email::Email::builder()
        .from("sender@localhost")
        .to(to)
        .subject("My first e-mail")
        .text("Hello world from SMTP")
        .build()
        .unwrap();
    s.send(e.into()).unwrap();

    // now we should see the e-mail!
    let inbox = c.search("ALL").unwrap();
    // and the one message should have the first message sequence number
    assert_eq!(inbox.len(), 1);
    assert!(inbox.contains(&1));

    // we should also get two unsolicited responses: Exists and Recent
    c.noop().unwrap();
    let mut unsolicited = Vec::new();
    while let Ok(m) = c.unsolicited_responses.try_recv() {
        unsolicited.push(m);
    }
    assert_eq!(unsolicited.len(), 2);
    assert!(unsolicited
        .iter()
        .any(|m| m == &imap::types::UnsolicitedResponse::Exists(1)));
    assert!(unsolicited
        .iter()
        .any(|m| m == &imap::types::UnsolicitedResponse::Recent(1)));

    // let's see that we can also fetch the e-mail
    let fetch = c.fetch("1", "(ALL UID)").unwrap();
    assert_eq!(fetch.len(), 1);
    let fetch = &fetch[0];
    assert_eq!(fetch.message, 1);
    assert_ne!(fetch.uid, None);
    assert_eq!(fetch.size, Some(138));
    let e = fetch.envelope().unwrap();
    assert_eq!(e.subject, Some(&b"My first e-mail"[..]));
    assert_ne!(e.from, None);
    assert_eq!(e.from.as_ref().unwrap().len(), 1);
    let from = &e.from.as_ref().unwrap()[0];
    assert_eq!(from.mailbox, Some(&b"sender"[..]));
    assert_eq!(from.host, Some(&b"localhost"[..]));
    assert_ne!(e.to, None);
    assert_eq!(e.to.as_ref().unwrap().len(), 1);
    let to = &e.to.as_ref().unwrap()[0];
    assert_eq!(to.mailbox, Some(&b"inbox"[..]));
    assert_eq!(to.host, Some(&b"localhost"[..]));
    let date_opt = fetch.internal_date();
    assert!(date_opt.is_some());

    // and let's delete it to clean up
    c.store("1", "+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 inbox_uid() {
    let to = "inbox-uid@localhost";

    // first log in so we'll see the unsolicited e-mails
    let mut c = session(to);
    c.select("INBOX").unwrap();

    // then send the e-mail
    let mut s = smtp(to);
    let e = lettre_email::Email::builder()
        .from("sender@localhost")
        .to(to)
        .subject("My first e-mail")
        .text("Hello world from SMTP")
        .build()
        .unwrap();
    s.send(e.into()).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();

    // we should also get two unsolicited responses: Exists and Recent
    c.noop().unwrap();
    let mut unsolicited = Vec::new();
    while let Ok(m) = c.unsolicited_responses.try_recv() {
        unsolicited.push(m);
    }
    assert_eq!(unsolicited.len(), 2);
    assert!(unsolicited
        .iter()
        .any(|m| m == &imap::types::UnsolicitedResponse::Exists(1)));
    assert!(unsolicited
        .iter()
        .any(|m| m == &imap::types::UnsolicitedResponse::Recent(1)));

    // let's see that we can also 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 first e-mail"[..]));
    let date_opt = fetch.internal_date();
    assert!(date_opt.is_some());

    // 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]
#[ignore]
fn list() {
    let mut s = session("readonly-test@localhost");
    s.select("INBOX").unwrap();
    let subdirs = s.list(None, Some("%")).unwrap();
    assert_eq!(subdirs.len(), 0);

    // 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);
}

#[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);
}