From 1276ead25f0e4e0e2137511f0ab93aa6b3b02122 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 22 Feb 2021 17:23:13 -0600 Subject: [PATCH] mailbox_data --- imap/src/parser/mod.rs | 128 ++++++++++++++++++++++++++--------- imap/src/parser/rfc3501.pest | 5 +- imap/src/response/mod.rs | 43 +++++++++++- 3 files changed, 140 insertions(+), 36 deletions(-) diff --git a/imap/src/parser/mod.rs b/imap/src/parser/mod.rs index 2c02b9a..16575ba 100644 --- a/imap/src/parser/mod.rs +++ b/imap/src/parser/mod.rs @@ -1,4 +1,8 @@ -use pest::{error::Error, Parser, iterators::{Pair, Pairs}}; +use pest::{ + error::Error, + iterators::{Pair, Pairs}, + Parser, +}; use crate::response::*; @@ -6,8 +10,8 @@ use crate::response::*; #[grammar = "parser/rfc3501.pest"] struct Rfc3501; -pub fn parse_capability(s: &str) -> Result> { - let mut pairs = Rfc3501::parse(Rule::capability, s)?; +pub fn parse_capability(s: impl AsRef) -> Result> { + let mut pairs = Rfc3501::parse(Rule::capability, s.as_ref())?; let pair = pairs.next().unwrap(); let cap = match pair.as_rule() { Rule::capability => { @@ -27,60 +31,111 @@ pub fn parse_capability(s: &str) -> Result> { Ok(cap) } -pub fn parse_response(s: &str) -> Result> { - let mut pairs = Rfc3501::parse(Rule::response, s)?; +pub fn parse_response(s: impl AsRef) -> Result> { + let mut pairs = Rfc3501::parse(Rule::response, s.as_ref())?; let pair = pairs.next().unwrap(); Ok(build_response(pair)) } fn build_response(pair: Pair) -> Response { + if !matches!(pair.as_rule(), Rule::response) { + unreachable!("{:#?}", pair); + } + + let mut pairs = pair.into_inner(); + let pair = pairs.next().unwrap(); match pair.as_rule() { - Rule::response => { + Rule::response_done => { let mut pairs = pair.into_inner(); let pair = pairs.next().unwrap(); match pair.as_rule() { - Rule::response_data => { + Rule::response_tagged => { let mut pairs = pair.into_inner(); let pair = pairs.next().unwrap(); - match pair.as_rule() { - Rule::resp_cond_state => { - 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 tag = pair.as_str().to_owned(); - for pair in pairs { - if let resp_text = pair.as_rule() { - information = Some(pair.as_str().to_owned()); - } - } - Response::Data { status, code, information } - } - _ => unreachable!("{:?}", pair), + let pair = pairs.next().unwrap(); + let (status, code, information) = build_resp_cond_state(pair); + Response::Done { + tag, + status, + code, + information, } } - _ => unreachable!("{:?}", pair), + _ => unreachable!("{:#?}", pair), } } - _ => 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)), + _ => unreachable!("{:#?}", pair), + } + } + _ => unreachable!("{:#?}", pair), } } +fn build_resp_cond_state(pair: Pair) -> (Status, Option, Option) { + 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; + + for pair in pairs { + match pair.as_rule() { + Rule::resp_text => information = Some(pair.as_str().to_owned()), + _ => unreachable!("{:#?}", pair), + } + } + (status, code, information) +} + fn build_status(pair: Pair) -> 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), - } - } + 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_mailbox_data(pair: Pair) -> 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 => { + let mut pairs = pair.into_inner(); + let pair = pairs.next().unwrap(); + let number = pair.as_str().parse::().unwrap(); + MailboxData::Exists(number) + } + _ => unreachable!("{:#?}", pair), + } +} + #[cfg(test)] #[rustfmt::skip] mod tests { @@ -115,5 +170,14 @@ mod tests { 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)))); } } diff --git a/imap/src/parser/rfc3501.pest b/imap/src/parser/rfc3501.pest index b9b6270..69e38a3 100644 --- a/imap/src/parser/rfc3501.pest +++ b/imap/src/parser/rfc3501.pest @@ -64,7 +64,8 @@ header_list = { "(" ~ header_fld_name ~ (sp ~ header_fld_name)* ~ ")" } list_wildcards = @{ "%" | "*" } literal = @{ "{" ~ number ~ "}" ~ crlf ~ char8* } mailbox = { ^"INBOX" | astring } -mailbox_data = { (^"FLAGS" ~ sp ~ flag_list) | (^"LIST" ~ sp ~ mailbox_list) | (^"LSUB" ~ sp ~ mailbox_list) | (^"SEARCH" ~ (sp ~ nz_number)*) | (^"STATUS" ~ sp ~ mailbox ~ sp ~ ^"(" ~ status_att_list? ~ ^")") | (number ~ sp ~ ^"EXISTS") | (number ~ sp ~ ^"RECENT") } +mailbox_data = { (^"FLAGS" ~ sp ~ flag_list) | (^"LIST" ~ sp ~ mailbox_list) | (^"LSUB" ~ sp ~ mailbox_list) | (^"SEARCH" ~ (sp ~ nz_number)*) | (^"STATUS" ~ sp ~ mailbox ~ sp ~ ^"(" ~ status_att_list? ~ ^")") | mailbox_data_exists | (number ~ sp ~ ^"RECENT") } +mailbox_data_exists = { number ~ sp ~ ^"EXISTS" } mailbox_list = { "(" ~ mbx_list_flags* ~ ")" ~ sp ~ (dquote ~ quoted_char ~ dquote | nil) ~ sp ~ mailbox } mbx_list_flags = { (mbx_list_oflag ~ sp)* ~ mbx_list_sflag ~ (sp ~ mbx_list_oflag)* | mbx_list_oflag ~ (sp ~ mbx_list_oflag)* } mbx_list_oflag = { "\\NoInferiors" | flag_extension } @@ -116,7 +117,7 @@ zone = { ("+" | "-") ~ digit{4} } alpha = @{ '\x41'..'\x5a' | '\x61'..'\x7a' } char = @{ '\x01'..'\x7f' } cr = @{ "\x0d" } -crlf = @{ cr ~ lf } +crlf = _{ cr ~ lf } ctl = @{ '\x00'..'\x1f' | "\x7f" } digit = @{ '\x30'..'\x39' } dquote = @{ "\"" } diff --git a/imap/src/response/mod.rs b/imap/src/response/mod.rs index b8be675..026f650 100644 --- a/imap/src/response/mod.rs +++ b/imap/src/response/mod.rs @@ -24,7 +24,7 @@ pub enum Response { uids: Vec>, }, Fetch(u32, Vec), - MailboxData(MailboxDatum), + MailboxData(MailboxData), } #[derive(Clone, Debug, Hash, PartialEq, Eq)] @@ -63,7 +63,46 @@ pub enum UidSetMember { pub enum AttributeValue {} #[derive(Clone, Debug, PartialEq, Eq)] -pub enum MailboxDatum {} +pub enum MailboxData { + Exists(u32), + Flags(Vec), + List { + flags: Vec, + delimiter: Option, + name: String, + }, + Search(Vec), + Status { + mailbox: String, + status: Vec, + }, + Recent(u32), + MetadataSolicited { + mailbox: String, + values: Vec, + }, + MetadataUnsolicited { + mailbox: String, + values: Vec, + }, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Metadata { + pub entry: String, + pub value: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum StatusAttribute { + HighestModSeq(u64), // RFC 4551 + Messages(u32), + Recent(u32), + UidNext(u32), + UidValidity(u32), + Unseen(u32), +} #[derive(Clone, Debug, Eq, PartialEq)] pub enum Status {