diff --git a/imap/src/client/auth.rs b/imap/src/client/auth.rs index adb5b59..4647b42 100644 --- a/imap/src/client/auth.rs +++ b/imap/src/client/auth.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use futures::stream::StreamExt; use crate::command::Command; use crate::response::{Response, ResponseDone, Status}; diff --git a/imap/src/client/inner.rs b/imap/src/client/inner.rs index 588cf30..add69d5 100644 --- a/imap/src/client/inner.rs +++ b/imap/src/client/inner.rs @@ -223,6 +223,7 @@ where } len = read_fut => { + trace!("read line {:?}", next_line); // res should not be None here let resp = parse_response(next_line)?; diff --git a/imap/src/client/mod.rs b/imap/src/client/mod.rs index afeff53..f01f279 100644 --- a/imap/src/client/mod.rs +++ b/imap/src/client/mod.rs @@ -39,17 +39,13 @@ mod inner; use std::sync::Arc; use anyhow::Result; -use futures::{ - future::{self, Either, FutureExt}, - stream::StreamExt, -}; use tokio::net::TcpStream; use tokio_rustls::{ client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector, }; use tokio_stream::wrappers::UnboundedReceiverStream; -use crate::command::Command; +use crate::command::{Command, FetchItems, SearchCriteria}; use crate::response::{MailboxData, Response, ResponseData, ResponseDone}; pub use self::inner::{Client, ResponseStream}; @@ -176,12 +172,11 @@ impl ClientAuthenticated { let cmd = Command::Select { mailbox: mailbox.as_ref().to_owned(), }; - let mut stream = self.execute(cmd).await?; - // let (resp, mut st) = self.execute(cmd).await?; - debug!("execute called returned..."); - debug!("ST: {:?}", stream.next().await); - // let resp = resp.await?; - // debug!("select response: {:?}", resp); + let stream = self.execute(cmd).await?; + let (done, data) = stream.wait().await?; + for resp in data { + debug!("execute called returned: {:?}", resp); + } // nuke the capabilities cache self.nuke_capabilities(); @@ -189,6 +184,37 @@ impl ClientAuthenticated { Ok(()) } + /// Runs the SEARCH command + pub async fn uid_search(&mut self) -> Result> { + let cmd = Command::UidSearch { + criteria: SearchCriteria::All, + }; + let stream = self.execute(cmd).await?; + let (_, data) = stream.wait().await?; + for resp in data { + if let Response::MailboxData(MailboxData::Search(uids)) = resp { + return Ok(uids); + } + } + bail!("could not find the SEARCH response") + } + + /// Runs the UID FETCH command + pub async fn uid_fetch(&mut self, uids: &[u32]) -> Result<()> { + let cmd = Command::UidFetch { + uids: uids.to_vec(), + items: FetchItems::All, + }; + debug!("uid fetch: {}", cmd); + let stream = self.execute(cmd).await?; + let (done, data) = stream.wait().await?; + debug!("done: {:?} {:?}", done, data); + for resp in data { + debug!("uid fetch: {:?}", resp); + } + todo!() + } + /// Runs the IDLE command #[cfg(feature = "rfc2177-idle")] pub async fn idle(&mut self) -> Result { diff --git a/imap/src/command/mod.rs b/imap/src/command/mod.rs index 043a74d..eca2891 100644 --- a/imap/src/command/mod.rs +++ b/imap/src/command/mod.rs @@ -16,6 +16,17 @@ pub enum Command { reference: String, mailbox: String, }, + Search { + criteria: SearchCriteria, + }, + UidSearch { + criteria: SearchCriteria, + }, + UidFetch { + // TODO: do sequence-set + uids: Vec, + items: FetchItems, + }, #[cfg(feature = "rfc2177-idle")] Idle, @@ -25,14 +36,8 @@ impl fmt::Debug for Command { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Command::*; match self { - Capability => write!(f, "CAPABILITY"), - Starttls => write!(f, "STARTTLS"), Login { .. } => write!(f, "LOGIN"), - Select { mailbox } => write!(f, "SELECT {}", mailbox), - List { reference, mailbox } => write!(f, "LIST {:?} {:?}", reference, mailbox), - - #[cfg(feature = "rfc2177-idle")] - Idle => write!(f, "IDLE"), + _ => ::fmt(self, f), } } } @@ -45,10 +50,53 @@ impl fmt::Display for Command { Starttls => write!(f, "STARTTLS"), Login { username, password } => write!(f, "LOGIN {:?} {:?}", username, password), Select { mailbox } => write!(f, "SELECT {}", mailbox), + Search { criteria } => write!(f, "SEARCH {}", criteria), + UidSearch { criteria } => write!(f, "UID SEARCH {}", criteria), List { reference, mailbox } => write!(f, "LIST {:?} {:?}", reference, mailbox), + UidFetch { uids, items } => write!( + f, + "UID FETCH {} {}", + uids.iter() + .map(|s| s.to_string()) + .collect::>() + .join(","), + items + ), #[cfg(feature = "rfc2177-idle")] Idle => write!(f, "IDLE"), } } } + +#[derive(Clone, Debug)] +pub enum SearchCriteria { + All, +} + +impl fmt::Display for SearchCriteria { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use SearchCriteria::*; + match self { + All => write!(f, "ALL"), + } + } +} + +#[derive(Clone, Debug)] +pub enum FetchItems { + All, + Fast, + Full, +} + +impl fmt::Display for FetchItems { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use FetchItems::*; + match self { + All => write!(f, "ALL"), + Fast => write!(f, "FAST"), + Full => write!(f, "FULL"), + } + } +} diff --git a/imap/src/parser/mod.rs b/imap/src/parser/mod.rs index b661547..86646ec 100644 --- a/imap/src/parser/mod.rs +++ b/imap/src/parser/mod.rs @@ -152,6 +152,7 @@ fn build_msg_att_static(pair: Pair) -> AttributeValue { index: None, data: None, }, + Rule::msg_att_static_uid => AttributeValue::Uid(build_number(unwrap1(unwrap1(pair)))), _ => unreachable!("{:#?}", pair), } } @@ -313,6 +314,10 @@ fn build_mailbox_data(pair: Pair) -> MailboxData { name, } } + Rule::mailbox_data_search => { + let uids = pair.into_inner().map(build_number).collect(); + MailboxData::Search(uids) + } _ => unreachable!("{:#?}", pair), } } diff --git a/imap/src/parser/rfc3501.pest b/imap/src/parser/rfc3501.pest index b0368c7..44b0f23 100644 --- a/imap/src/parser/rfc3501.pest +++ b/imap/src/parser/rfc3501.pest @@ -64,11 +64,12 @@ header_fld_name = { astring } header_list = { "(" ~ header_fld_name ~ (sp ~ header_fld_name)* ~ ")" } list_wildcards = @{ "%" | "*" } mailbox = { ^"INBOX" | astring } -mailbox_data = { mailbox_data_flags | mailbox_data_list | (^"LSUB" ~ sp ~ mailbox_list) | (^"SEARCH" ~ (sp ~ nz_number)*) | (^"STATUS" ~ sp ~ mailbox ~ sp ~ ^"(" ~ status_att_list? ~ ^")") | mailbox_data_exists | mailbox_data_recent } +mailbox_data = { mailbox_data_flags | mailbox_data_list | (^"LSUB" ~ sp ~ mailbox_list) | mailbox_data_search | (^"STATUS" ~ sp ~ mailbox ~ sp ~ ^"(" ~ status_att_list? ~ ^")") | mailbox_data_exists | mailbox_data_recent } mailbox_data_exists = { number ~ sp ~ ^"EXISTS" } mailbox_data_flags = { ^"FLAGS" ~ sp ~ flag_list } mailbox_data_list = { ^"LIST" ~ sp ~ mailbox_list } mailbox_data_recent = { number ~ sp ~ ^"RECENT" } +mailbox_data_search = { ^"SEARCH" ~ (sp ~ nz_number)* } mailbox_list = { mailbox_list_flags ~ sp ~ mailbox_list_string ~ sp ~ mailbox } mailbox_list_flags = { "(" ~ mbx_list_flags* ~ ")" } mailbox_list_string = { dquote ~ quoted_char ~ dquote | nil } @@ -85,11 +86,12 @@ message_data_fetch = { ^"FETCH" ~ sp ~ msg_att } msg_att = { "(" ~ msg_att_dyn_or_stat ~ (sp ~ msg_att_dyn_or_stat)* ~ ")" } msg_att_dyn_or_stat = { msg_att_dynamic | msg_att_static } msg_att_dynamic = { ^"FLAGS" ~ sp ~ "(" ~ (flag_fetch ~ (sp ~ flag_fetch)*)? ~ ")" } -msg_att_static = { msg_att_static_envelope | msg_att_static_internaldate | (^"RFC822" ~ (^".HEADER" | ^".TEXT") ~ sp ~ nstring) | msg_att_static_rfc822_size | msg_att_static_body | (^"BODY" ~ section ~ ("<" ~ number ~ ">")? ~ sp ~ nstring) | (^"UID" ~ sp ~ uniqueid) } +msg_att_static = { msg_att_static_envelope | msg_att_static_internaldate | (^"RFC822" ~ (^".HEADER" | ^".TEXT") ~ sp ~ nstring) | msg_att_static_rfc822_size | msg_att_static_body | (^"BODY" ~ section ~ ("<" ~ number ~ ">")? ~ sp ~ nstring) | msg_att_static_uid } msg_att_static_body = { ^"BODY" ~ ^"STRUCTURE"? ~ sp ~ body } msg_att_static_envelope = { ^"ENVELOPE" ~ sp ~ envelope } msg_att_static_internaldate = { ^"INTERNALDATE" ~ sp ~ date_time } msg_att_static_rfc822_size = { ^"RFC822.SIZE" ~ sp ~ number } +msg_att_static_uid = { ^"UID" ~ sp ~ uniqueid } nil = { ^"NIL" } nstring = { string | nil } number = @{ digit{1,} } diff --git a/src/mail/mod.rs b/src/mail/mod.rs index 69fe5bc..3951540 100644 --- a/src/mail/mod.rs +++ b/src/mail/mod.rs @@ -135,16 +135,18 @@ async fn imap_main(acct: MailAccountConfig, mail2ui_tx: UnboundedSender