mailbox_data
This commit is contained in:
parent
fed8031f5c
commit
1276ead25f
3 changed files with 140 additions and 36 deletions
|
@ -1,4 +1,8 @@
|
||||||
use pest::{error::Error, Parser, iterators::{Pair, Pairs}};
|
use pest::{
|
||||||
|
error::Error,
|
||||||
|
iterators::{Pair, Pairs},
|
||||||
|
Parser,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::response::*;
|
use crate::response::*;
|
||||||
|
|
||||||
|
@ -6,8 +10,8 @@ use crate::response::*;
|
||||||
#[grammar = "parser/rfc3501.pest"]
|
#[grammar = "parser/rfc3501.pest"]
|
||||||
struct Rfc3501;
|
struct Rfc3501;
|
||||||
|
|
||||||
pub fn parse_capability(s: &str) -> Result<Capability, Error<Rule>> {
|
pub fn parse_capability(s: impl AsRef<str>) -> Result<Capability, Error<Rule>> {
|
||||||
let mut pairs = Rfc3501::parse(Rule::capability, s)?;
|
let mut pairs = Rfc3501::parse(Rule::capability, s.as_ref())?;
|
||||||
let pair = pairs.next().unwrap();
|
let pair = pairs.next().unwrap();
|
||||||
let cap = match pair.as_rule() {
|
let cap = match pair.as_rule() {
|
||||||
Rule::capability => {
|
Rule::capability => {
|
||||||
|
@ -27,23 +31,66 @@ pub fn parse_capability(s: &str) -> Result<Capability, Error<Rule>> {
|
||||||
Ok(cap)
|
Ok(cap)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_response(s: &str) -> Result<Response, Error<Rule>> {
|
pub fn parse_response(s: impl AsRef<str>) -> Result<Response, Error<Rule>> {
|
||||||
let mut pairs = Rfc3501::parse(Rule::response, s)?;
|
let mut pairs = Rfc3501::parse(Rule::response, s.as_ref())?;
|
||||||
let pair = pairs.next().unwrap();
|
let pair = pairs.next().unwrap();
|
||||||
Ok(build_response(pair))
|
Ok(build_response(pair))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_response(pair: Pair<Rule>) -> Response {
|
fn build_response(pair: Pair<Rule>) -> Response {
|
||||||
match pair.as_rule() {
|
if !matches!(pair.as_rule(), Rule::response) {
|
||||||
Rule::response => {
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
let mut pairs = pair.into_inner();
|
let mut pairs = pair.into_inner();
|
||||||
let pair = pairs.next().unwrap();
|
let pair = pairs.next().unwrap();
|
||||||
match pair.as_rule() {
|
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 => {
|
Rule::response_data => {
|
||||||
let mut pairs = pair.into_inner();
|
let mut pairs = pair.into_inner();
|
||||||
let pair = pairs.next().unwrap();
|
let pair = pairs.next().unwrap();
|
||||||
match pair.as_rule() {
|
match pair.as_rule() {
|
||||||
Rule::resp_cond_state => {
|
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 mut pairs = pair.into_inner();
|
||||||
let pair = pairs.next().unwrap();
|
let pair = pairs.next().unwrap();
|
||||||
let status = build_status(pair);
|
let status = build_status(pair);
|
||||||
|
@ -51,36 +98,44 @@ fn build_response(pair: Pair<Rule>) -> Response {
|
||||||
let mut information = None;
|
let mut information = None;
|
||||||
|
|
||||||
for pair in pairs {
|
for pair in pairs {
|
||||||
if let resp_text = pair.as_rule() {
|
match pair.as_rule() {
|
||||||
information = Some(pair.as_str().to_owned());
|
Rule::resp_text => information = Some(pair.as_str().to_owned()),
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Response::Data { status, code, information }
|
(status, code, information)
|
||||||
}
|
|
||||||
_ => unreachable!("{:?}", pair),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!("{:?}", pair),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!("{:?}", pair),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_status(pair: Pair<Rule>) -> Status {
|
fn build_status(pair: Pair<Rule>) -> Status {
|
||||||
match pair.as_rule() {
|
match pair.as_rule() {
|
||||||
Rule::resp_status => {
|
Rule::resp_status => match pair.as_str().to_uppercase().as_str() {
|
||||||
match pair.as_str().to_uppercase().as_str() {
|
|
||||||
"OK" => Status::Ok,
|
"OK" => Status::Ok,
|
||||||
"NO" => Status::No,
|
"NO" => Status::No,
|
||||||
"BAD" => Status::Bad,
|
"BAD" => Status::Bad,
|
||||||
s => unreachable!("invalid status {:?}", s),
|
s => unreachable!("invalid status {:?}", s),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
_ => unreachable!("{:?}", pair),
|
_ => 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)]
|
#[cfg(test)]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -115,5 +170,14 @@ mod tests {
|
||||||
code: None,
|
code: None,
|
||||||
information: Some("IMAP4rev1 Service Ready".to_owned()),
|
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))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,8 @@ header_list = { "(" ~ header_fld_name ~ (sp ~ header_fld_name)* ~ ")" }
|
||||||
list_wildcards = @{ "%" | "*" }
|
list_wildcards = @{ "%" | "*" }
|
||||||
literal = @{ "{" ~ number ~ "}" ~ crlf ~ char8* }
|
literal = @{ "{" ~ number ~ "}" ~ crlf ~ char8* }
|
||||||
mailbox = { ^"INBOX" | astring }
|
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 }
|
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_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 }
|
mbx_list_oflag = { "\\NoInferiors" | flag_extension }
|
||||||
|
@ -116,7 +117,7 @@ zone = { ("+" | "-") ~ digit{4} }
|
||||||
alpha = @{ '\x41'..'\x5a' | '\x61'..'\x7a' }
|
alpha = @{ '\x41'..'\x5a' | '\x61'..'\x7a' }
|
||||||
char = @{ '\x01'..'\x7f' }
|
char = @{ '\x01'..'\x7f' }
|
||||||
cr = @{ "\x0d" }
|
cr = @{ "\x0d" }
|
||||||
crlf = @{ cr ~ lf }
|
crlf = _{ cr ~ lf }
|
||||||
ctl = @{ '\x00'..'\x1f' | "\x7f" }
|
ctl = @{ '\x00'..'\x1f' | "\x7f" }
|
||||||
digit = @{ '\x30'..'\x39' }
|
digit = @{ '\x30'..'\x39' }
|
||||||
dquote = @{ "\"" }
|
dquote = @{ "\"" }
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub enum Response {
|
||||||
uids: Vec<RangeInclusive<u32>>,
|
uids: Vec<RangeInclusive<u32>>,
|
||||||
},
|
},
|
||||||
Fetch(u32, Vec<AttributeValue>),
|
Fetch(u32, Vec<AttributeValue>),
|
||||||
MailboxData(MailboxDatum),
|
MailboxData(MailboxData),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
@ -63,7 +63,46 @@ pub enum UidSetMember {
|
||||||
pub enum AttributeValue {}
|
pub enum AttributeValue {}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[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)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
|
|
Loading…
Reference in a new issue