mailbox_data

This commit is contained in:
Michael Zhang 2021-02-22 17:23:13 -06:00
parent fed8031f5c
commit 1276ead25f
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
3 changed files with 140 additions and 36 deletions

View file

@ -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<Capability, Error<Rule>> {
let mut pairs = Rfc3501::parse(Rule::capability, s)?;
pub fn parse_capability(s: impl AsRef<str>) -> Result<Capability, Error<Rule>> {
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<Capability, Error<Rule>> {
Ok(cap)
}
pub fn parse_response(s: &str) -> Result<Response, Error<Rule>> {
let mut pairs = Rfc3501::parse(Rule::response, s)?;
pub fn parse_response(s: impl AsRef<str>) -> Result<Response, Error<Rule>> {
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 {
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<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;
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<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),
}
}
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<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 => {
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
let number = pair.as_str().parse::<u32>().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))));
}
}

View file

@ -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 = @{ "\"" }

View file

@ -24,7 +24,7 @@ pub enum Response {
uids: Vec<RangeInclusive<u32>>,
},
Fetch(u32, Vec<AttributeValue>),
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<String>),
List {
flags: Vec<String>,
delimiter: Option<String>,
name: String,
},
Search(Vec<u32>),
Status {
mailbox: String,
status: Vec<StatusAttribute>,
},
Recent(u32),
MetadataSolicited {
mailbox: String,
values: Vec<Metadata>,
},
MetadataUnsolicited {
mailbox: String,
values: Vec<String>,
},
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Metadata {
pub entry: String,
pub value: Option<String>,
}
#[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 {