Implement LIST X Y RETURN (STATUS (UIDNEXT ...)) #75

Merged
quentin merged 2 commits from feat/list-status into main 2024-01-20 18:24:06 +00:00
3 changed files with 87 additions and 4 deletions
Showing only changes of commit 9c3f447480 - Show all commits

View file

@ -18,6 +18,10 @@ fn capability_uidplus() -> Capability<'static> {
Capability::try_from("UIDPLUS").unwrap() Capability::try_from("UIDPLUS").unwrap()
} }
fn capability_liststatus() -> Capability<'static> {
Capability::try_from("LIST-STATUS").unwrap()
}
/* /*
fn capability_qresync() -> Capability<'static> { fn capability_qresync() -> Capability<'static> {
Capability::try_from("QRESYNC").unwrap() Capability::try_from("QRESYNC").unwrap()
@ -38,6 +42,7 @@ impl Default for ServerCapability {
capability_unselect(), capability_unselect(),
capability_condstore(), capability_condstore(),
capability_uidplus(), capability_uidplus(),
capability_liststatus(),
//capability_qresync(), //capability_qresync(),
])) ]))
} }

View file

@ -6,13 +6,14 @@ use crate::common::fragments::*;
fn main() { fn main() {
rfc3501_imap4rev1_base(); rfc3501_imap4rev1_base();
rfc3691_imapext_unselect();
rfc5161_imapext_enable();
rfc6851_imapext_move(); rfc6851_imapext_move();
rfc7888_imapext_literal();
rfc4551_imapext_condstore(); rfc4551_imapext_condstore();
rfc2177_imapext_idle(); rfc2177_imapext_idle();
rfc4315_imapext_uidplus(); rfc5161_imapext_enable(); // 1
rfc3691_imapext_unselect(); // 2
rfc7888_imapext_literal(); // 3
rfc4315_imapext_uidplus(); // 4
rfc5819_imapext_liststatus(); // 5
println!("✅ SUCCESS 🌟🚀🥳🙏🥹"); println!("✅ SUCCESS 🌟🚀🥳🙏🥹");
} }
@ -307,3 +308,50 @@ fn rfc4315_imapext_uidplus() {
}) })
.expect("test fully run"); .expect("test fully run");
} }
///
/// Example
///
/// ```text
/// 30 list "" "*" RETURN (STATUS (MESSAGES UNSEEN))
/// * LIST (\Subscribed) "." INBOX
/// * STATUS INBOX (MESSAGES 2 UNSEEN 1)
/// 30 OK LIST completed
/// ```
fn rfc5819_imapext_liststatus() {
println!("🧪 rfc5819_imapext_liststatus");
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
// Test setup, check capability, add 2 emails, read 1
connect(imap_socket).context("server says hello")?;
capability(imap_socket, Extension::ListStatus).context("check server capabilities")?;
login(imap_socket, Account::Alice).context("login test")?;
select(imap_socket, Mailbox::Inbox, SelectMod::None).context("select inbox")?;
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
lmtp_deliver_email(lmtp_socket, Email::Multipart).context("mail delivered successfully")?;
noop_exists(imap_socket, 2).context("noop loop must detect a new email")?;
fetch(
imap_socket,
Selection::FirstId,
FetchKind::Rfc822,
FetchMod::None,
)
.context("read one message")?;
close(imap_socket).context("close inbox")?;
// Test return status MESSAGES UNSEEN
let ret = list(
imap_socket,
MbxSelect::All,
ListReturn::StatusMessagesUnseen,
)?;
assert!(ret.contains("* STATUS INBOX (MESSAGES 2 UNSEEN 1)"));
// Test that without RETURN, no status is sent
let ret = list(imap_socket, MbxSelect::All, ListReturn::None)?;
assert!(!ret.contains("* STATUS"));
Ok(())
})
.expect("test fully run");
}

View file

@ -38,6 +38,7 @@ pub enum Extension {
LiteralPlus, LiteralPlus,
Idle, Idle,
UidPlus, UidPlus,
ListStatus,
} }
pub enum Enable { pub enum Enable {
@ -107,6 +108,15 @@ pub enum StatusKind {
HighestModSeq, HighestModSeq,
} }
pub enum MbxSelect {
All,
}
pub enum ListReturn {
None,
StatusMessagesUnseen,
}
pub fn capability(imap: &mut TcpStream, ext: Extension) -> Result<()> { pub fn capability(imap: &mut TcpStream, ext: Extension) -> Result<()> {
imap.write(&b"5 capability\r\n"[..])?; imap.write(&b"5 capability\r\n"[..])?;
@ -118,6 +128,7 @@ pub fn capability(imap: &mut TcpStream, ext: Extension) -> Result<()> {
Extension::LiteralPlus => Some("LITERAL+"), Extension::LiteralPlus => Some("LITERAL+"),
Extension::Idle => Some("IDLE"), Extension::Idle => Some("IDLE"),
Extension::UidPlus => Some("UIDPLUS"), Extension::UidPlus => Some("UIDPLUS"),
Extension::ListStatus => Some("LIST-STATUS"),
}; };
let mut buffer: [u8; 6000] = [0; 6000]; let mut buffer: [u8; 6000] = [0; 6000];
@ -169,6 +180,25 @@ pub fn create_mailbox(imap: &mut TcpStream, mbx: Mailbox) -> Result<()> {
Ok(()) Ok(())
} }
pub fn list(imap: &mut TcpStream, select: MbxSelect, mod_return: ListReturn) -> Result<String> {
let mut buffer: [u8; 6000] = [0; 6000];
let select_str = match select {
MbxSelect::All => "%",
};
let mod_return_str = match mod_return {
ListReturn::None => "",
ListReturn::StatusMessagesUnseen => " RETURN (STATUS (MESSAGES UNSEEN))",
};
imap.write(format!("19 LIST \"\" \"{}\"{}\r\n", select_str, mod_return_str).as_bytes())?;
let read = read_lines(imap, &mut buffer, Some(&b"19 OK"[..]))?;
let srv_msg = std::str::from_utf8(read)?;
Ok(srv_msg.to_string())
}
pub fn select(imap: &mut TcpStream, mbx: Mailbox, modifier: SelectMod) -> Result<String> { pub fn select(imap: &mut TcpStream, mbx: Mailbox, modifier: SelectMod) -> Result<String> {
let mut buffer: [u8; 6000] = [0; 6000]; let mut buffer: [u8; 6000] = [0; 6000];