From 875041edfd9b9a43880161b44c65fa311065acae Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 22 Feb 2021 16:32:00 -0600 Subject: [PATCH] add rfc3501 rules that relate to response parsing --- imap/src/parser/mod.rs | 9 ++- imap/src/parser/rfc3501.pest | 111 ++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/imap/src/parser/mod.rs b/imap/src/parser/mod.rs index 4c7c222..88f1d52 100644 --- a/imap/src/parser/mod.rs +++ b/imap/src/parser/mod.rs @@ -14,10 +14,10 @@ pub fn parse_capability(s: &str) -> Result> { 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> { Ok(cap) } +pub fn parse_response(s: &str) -> Result> { + 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()); diff --git a/imap/src/parser/rfc3501.pest b/imap/src/parser/rfc3501.pest index 2fecd78..efde2c1 100644 --- a/imap/src/parser/rfc3501.pest +++ b/imap/src/parser/rfc3501.pest @@ -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 = @{ " " }