add rfc3501 rules that relate to response parsing

This commit is contained in:
Michael Zhang 2021-02-22 16:32:00 -06:00
parent e0ca51ef79
commit 875041edfd
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
2 changed files with 116 additions and 4 deletions

View file

@ -14,10 +14,10 @@ pub fn parse_capability(s: &str) -> Result<Capability, Error<Rule>> {
let mut inner = pair.into_inner();
let pair = inner.next().unwrap();
match pair.as_rule() {
Rule::auth_type => Capability::Auth(pair.as_str().to_owned()),
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_owned()),
s => Capability::Atom(s.to_uppercase().to_owned()),
},
_ => unreachable!("{:?}", pair),
}
@ -27,6 +27,10 @@ pub fn parse_capability(s: &str) -> Result<Capability, Error<Rule>> {
Ok(cap)
}
pub fn parse_response(s: &str) -> Result<Response, Error<Rule>> {
todo!()
}
#[cfg(test)]
#[rustfmt::skip]
mod tests {
@ -39,6 +43,7 @@ mod tests {
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());

View file

@ -1,17 +1,124 @@
// formal syntax from https://tools.ietf.org/html/rfc3501#section-9
addr_adl = { nstring }
addr_host = { nstring }
addr_mailbox = { nstring }
addr_name = { nstring }
address = { "(" ~ addr_name ~ sp ~ addr_adl ~ sp ~ addr_mailbox ~ sp ~ addr_host ~ ")" }
astring = @{ astring_char{1,} | string }
astring_char = @{ atom_char | resp_specials }
atom = @{ atom_char{1,} }
atom_char = @{ !atom_specials ~ char }
atom_specials = @{ "(" | ")" | "{" | sp | ctl | list_wildcards | quoted_specials | resp_specials }
auth_type = { atom }
capability = ${ "AUTH=" ~ auth_type | atom }
base64 = @{ (base64_char{4})* ~ base64_terminal }
base64_char = @{ alpha | digit | "+" | "/" }
base64_terminal = @{ (base64_char{2} ~ "==") | (base64_char{3} ~ "=") }
body = { "(" ~ (body_type_1part | body_type_mpart) ~ ")" }
body_ext_1part = { body_fld_md5 ~ (sp ~ body_fld_dsp ~ (sp ~ body_fld_lang ~ (sp ~ body_fld_loc ~ (sp ~ body_extension)*)?)?)? }
body_ext_mpart = { body_fld_param ~ (sp ~ body_fld_dsp ~ (sp ~ body_fld_lang ~ (sp ~ body_fld_loc ~ (sp ~ body_extension)*)?)?)? }
body_extension = { nstring | number | "(" ~ body_extension ~ (sp ~ body_extension)* ~ ")" }
body_fields = { body_fld_param ~ sp ~ body_fld_id ~ sp ~ body_fld_desc ~ sp ~ body_fld_enc ~ sp ~ body_fld_octets }
body_fld_desc = { nstring }
body_fld_dsp = { "(" ~ string ~ sp ~ body_fld_param ~ ")" | nil }
body_fld_enc = { (dquote ~ (^"7BIT" | ^"8BIT" | ^"BINARY" | ^"BASE64" | ^"QUOTED-PRINTABLE") ~ dquote) | string }
body_fld_id = { nstring }
body_fld_lang = { nstring | "(" ~ string ~ (sp ~ string)* ~ ")" }
body_fld_lines = { number }
body_fld_loc = { nstring }
body_fld_md5 = { nstring }
body_fld_octets = { number }
body_fld_param = { "(" ~ string ~ sp ~ string ~ (sp ~ string ~ sp ~ string)* ~ ")" | nil}
body_type_1part = { (body_type_basic | body_type_msg | body_type_text) ~ (sp ~ body_ext_1part)? }
body_type_basic = { media_basic ~ sp ~ body_fields }
body_type_mpart = { body{1,} ~ sp ~ media_subtype ~ (sp ~ body_ext_mpart)? }
body_type_msg = { media_message ~ sp ~ body_fields ~ sp ~ envelope ~ sp ~ body ~ sp ~ body_fld_lines }
body_type_text = { media_text ~ sp ~ body_fields ~ sp ~ body_fld_lines }
capability = ${ ^"AUTH=" ~ auth_type | atom }
capability_data = { ^"CAPABILITY" ~ (sp ~ capability)* ~ sp ~ "IMAP4rev1" ~ (sp ~ capability)* }
char8 = @{ '\x01'..'\xff' }
continue_req = { "+" ~ sp ~ (resp_text | base64) ~ crlf }
date_day_fixed = { (sp ~ digit) | digit{2} }
date_month = { "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec" }
date_time = { dquote ~ date_day_fixed ~ "-" ~ date_month ~ "-" ~ date_year ~ sp ~ time ~ sp ~ zone ~ dquote }
date_year = { digit{4} }
digit_nz = @{ '\x31'..'\x39' }
env_bcc = { "(" ~ address{1,} ~ ")" | nil }
env_cc = { "(" ~ address{1,} ~ ")" | nil }
env_date = { nstring }
env_from = { "(" ~ address{1,} ~ ")" | nil }
env_in_reply_to = { nstring }
env_message_id = { nstring }
env_reply_to = { "(" ~ address{1,} ~ ")" | nil }
env_sender = { "(" ~ address{1,} ~ ")" | nil }
env_subject = { nstring }
env_to = { "(" ~ address{1,} ~ ")" | nil }
envelope = { "(" ~ env_date ~ sp ~ env_subject ~ sp ~ env_from ~ sp ~ env_sender ~ sp ~ env_reply_to ~ sp ~ env_to ~ sp ~ env_cc ~ sp ~ env_bcc ~ sp ~ env_in_reply_to ~ sp ~ env_message_id ~ ")" }
flag = { "\\Answered" | "\\Flagged" | "\\Deleted" | "\\Seen" | "\\Draft" | flag_keyword | flag_extension }
flag_extension = @{ "\\" ~ atom }
flag_fetch = { flag | "\\Recent" }
flag_keyword = @{ atom }
flag_list = { "(" ~ (flag ~ (sp ~ flag)*)? ~ ")" }
flag_perm = { flag | "\\*" }
header_fld_name = { astring }
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_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 }
mbx_list_sflag = { "\\NoSelect" | "\\Marked" | "\\Unmarked" }
media_basic = { ((dquote ~ ("APPLICATION" | "AUDIO" | "IMAGE" | "MESSAGE" | "VIDEO") ~ dquote) | string) ~ sp ~ media_subtype }
media_message = { dquote ~ "MESSAGE" ~ dquote ~ sp ~ dquote ~ "RFC822" ~ dquote }
media_subtype = { string }
media_text = { dquote ~ "TEXT" ~ dquote ~ sp ~ media_subtype }
message_data = { nz_number ~ sp ~ (^"EXPUNGE" | (^"FETCH" ~ sp ~ msg_att)) }
msg_att = { "(" ~ (msg_att_dynamic | msg_att_static) ~ (sp ~ (msg_att_dynamic | msg_att_static))* ~ ")" }
msg_att_dynamic = { ^"FLAGS" ~ sp ~ "(" ~ (flag_fetch ~ (sp ~ flag_fetch)*)? ~ ")" }
msg_att_static = { (^"ENVELOPE" ~ sp ~ envelope) | (^"INTERNALDATE" ~ sp ~ date_time) | (^"RFC822" ~ (^".HEADER" | ^".TEXT") ~ sp ~ nstring) | (^"RFC822.SIZE" ~ sp ~ number) | (^"BODY" ~ ^"STRUCTURE"? ~ sp ~ body) | (^"BODY" ~ section ~ ("<" ~ number ~ ">")? ~ sp ~ nstring) | (^"UID" ~ sp ~ uniqueid) }
nil = { ^"NIL" }
nstring = { string | nil }
number = @{ digit{1,} }
nz_number = @{ digit_nz ~ digit* }
quoted = @{ dquote ~ quoted_char* ~ dquote }
quoted_char = @{ (!quoted_specials ~ char) | ("\\" ~ quoted_specials) }
quoted_specials = @{ dquote | "\\" }
resp_cond_bye = { ^"BYE" ~ sp ~ resp_text }
resp_cond_state = { (^"OK" | ^"NO" | ^"BAD") ~ resp_text }
resp_specials = @{ "]" }
nil = { "NIL" }
resp_text = { ("[" ~ resp_text_code ~ "]" ~ sp)? ~ text }
resp_text_code = { ^"ALERT" | (^"BADCHARSET" ~ (sp ~ "(" ~ astring ~ (sp ~ astring)* ~ ")")?) | capability_data | ^"PARSE" | (^"PERMANENTFLAGS" ~ sp ~ "(" ~ (flag_perm ~ (sp ~ flag_perm)*)? ~ ")") | ^"READ-ONLY" | ^"READ-WRITE" | ^"TRYCREATE" | (^"UIDNEXT" ~ sp ~ nz_number) | (^"UIDVALIDITY" ~ sp ~ nz_number) | (^"UNSEEN" ~ sp ~ nz_number) | (atom ~ (sp ~ resp_text_code_atom)?) }
resp_text_code_atom = @{ (!"]" ~ text_char){1,} }
response = { continue_req | response_data | response_done }
response_data = { "*" ~ sp ~ (resp_cond_state | resp_cond_bye | mailbox_data | message_data | capability_data) ~ crlf }
response_done = { response_tagged | response_fatal }
response_fatal = { "*" ~ sp ~ resp_cond_bye ~ crlf }
response_tagged = { tag ~ sp ~ resp_cond_state ~ crlf }
section = { "[" ~ section_spec? ~ "]" }
section_msgtext = { ^"HEADER" | (^"HEADER.FIELDS" ~ ^".NOT"? ~ sp ~ header_list) | ^"TEXT" }
section_part = { nz_number ~ ("." ~ nz_number)* }
section_spec = { section_msgtext | (section_part ~ ("." ~ section_text)?) }
section_text = { section_msgtext | "MIME" }
status_att = { ^"MESSAGES" | ^"RECENT" | ^"UIDNEXT" | ^"UIDVALIDITY" | ^"UNSEEN" }
status_att_list = { status_att ~ sp ~ number ~ (sp ~ status_att ~ sp ~ number)* }
string = @{ quoted | literal }
tag = @{ tag_char{1,} }
tag_char = @{ !"+" ~ astring_char }
text = @{ text_char{1,} }
text_char = @{ !cr ~ !lf ~ char }
time = { digit{2} ~ ":" ~ digit{2} ~ ":" ~ digit{2} }
uniqueid = { nz_number }
zone = { ("+" | "-") ~ digit{4} }
// core rules from https://tools.ietf.org/html/rfc2234#section-6.1
alpha = @{ '\x41'..'\x5a' | '\x61'..'\x7a' }
char = @{ '\x01'..'\x7f' }
cr = @{ "\x0d" }
crlf = @{ cr ~ lf }
ctl = @{ '\x00'..'\x1f' | "\x7f" }
digit = @{ '\x30'..'\x39' }
dquote = @{ "\"" }
lf = @{ "\x0a" }
sp = @{ " " }