Implement some IMAP extensions #50
3 changed files with 139 additions and 13 deletions
14
Cargo.toml
14
Cargo.toml
|
@ -68,16 +68,6 @@ imap-types = { git = "https://github.com/duesee/imap-codec", branch = "v2" }
|
||||||
imap-codec = { git = "https://github.com/duesee/imap-codec", branch = "v2" }
|
imap-codec = { git = "https://github.com/duesee/imap-codec", branch = "v2" }
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "rfc3501_imap4rev1_base"
|
name = "behavior"
|
||||||
path = "tests/rfc3501_imap4rev1_base.rs"
|
path = "tests/behavior.rs"
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "rfc3691_imapext_unselect"
|
|
||||||
path = "tests/rfc3691_imapext_unselect.rs"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "rfc6851_imapext_move"
|
|
||||||
path = "tests/rfc6851_imapext_move.rs"
|
|
||||||
harness = false
|
harness = false
|
||||||
|
|
107
tests/behavior.rs
Normal file
107
tests/behavior.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
use crate::common::fragments::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
rfc3501_imap4rev1_base();
|
||||||
|
rfc3691_imapext_unselect();
|
||||||
|
rfc5161_imapext_enable();
|
||||||
|
rfc6851_imapext_move();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rfc3501_imap4rev1_base() {
|
||||||
|
println!("rfc3501_imap4rev1_base");
|
||||||
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
|
||||||
|
connect(imap_socket).context("server says hello")?;
|
||||||
|
capability(imap_socket, Extension::None).context("check server capabilities")?;
|
||||||
|
login(imap_socket, Account::Alice).context("login test")?;
|
||||||
|
create_mailbox(imap_socket, Mailbox::Archive).context("created mailbox archive")?;
|
||||||
|
// UNSUBSCRIBE IS NOT IMPLEMENTED YET
|
||||||
|
//unsubscribe_mailbox(imap_socket).context("unsubscribe from archive")?;
|
||||||
|
select(imap_socket, Mailbox::Inbox, None).context("select inbox")?;
|
||||||
|
check(imap_socket).context("check must run")?;
|
||||||
|
status_mailbox(imap_socket, Mailbox::Archive).context("status of archive from inbox")?;
|
||||||
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
||||||
|
lmtp_deliver_email(lmtp_socket, Email::Multipart).context("mail delivered successfully")?;
|
||||||
|
noop_exists(imap_socket).context("noop loop must detect a new email")?;
|
||||||
|
fetch_rfc822(imap_socket, Selection::FirstId, Email::Multipart).context("fetch rfc822 message, should be our first message")?;
|
||||||
|
copy(imap_socket, Selection::FirstId, Mailbox::Archive).context("copy message to the archive mailbox")?;
|
||||||
|
append_email(imap_socket, Email::Basic).context("insert email in INBOX")?;
|
||||||
|
// SEARCH IS NOT IMPLEMENTED YET
|
||||||
|
//search(imap_socket).expect("search should return something");
|
||||||
|
add_flags_email(imap_socket, Selection::FirstId, Flag::Deleted)
|
||||||
|
.context("should add delete flag to the email")?;
|
||||||
|
expunge(imap_socket).context("expunge emails")?;
|
||||||
|
rename_mailbox(imap_socket, Mailbox::Archive, Mailbox::Drafts).context("Archive mailbox is renamed Drafts")?;
|
||||||
|
delete_mailbox(imap_socket, Mailbox::Drafts).context("Drafts mailbox is deleted")?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.expect("test fully run");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rfc3691_imapext_unselect() {
|
||||||
|
println!("rfc3691_imapext_unselect");
|
||||||
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
|
||||||
|
connect(imap_socket).context("server says hello")?;
|
||||||
|
|
||||||
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
||||||
|
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
|
||||||
|
|
||||||
|
capability(imap_socket, Extension::Unselect).context("check server capabilities")?;
|
||||||
|
login(imap_socket, Account::Alice).context("login test")?;
|
||||||
|
select(imap_socket, Mailbox::Inbox, None).context("select inbox")?;
|
||||||
|
noop_exists(imap_socket).context("noop loop must detect a new email")?;
|
||||||
|
add_flags_email(imap_socket, Selection::FirstId, Flag::Deleted).context("add delete flags to the email")?;
|
||||||
|
unselect(imap_socket)
|
||||||
|
.context("unselect inbox while preserving email with the \\Delete flag")?;
|
||||||
|
select(imap_socket, Mailbox::Inbox, Some(1)).context("select inbox again")?;
|
||||||
|
fetch_rfc822(imap_socket, Selection::FirstId, Email::Basic).context("message is still present")?;
|
||||||
|
close(imap_socket).context("close inbox and expunge message")?;
|
||||||
|
select(imap_socket, Mailbox::Inbox, Some(0)).context("select inbox again and check it's empty")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.expect("test fully run");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rfc5161_imapext_enable() {
|
||||||
|
println!("rfc5161_imapext_enable");
|
||||||
|
common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket| {
|
||||||
|
connect(imap_socket).context("server says hello")?;
|
||||||
|
login(imap_socket, Account::Alice).context("login test")?;
|
||||||
|
enable(imap_socket, Enable::Utf8Accept, Some(Enable::Utf8Accept))?;
|
||||||
|
enable(imap_socket, Enable::Utf8Accept, None)?;
|
||||||
|
logout(imap_socket)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.expect("test fully run");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rfc6851_imapext_move() {
|
||||||
|
println!("rfc6851_imapext_move");
|
||||||
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
|
||||||
|
connect(imap_socket).context("server says hello")?;
|
||||||
|
|
||||||
|
capability(imap_socket, Extension::Move).context("check server capabilities")?;
|
||||||
|
login(imap_socket, Account::Alice).context("login test")?;
|
||||||
|
create_mailbox(imap_socket, Mailbox::Archive).context("created mailbox archive")?;
|
||||||
|
select(imap_socket, Mailbox::Inbox, None).context("select inbox")?;
|
||||||
|
|
||||||
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
||||||
|
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
|
||||||
|
|
||||||
|
noop_exists(imap_socket).context("noop loop must detect a new email")?;
|
||||||
|
r#move(imap_socket, Selection::FirstId, Mailbox::Archive).context("message from inbox moved to archive")?;
|
||||||
|
|
||||||
|
unselect(imap_socket)
|
||||||
|
.context("unselect inbox while preserving email with the \\Delete flag")?;
|
||||||
|
select(imap_socket, Mailbox::Archive, Some(1)).context("select archive")?;
|
||||||
|
fetch_rfc822(imap_socket, Selection::FirstId, Email::Basic).context("check mail exists")?;
|
||||||
|
logout(imap_socket).context("must quit")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.expect("test fully run");
|
||||||
|
}
|
|
@ -11,7 +11,11 @@ use crate::common::*;
|
||||||
/// arbitrary values, only enum for which the code is known
|
/// arbitrary values, only enum for which the code is known
|
||||||
/// to be correct. The idea is that the generated message is more
|
/// to be correct. The idea is that the generated message is more
|
||||||
/// or less hardcoded by the developer, so its clear what's expected,
|
/// or less hardcoded by the developer, so its clear what's expected,
|
||||||
/// and not generated by a library.
|
/// and not generated by a library. Also don't use vector of enum,
|
||||||
|
/// as it again introduce some kind of genericity we try so hard to avoid:
|
||||||
|
/// instead add a dedicated enum, for example "All" or anything relaevent that would
|
||||||
|
/// describe your list and then hardcode it in your fragment.
|
||||||
|
/// DON'T. TRY. TO. BE. GENERIC. HERE.
|
||||||
|
|
||||||
pub fn connect(imap: &mut TcpStream) -> Result<()> {
|
pub fn connect(imap: &mut TcpStream) -> Result<()> {
|
||||||
let mut buffer: [u8; 1500] = [0; 1500];
|
let mut buffer: [u8; 1500] = [0; 1500];
|
||||||
|
@ -33,6 +37,12 @@ pub enum Extension {
|
||||||
CondStore,
|
CondStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum Enable {
|
||||||
|
Utf8Accept,
|
||||||
|
CondStore,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum Mailbox {
|
pub enum Mailbox {
|
||||||
Inbox,
|
Inbox,
|
||||||
Archive,
|
Archive,
|
||||||
|
@ -357,6 +367,25 @@ pub fn r#move(imap: &mut TcpStream, selection: Selection, to: Mailbox) -> Result
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn enable(imap: &mut TcpStream, ask: Enable, done: Option<Enable>) -> Result<()> {
|
||||||
|
let mut buffer: [u8; 6000] = [0; 6000];
|
||||||
|
assert!(matches!(ask, Enable::Utf8Accept));
|
||||||
|
|
||||||
|
imap.write(&b"36 enable UTF8=ACCEPT\r\n"[..])?;
|
||||||
|
let read = read_lines(imap, &mut buffer, Some(&b"36 OK"[..]))?;
|
||||||
|
let srv_msg = std::str::from_utf8(read)?;
|
||||||
|
match done {
|
||||||
|
None => assert_eq!(srv_msg.lines().count(), 1),
|
||||||
|
Some(Enable::Utf8Accept) => {
|
||||||
|
assert_eq!(srv_msg.lines().count(), 2);
|
||||||
|
assert!(srv_msg.contains("* ENABLED UTF8=ACCEPT"));
|
||||||
|
},
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn logout(imap: &mut TcpStream) -> Result<()> {
|
pub fn logout(imap: &mut TcpStream) -> Result<()> {
|
||||||
imap.write(&b"99 logout\r\n"[..])?;
|
imap.write(&b"99 logout\r\n"[..])?;
|
||||||
let mut buffer: [u8; 1500] = [0; 1500];
|
let mut buffer: [u8; 1500] = [0; 1500];
|
||||||
|
|
Loading…
Reference in a new issue