osudebug
This commit is contained in:
parent
1e6ecc45bb
commit
3f726f3129
7 changed files with 594 additions and 86 deletions
|
@ -79,13 +79,19 @@ where
|
||||||
let greeting = Arc::new(RwLock::new((None, None)));
|
let greeting = Arc::new(RwLock::new((None, None)));
|
||||||
let caps: CapsLock = Arc::new(RwLock::new(None));
|
let caps: CapsLock = Arc::new(RwLock::new(None));
|
||||||
|
|
||||||
let listener_handle = tokio::spawn(listen(
|
let listener_handle = tokio::spawn(
|
||||||
read_half,
|
listen(
|
||||||
caps.clone(),
|
read_half,
|
||||||
results.clone(),
|
caps.clone(),
|
||||||
exit_rx,
|
results.clone(),
|
||||||
greeting.clone(),
|
exit_rx,
|
||||||
));
|
greeting.clone(),
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
|
error!("Help, the listener loop died: {}", err);
|
||||||
|
err
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
Client {
|
Client {
|
||||||
config,
|
config,
|
||||||
|
@ -118,6 +124,7 @@ where
|
||||||
// this should queue up responses
|
// this should queue up responses
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
debug!("EX[{}]: adding handler result to the handlers queue", id);
|
||||||
{
|
{
|
||||||
let mut handlers = self.results.write();
|
let mut handlers = self.results.write();
|
||||||
handlers.push_back(HandlerResult {
|
handlers.push_back(HandlerResult {
|
||||||
|
@ -128,6 +135,7 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("EX[{}]: send the command to the server", id);
|
||||||
let cmd_str = format!("{}{} {}\r\n", TAG_PREFIX, id, cmd);
|
let cmd_str = format!("{}{} {}\r\n", TAG_PREFIX, id, cmd);
|
||||||
// debug!("[{}] writing to socket: {:?}", id, cmd_str);
|
// debug!("[{}] writing to socket: {:?}", id, cmd_str);
|
||||||
self.conn.write_all(cmd_str.as_bytes()).await?;
|
self.conn.write_all(cmd_str.as_bytes()).await?;
|
||||||
|
@ -141,9 +149,11 @@ where
|
||||||
|
|
||||||
// let resp = end_rx.await?;
|
// let resp = end_rx.await?;
|
||||||
|
|
||||||
|
debug!("EX[{}]: hellosu", id);
|
||||||
let q = self.results.clone();
|
let q = self.results.clone();
|
||||||
// let end = Box::new(end_rx.map_err(|err| Error::from).map(move |resp| resp));
|
// let end = Box::new(end_rx.map_err(|err| Error::from).map(move |resp| resp));
|
||||||
let end = Box::new(end_rx.map_err(Error::from).map(move |resp| {
|
let end = Box::new(end_rx.map_err(Error::from).map(move |resp| {
|
||||||
|
debug!("EX[{}]: -end result- {:?}", id, resp);
|
||||||
// pop the first entry from the list
|
// pop the first entry from the list
|
||||||
let mut results = q.write();
|
let mut results = q.write();
|
||||||
results.pop_front();
|
results.pop_front();
|
||||||
|
@ -251,50 +261,6 @@ impl Future for GreetingWaiter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub struct ExecWaiter<'a, C>(&'a Client<C>, usize, bool);
|
|
||||||
//
|
|
||||||
// impl<'a, C> Future for ExecWaiter<'a, C> {
|
|
||||||
// type Output = (Response, ResponseStream);
|
|
||||||
// fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
|
||||||
// // add the waker
|
|
||||||
// let mut results = self.0.results.write();
|
|
||||||
// if !self.2 {
|
|
||||||
// if let Some(HandlerResult {
|
|
||||||
// waker: waker_ref, ..
|
|
||||||
// }) = results
|
|
||||||
// .iter_mut()
|
|
||||||
// .find(|HandlerResult { id, .. }| *id == self.1)
|
|
||||||
// {
|
|
||||||
// let waker = cx.waker().clone();
|
|
||||||
// *waker_ref = Some(waker);
|
|
||||||
// self.2 = true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // if this struct exists then there's definitely at least one entry
|
|
||||||
// let HandlerResult {
|
|
||||||
// id,
|
|
||||||
// end: last_response,
|
|
||||||
// ..
|
|
||||||
// } = &results[0];
|
|
||||||
// if *id != self.1 || last_response.is_none() {
|
|
||||||
// return Poll::Pending;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let HandlerResult {
|
|
||||||
// end: last_response,
|
|
||||||
// stream: intermediate_responses,
|
|
||||||
// ..
|
|
||||||
// } = results.pop_front().unwrap();
|
|
||||||
// mem::drop(results);
|
|
||||||
//
|
|
||||||
// Poll::Ready((
|
|
||||||
// last_response.expect("already checked"),
|
|
||||||
// intermediate_responses,
|
|
||||||
// ))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Main listen loop for the application
|
/// Main listen loop for the application
|
||||||
async fn listen<C>(
|
async fn listen<C>(
|
||||||
conn: C,
|
conn: C,
|
||||||
|
@ -324,13 +290,13 @@ where
|
||||||
bail!("connection probably died");
|
bail!("connection probably died");
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("got a new line {:?}", next_line);
|
debug!("[LISTEN] got a new line {:?}", next_line);
|
||||||
let resp = parse_response(next_line)?;
|
let resp = parse_response(next_line)?;
|
||||||
|
|
||||||
// if this is the very first message, treat it as a greeting
|
// if this is the very first message, treat it as a greeting
|
||||||
if let Some(greeting) = greeting.take() {
|
if let Some(greeting) = greeting.take() {
|
||||||
let (greeting, waker) = &mut *greeting.write();
|
let (greeting, waker) = &mut *greeting.write();
|
||||||
debug!("received greeting!");
|
debug!("[LISTEN] received greeting!");
|
||||||
*greeting = Some(resp.clone());
|
*greeting = Some(resp.clone());
|
||||||
if let Some(waker) = waker.take() {
|
if let Some(waker) = waker.take() {
|
||||||
waker.wake();
|
waker.wake();
|
||||||
|
@ -357,9 +323,16 @@ where
|
||||||
} => {
|
} => {
|
||||||
let mut results = results.write();
|
let mut results = results.write();
|
||||||
if let Some(HandlerResult { id, sender, .. }) = results.iter_mut().next() {
|
if let Some(HandlerResult { id, sender, .. }) = results.iter_mut().next() {
|
||||||
debug!("pushed to intermediate for id {}: {:?}", id, resp);
|
// we don't really care if it fails to send
|
||||||
sender.send(resp)?;
|
// this just means that the other side has dropped the channel
|
||||||
|
//
|
||||||
|
// which is fine since that just means they don't care about
|
||||||
|
// intermediate messages
|
||||||
|
let _ = sender.send(resp);
|
||||||
|
debug!("[LISTEN] pushed to intermediate for id {}", id);
|
||||||
}
|
}
|
||||||
|
std::mem::drop(results);
|
||||||
|
debug!("[LISTEN] << unlocked self.results");
|
||||||
}
|
}
|
||||||
|
|
||||||
// bye
|
// bye
|
||||||
|
|
|
@ -52,11 +52,11 @@ pub use self::inner::{Client, ResponseFuture, ResponseStream};
|
||||||
|
|
||||||
/// Struct used to start building the config for a client.
|
/// Struct used to start building the config for a client.
|
||||||
///
|
///
|
||||||
/// Call [`.build`][1] to _build_ the config, then run [`.connect`][2] to actually start opening
|
/// Call [`.build`][1] to _build_ the config, then run [`.open`][2] to actually start opening
|
||||||
/// the connection to the server.
|
/// the connection to the server.
|
||||||
///
|
///
|
||||||
/// [1]: self::ClientConfigBuilder::build
|
/// [1]: self::ClientConfigBuilder::build
|
||||||
/// [2]: self::ClientConfig::connect
|
/// [2]: self::ClientConfig::open
|
||||||
pub type ClientBuilder = ClientConfigBuilder;
|
pub type ClientBuilder = ClientConfigBuilder;
|
||||||
|
|
||||||
/// An IMAP client that hasn't been connected yet.
|
/// An IMAP client that hasn't been connected yet.
|
||||||
|
@ -148,15 +148,33 @@ impl ClientAuthenticated {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the LIST command
|
/// Runs the LIST command
|
||||||
pub async fn list(&mut self) -> Result<()> {
|
pub async fn list(&mut self) -> Result<Vec<String>> {
|
||||||
let cmd = Command::List {
|
let cmd = Command::List {
|
||||||
reference: "".to_owned(),
|
reference: "".to_owned(),
|
||||||
mailbox: "*".to_owned(),
|
mailbox: "*".to_owned(),
|
||||||
};
|
};
|
||||||
let (resp, _) = self.execute(cmd).await?;
|
let (mut resp, mut st) = self.execute(cmd).await?;
|
||||||
let resp = resp.await?;
|
todo!()
|
||||||
debug!("list response: {:?}", resp);
|
|
||||||
Ok(())
|
// let mut folders = Vec::new();
|
||||||
|
// loop {
|
||||||
|
// let st_next = st.recv();
|
||||||
|
// pin_mut!(st_next);
|
||||||
|
|
||||||
|
// match future::select(resp, st_next).await {
|
||||||
|
// Either::Left((v, _)) => {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Either::Right((v, _) ) => {
|
||||||
|
// debug!("RESP: {:?}", v);
|
||||||
|
// // folders.push(v);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let resp = resp.await?;
|
||||||
|
// debug!("list response: {:?}", resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the SELECT command
|
/// Runs the SELECT command
|
||||||
|
@ -164,7 +182,9 @@ impl ClientAuthenticated {
|
||||||
let cmd = Command::Select {
|
let cmd = Command::Select {
|
||||||
mailbox: mailbox.as_ref().to_owned(),
|
mailbox: mailbox.as_ref().to_owned(),
|
||||||
};
|
};
|
||||||
let (resp, _) = self.execute(cmd).await?;
|
let (resp, mut st) = self.execute(cmd).await?;
|
||||||
|
debug!("execute called returned...");
|
||||||
|
debug!("ST: {:?}", st.recv().await);
|
||||||
let resp = resp.await?;
|
let resp = resp.await?;
|
||||||
debug!("select response: {:?}", resp);
|
debug!("select response: {:?}", resp);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use pest::{ParseResult as PestResult, ParserState};
|
use pest::{ParseResult as PestResult, ParserState};
|
||||||
|
|
||||||
use super::old::Rule;
|
use super::Rule;
|
||||||
|
|
||||||
type PSR<'a> = Box<ParserState<'a, Rule>>;
|
type PSR<'a> = Box<ParserState<'a, Rule>>;
|
||||||
|
|
||||||
|
/// This is a hack around the literal syntax to allow us to parse characters statefully.
|
||||||
pub(crate) fn literal_internal(state: PSR) -> PestResult<PSR> {
|
pub(crate) fn literal_internal(state: PSR) -> PestResult<PSR> {
|
||||||
use pest::Atomicity;
|
use pest::Atomicity;
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,532 @@
|
||||||
mod literal;
|
//! Module that implements parsers for all of the IMAP types.
|
||||||
mod old;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
mod literal;
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::mem;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use pest::{error::Error, iterators::Pair, ParseResult as PestResult, Parser, ParserState};
|
||||||
|
|
||||||
use crate::response::*;
|
use crate::response::*;
|
||||||
|
|
||||||
use self::literal::literal_internal;
|
use self::literal::literal_internal;
|
||||||
|
|
||||||
pub fn parse_capability(s: impl AsRef<str>) -> Result<Capability> {
|
#[derive(Parser)]
|
||||||
let s = s.as_ref();
|
#[grammar = "parser/rfc3501.pest"]
|
||||||
if s == "IMAP4rev1" {
|
|
||||||
Ok(Capability::Imap4rev1)
|
struct Rfc3501;
|
||||||
} else if s.to_lowercase().starts_with("AUTH=") {
|
|
||||||
Ok(Capability::Auth(s[5..].to_owned()))
|
pub type ParseResult<T, E = Error<Rule>> = Result<T, E>;
|
||||||
} else {
|
|
||||||
Ok(Capability::Atom(s.to_owned()))
|
pub fn parse_capability(s: impl AsRef<str>) -> ParseResult<Capability> {
|
||||||
|
let mut pairs = Rfc3501::parse(Rule::capability, s.as_ref())?;
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
Ok(build_capability(pair))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_response(s: impl AsRef<str>) -> ParseResult<Response> {
|
||||||
|
let mut pairs = Rfc3501::parse(Rule::response, s.as_ref())?;
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
Ok(build_response(pair))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_response(pair: Pair<Rule>) -> Response {
|
||||||
|
assert!(matches!(pair.as_rule(), Rule::response));
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::response_done => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::response_tagged => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let tag = pair.as_str().to_owned();
|
||||||
|
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let (status, code, information) = build_resp_cond_state(pair);
|
||||||
|
Response::Done {
|
||||||
|
tag,
|
||||||
|
status,
|
||||||
|
code,
|
||||||
|
information,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rule::response_data => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::resp_cond_state => {
|
||||||
|
let (status, code, information) = build_resp_cond_state(pair);
|
||||||
|
Response::Data {
|
||||||
|
status,
|
||||||
|
code,
|
||||||
|
information,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rule::mailbox_data => Response::MailboxData(build_mailbox_data(pair)),
|
||||||
|
Rule::capability_data => Response::Capabilities(build_capabilities(pair)),
|
||||||
|
Rule::message_data => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let seq: u32 = build_number(pair);
|
||||||
|
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::message_data_expunge => Response::Expunge(seq),
|
||||||
|
Rule::message_data_fetch => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let msg_att = pairs.next().unwrap();
|
||||||
|
let attrs = msg_att.into_inner().map(build_msg_att).collect();
|
||||||
|
Response::Fetch(seq, attrs)
|
||||||
|
}
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rule::continue_req => {
|
||||||
|
let (code, s) = build_resp_text(unwrap1(pair));
|
||||||
|
Response::Continue {
|
||||||
|
code,
|
||||||
|
information: Some(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_response(s: impl AsRef<str>) -> Result<Response> {
|
fn build_resp_text(pair: Pair<Rule>) -> (Option<ResponseCode>, String) {
|
||||||
let s = s.as_ref();
|
assert!(matches!(pair.as_rule(), Rule::resp_text));
|
||||||
let mut parts = s.split(' ');
|
let mut pairs = pair.into_inner();
|
||||||
let tag = parts.next().unwrap();
|
let mut pair = pairs.next().unwrap();
|
||||||
todo!()
|
let mut resp_code = None;
|
||||||
|
if let Rule::resp_text_code = pair.as_rule() {
|
||||||
|
resp_code = build_resp_text_code(pair);
|
||||||
|
pair = pairs.next().unwrap();
|
||||||
|
}
|
||||||
|
assert!(matches!(pair.as_rule(), Rule::text));
|
||||||
|
let s = pair.as_str().to_owned();
|
||||||
|
(resp_code, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_msg_att(pair: Pair<Rule>) -> AttributeValue {
|
||||||
|
if !matches!(pair.as_rule(), Rule::msg_att_dyn_or_stat) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::msg_att_dynamic => AttributeValue::Flags(pair.into_inner().map(build_flag).collect()),
|
||||||
|
Rule::msg_att_static => build_msg_att_static(pair),
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_msg_att_static(pair: Pair<Rule>) -> AttributeValue {
|
||||||
|
if !matches!(pair.as_rule(), Rule::msg_att_static) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::msg_att_static_internaldate => {
|
||||||
|
AttributeValue::InternalDate(build_string(unwrap1(pair)))
|
||||||
|
}
|
||||||
|
Rule::msg_att_static_rfc822_size => AttributeValue::Rfc822Size(build_number(unwrap1(pair))),
|
||||||
|
Rule::msg_att_static_envelope => AttributeValue::Envelope(build_envelope(unwrap1(pair))),
|
||||||
|
// TODO: do this
|
||||||
|
Rule::msg_att_static_body => AttributeValue::BodySection {
|
||||||
|
section: None,
|
||||||
|
index: None,
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_envelope(_pair: Pair<Rule>) -> Envelope {
|
||||||
|
// TODO: do this
|
||||||
|
Envelope::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_resp_cond_state(pair: Pair<Rule>) -> (Status, Option<ResponseCode>, Option<String>) {
|
||||||
|
if !matches!(pair.as_rule(), Rule::resp_cond_state) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let status = build_status(pair);
|
||||||
|
let mut code = None;
|
||||||
|
let mut information = None;
|
||||||
|
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let pairs = pair.into_inner();
|
||||||
|
for pair in pairs {
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::resp_text_code => code = build_resp_text_code(pair),
|
||||||
|
Rule::text => information = Some(pair.as_str().to_owned()),
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(status, code, information)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_resp_text_code(pair: Pair<Rule>) -> Option<ResponseCode> {
|
||||||
|
if !matches!(pair.as_rule(), Rule::resp_text_code) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next()?;
|
||||||
|
Some(match pair.as_rule() {
|
||||||
|
Rule::capability_data => ResponseCode::Capabilities(build_capabilities(pair)),
|
||||||
|
Rule::resp_text_code_readwrite => ResponseCode::ReadWrite,
|
||||||
|
Rule::resp_text_code_uidvalidity => ResponseCode::UidValidity(build_number(unwrap1(pair))),
|
||||||
|
Rule::resp_text_code_uidnext => ResponseCode::UidNext(build_number(unwrap1(pair))),
|
||||||
|
Rule::resp_text_code_unseen => ResponseCode::Unseen(build_number(unwrap1(pair))),
|
||||||
|
// TODO: maybe have an actual type for these flags instead of just string
|
||||||
|
Rule::resp_text_code_permanentflags => {
|
||||||
|
ResponseCode::PermanentFlags(pair.into_inner().map(|p| p.as_str().to_owned()).collect())
|
||||||
|
}
|
||||||
|
Rule::resp_text_code_other => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let a = pair.as_str().to_owned();
|
||||||
|
let mut b = None;
|
||||||
|
if let Some(pair) = pairs.next() {
|
||||||
|
b = Some(pair.as_str().to_owned());
|
||||||
|
}
|
||||||
|
ResponseCode::Other(a, b)
|
||||||
|
}
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_capability(pair: Pair<Rule>) -> Capability {
|
||||||
|
if !matches!(pair.as_rule(), Rule::capability) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::auth_type => Capability::Auth(pair.as_str().to_uppercase().to_owned()),
|
||||||
|
Rule::atom => match pair.as_str() {
|
||||||
|
"IMAP4rev1" => Capability::Imap4rev1,
|
||||||
|
s => Capability::Atom(s.to_uppercase().to_owned()),
|
||||||
|
},
|
||||||
|
_ => unreachable!("{:?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_capabilities(pair: Pair<Rule>) -> Vec<Capability> {
|
||||||
|
if !matches!(pair.as_rule(), Rule::capability_data) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
pair.into_inner().map(build_capability).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_status(pair: Pair<Rule>) -> Status {
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::resp_status => match pair.as_str().to_uppercase().as_str() {
|
||||||
|
"OK" => Status::Ok,
|
||||||
|
"NO" => Status::No,
|
||||||
|
"BAD" => Status::Bad,
|
||||||
|
s => unreachable!("invalid status {:?}", s),
|
||||||
|
},
|
||||||
|
_ => unreachable!("{:?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_flag_list(pair: Pair<Rule>) -> Vec<MailboxFlag> {
|
||||||
|
if !matches!(pair.as_rule(), Rule::flag_list) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
pair.into_inner().map(build_flag).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_flag(mut pair: Pair<Rule>) -> MailboxFlag {
|
||||||
|
if matches!(pair.as_rule(), Rule::flag_fetch) {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
pair = pairs.next().unwrap();
|
||||||
|
|
||||||
|
if matches!(pair.as_rule(), Rule::flag_fetch_recent) {
|
||||||
|
return MailboxFlag::Recent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matches!(pair.as_rule(), Rule::flag) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
match pair.as_str() {
|
||||||
|
"\\Answered" => MailboxFlag::Answered,
|
||||||
|
"\\Flagged" => MailboxFlag::Flagged,
|
||||||
|
"\\Deleted" => MailboxFlag::Deleted,
|
||||||
|
"\\Seen" => MailboxFlag::Seen,
|
||||||
|
"\\Draft" => MailboxFlag::Draft,
|
||||||
|
// s if s.starts_with("\\") => MailboxFlag::Ext(s.to_owned()),
|
||||||
|
// TODO: what??
|
||||||
|
s => MailboxFlag::Ext(s.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_mailbox_data(pair: Pair<Rule>) -> MailboxData {
|
||||||
|
if !matches!(pair.as_rule(), Rule::mailbox_data) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::mailbox_data_exists => MailboxData::Exists(build_number(unwrap1(pair))),
|
||||||
|
Rule::mailbox_data_flags => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let flags = build_flag_list(pair);
|
||||||
|
MailboxData::Flags(flags)
|
||||||
|
}
|
||||||
|
Rule::mailbox_data_recent => MailboxData::Recent(build_number(unwrap1(pair))),
|
||||||
|
Rule::mailbox_data_list => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let (flags, delimiter, name) = build_mailbox_list(pair);
|
||||||
|
MailboxData::List {
|
||||||
|
flags,
|
||||||
|
delimiter,
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_mailbox_list(pair: Pair<Rule>) -> (Vec<String>, Option<String>, String) {
|
||||||
|
if !matches!(pair.as_rule(), Rule::mailbox_list) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let mut pair = pairs.next().unwrap();
|
||||||
|
|
||||||
|
// let mut flags = Vec::new();
|
||||||
|
let flags = if let Rule::mailbox_list_flags = pair.as_rule() {
|
||||||
|
let pairs_ = pair.into_inner();
|
||||||
|
let mut flags = Vec::new();
|
||||||
|
for pair in pairs_ {
|
||||||
|
flags.extend(build_mbx_list_flags(pair));
|
||||||
|
}
|
||||||
|
pair = pairs.next().unwrap();
|
||||||
|
flags
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(matches!(pair.as_rule(), Rule::mailbox_list_string));
|
||||||
|
let s = build_nstring(pair);
|
||||||
|
|
||||||
|
pair = pairs.next().unwrap();
|
||||||
|
assert!(matches!(pair.as_rule(), Rule::mailbox));
|
||||||
|
let mailbox = build_string(pair);
|
||||||
|
|
||||||
|
(flags, s, mailbox)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_mbx_list_flags(pair: Pair<Rule>) -> Vec<String> {
|
||||||
|
assert!(matches!(pair.as_rule(), Rule::mbx_list_flags));
|
||||||
|
pair.into_inner()
|
||||||
|
.map(|pair| pair.as_str().to_owned())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unwraps a singleton pair (a pair that only has one element in its `inner` list)
|
||||||
|
fn unwrap1(pair: Pair<Rule>) -> Pair<Rule> {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
pairs.next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a numerical type, generic over anything that could possibly be read as a number
|
||||||
|
// TODO: should probably restrict this to a few cases
|
||||||
|
fn build_number<T>(pair: Pair<Rule>) -> T
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
T::Err: Debug,
|
||||||
|
{
|
||||||
|
if !matches!(pair.as_rule(), Rule::nz_number | Rule::number) {
|
||||||
|
unreachable!("not a number {:#?}", pair);
|
||||||
|
}
|
||||||
|
pair.as_str().parse::<T>().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper around [build_string][1], except return None for the `nil` case
|
||||||
|
///
|
||||||
|
/// [1]: self::build_string
|
||||||
|
fn build_nstring(pair: Pair<Rule>) -> Option<String> {
|
||||||
|
if matches!(pair.as_rule(), Rule::nil) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(build_string(pair))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a string-type, discarding the surrounding quotes and unescaping the escaped characters
|
||||||
|
fn build_string(pair: Pair<Rule>) -> String {
|
||||||
|
// TODO: actually get rid of the quotes and escaped chars
|
||||||
|
pair.as_str().to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_literal(s: impl AsRef<str>) -> ParseResult<String> {
|
||||||
|
let mut pairs = Rfc3501::parse(Rule::literal, s.as_ref())?;
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
Ok(build_literal(pair))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_literal(pair: Pair<Rule>) -> String {
|
||||||
|
assert!(matches!(pair.as_rule(), Rule::literal));
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let _ = pairs.next().unwrap();
|
||||||
|
let literal_str = pairs.next().unwrap();
|
||||||
|
literal_str.as_str().to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::response::*;
|
||||||
|
use pest::Parser;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_literal() {
|
||||||
|
assert_eq!(parse_literal("{7}\r\nhellosu"), Ok("hellosu".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn test_capability() {
|
||||||
|
assert_eq!(parse_capability("IMAP4rev1"), Ok(Capability::Imap4rev1));
|
||||||
|
assert_eq!(parse_capability("LOGINDISABLED"), Ok(Capability::Atom("LOGINDISABLED".to_owned())));
|
||||||
|
assert_eq!(parse_capability("AUTH=PLAIN"), Ok(Capability::Auth("PLAIN".to_owned())));
|
||||||
|
assert_eq!(parse_capability("auth=plain"), Ok(Capability::Auth("PLAIN".to_owned())));
|
||||||
|
|
||||||
|
assert!(parse_capability("(OSU)").is_err());
|
||||||
|
assert!(parse_capability("\x01HELLO").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn test_nil() {
|
||||||
|
assert!(Rfc3501::parse(Rule::nil, "NIL").is_ok());
|
||||||
|
assert!(Rfc3501::parse(Rule::nil, "anything else").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_section_8() {
|
||||||
|
// this little exchange is from section 8 of rfc3501
|
||||||
|
// https://tools.ietf.org/html/rfc3501#section-8
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* OK IMAP4rev1 Service Ready\r\n"),
|
||||||
|
Ok(Response::Data {
|
||||||
|
status: Status::Ok,
|
||||||
|
code: None,
|
||||||
|
information: Some("IMAP4rev1 Service Ready".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("a001 OK LOGIN completed\r\n"),
|
||||||
|
Ok(Response::Done {
|
||||||
|
tag: "a001".to_owned(),
|
||||||
|
status: Status::Ok,
|
||||||
|
code: None,
|
||||||
|
information: Some("LOGIN completed".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* 18 EXISTS\r\n"),
|
||||||
|
Ok(Response::MailboxData(MailboxData::Exists(18)))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"),
|
||||||
|
Ok(Response::MailboxData(MailboxData::Flags(vec![
|
||||||
|
MailboxFlag::Answered,
|
||||||
|
MailboxFlag::Flagged,
|
||||||
|
MailboxFlag::Deleted,
|
||||||
|
MailboxFlag::Seen,
|
||||||
|
MailboxFlag::Draft,
|
||||||
|
])))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* 2 RECENT\r\n"),
|
||||||
|
Ok(Response::MailboxData(MailboxData::Recent(2)))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* OK [UNSEEN 17] Message 17 is the first unseen message\r\n"),
|
||||||
|
Ok(Response::Data {
|
||||||
|
status: Status::Ok,
|
||||||
|
code: Some(ResponseCode::Unseen(17)),
|
||||||
|
information: Some("Message 17 is the first unseen message".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* OK [UIDVALIDITY 3857529045] UIDs valid\r\n"),
|
||||||
|
Ok(Response::Data {
|
||||||
|
status: Status::Ok,
|
||||||
|
code: Some(ResponseCode::UidValidity(3857529045)),
|
||||||
|
information: Some("UIDs valid".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("a002 OK [READ-WRITE] SELECT completed\r\n"),
|
||||||
|
Ok(Response::Done {
|
||||||
|
tag: "a002".to_owned(),
|
||||||
|
status: Status::Ok,
|
||||||
|
code: Some(ResponseCode::ReadWrite),
|
||||||
|
information: Some("SELECT completed".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response(concat!(
|
||||||
|
r#"* 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" "IMAP4rev1 WG mtg summary and minutes" (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) ((NIL NIL "imap" "cac.washington.edu")) ((NIL NIL "minutes" "CNRI.Reston.VA.US")("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL "<B27397-0100000@cac.washington.edu>") BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 302892))"#,
|
||||||
|
"\r\n",
|
||||||
|
)),
|
||||||
|
Ok(Response::Fetch(
|
||||||
|
12,
|
||||||
|
vec![
|
||||||
|
AttributeValue::Flags(vec![MailboxFlag::Seen]),
|
||||||
|
AttributeValue::InternalDate("\"17-Jul-1996 02:44:25 -0700\"".to_owned()),
|
||||||
|
AttributeValue::Rfc822Size(4286),
|
||||||
|
AttributeValue::Envelope(Envelope::default()),
|
||||||
|
AttributeValue::BodySection {
|
||||||
|
section: None,
|
||||||
|
index: None,
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
//! Module that implements parsers for all of the IMAP types.
|
//! Module that implements parsers for all of the IMAP types.
|
||||||
|
|
||||||
|
mod literal;
|
||||||
|
mod old;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
|
@ -30,7 +30,10 @@ pub enum MailCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Possible events returned from the server that should be sent to the UI
|
/// Possible events returned from the server that should be sent to the UI
|
||||||
pub enum MailEvent {}
|
pub enum MailEvent {
|
||||||
|
/// Got the list of folders
|
||||||
|
FolderList(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
/// Main entrypoint for the mail listener.
|
/// Main entrypoint for the mail listener.
|
||||||
pub async fn run_mail(
|
pub async fn run_mail(
|
||||||
|
@ -63,7 +66,7 @@ pub async fn run_mail(
|
||||||
|
|
||||||
// this loop is to make sure accounts are restarted on error
|
// this loop is to make sure accounts are restarted on error
|
||||||
loop {
|
loop {
|
||||||
match imap_main(acct.clone()).await {
|
match imap_main(acct.clone(), mail2ui_tx.clone()).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("IMAP Error: {}", err);
|
error!("IMAP Error: {}", err);
|
||||||
|
@ -88,7 +91,7 @@ pub async fn run_mail(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The main sequence of steps for the IMAP thread to follow
|
/// The main sequence of steps for the IMAP thread to follow
|
||||||
async fn imap_main(acct: MailAccountConfig) -> Result<()> {
|
async fn imap_main(acct: MailAccountConfig, mail2ui_tx: UnboundedSender<MailEvent>) -> Result<()> {
|
||||||
// loop ensures that the connection is retried after it dies
|
// loop ensures that the connection is retried after it dies
|
||||||
loop {
|
loop {
|
||||||
let builder: ClientConfig = ClientBuilder::default()
|
let builder: ClientConfig = ClientBuilder::default()
|
||||||
|
@ -129,8 +132,10 @@ async fn imap_main(acct: MailAccountConfig) -> Result<()> {
|
||||||
authed.select("INBOX").await?;
|
authed.select("INBOX").await?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
debug!("listing all emails...");
|
debug!("listing all mailboxes...");
|
||||||
let folder_tree = authed.list().await?;
|
let folder_list = authed.list().await?;
|
||||||
|
debug!("mailbox list: {:?}", folder_list);
|
||||||
|
mail2ui_tx.send(MailEvent::FolderList(folder_list));
|
||||||
|
|
||||||
let mut idle_stream = authed.idle().await?;
|
let mut idle_stream = authed.idle().await?;
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ fn setup_logger(log_file: Option<impl AsRef<Path>>) -> Result<()> {
|
||||||
message
|
message
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.level(log::LevelFilter::Debug);
|
.level(log::LevelFilter::Trace);
|
||||||
if let Some(log_file) = log_file {
|
if let Some(log_file) = log_file {
|
||||||
logger = logger.chain(fern::log_file(log_file)?);
|
logger = logger.chain(fern::log_file(log_file)?);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue