add more envelope fields

This commit is contained in:
Michael Zhang 2021-03-08 17:32:12 -06:00
parent 7cd69bd6a8
commit 345a9ab25f
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
4 changed files with 185 additions and 138 deletions

4
Cargo.lock generated
View file

@ -1483,6 +1483,7 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "git+https://github.com/iptq/pest?rev=6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99#6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99"
dependencies = [
"log",
"ucd-trie",
@ -1491,6 +1492,7 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "git+https://github.com/iptq/pest?rev=6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99#6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99"
dependencies = [
"pest",
"pest_generator",
@ -1499,6 +1501,7 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "git+https://github.com/iptq/pest?rev=6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99#6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99"
dependencies = [
"pest",
"pest_meta",
@ -1510,6 +1513,7 @@ dependencies = [
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "git+https://github.com/iptq/pest?rev=6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99#6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99"
dependencies = [
"cargo",
"maplit",

View file

@ -2,11 +2,13 @@
mod literal;
#[cfg(test)]
mod tests;
use std::fmt::Debug;
use std::mem;
use std::str::FromStr;
use pest::{error::Error, iterators::Pair, ParseResult as PestResult, Parser, ParserState};
use pest::{error::Error, iterators::Pair, Parser};
use crate::response::*;
@ -151,7 +153,7 @@ fn build_msg_att_static(pair: Pair<Rule>) -> AttributeValue {
match pair.as_rule() {
Rule::msg_att_static_internaldate => {
AttributeValue::InternalDate(build_string(unwrap1(pair)))
AttributeValue::InternalDate(build_string(unwrap1(unwrap1(pair))))
}
Rule::msg_att_static_rfc822_size => AttributeValue::Rfc822Size(build_number(unwrap1(pair))),
Rule::msg_att_static_envelope => AttributeValue::Envelope(build_envelope(unwrap1(pair))),
@ -166,9 +168,32 @@ fn build_msg_att_static(pair: Pair<Rule>) -> AttributeValue {
}
}
fn build_envelope(_pair: Pair<Rule>) -> Envelope {
fn build_envelope(pair: Pair<Rule>) -> Envelope {
// TODO: do this
Envelope::default()
let mut pairs = pair.into_inner();
let date = build_nstring(unwrap1(pairs.next().unwrap()));
let subject = build_nstring(unwrap1(pairs.next().unwrap()));
pairs.next().unwrap(); // env_from
pairs.next().unwrap(); // env_sender
pairs.next().unwrap(); // env_reply_to
pairs.next().unwrap(); // env_to
pairs.next().unwrap(); // env_cc
pairs.next().unwrap(); // env_bcc
let in_reply_to = build_nstring(unwrap1(pairs.next().unwrap()));
let message_id = build_nstring(unwrap1(pairs.next().unwrap()));
Envelope {
date,
subject,
from: None,
sender: None,
reply_to: None,
to: None,
cc: None,
bcc: None,
in_reply_to,
message_id,
}
}
fn build_resp_cond_state(pair: Pair<Rule>) -> (Status, Option<ResponseCode>, Option<String>) {
@ -392,17 +417,26 @@ where
///
/// [1]: self::build_string
fn build_nstring(pair: Pair<Rule>) -> Option<String> {
if matches!(pair.as_rule(), Rule::nil) {
return None;
assert!(matches!(pair.as_rule(), Rule::nstring));
let pair = unwrap1(pair);
match pair.as_rule() {
Rule::nil => None,
Rule::string => Some(build_string(pair)),
_ => unreachable!(),
}
Some(build_string(pair))
}
/// Extracts a string-type, discarding the surrounding quotes and unescaping the escaped characters
fn build_string(pair: Pair<Rule>) -> String {
// TODO: actually get rid of the quotes and escaped chars
pair.as_str().to_owned()
assert!(matches!(pair.as_rule(), Rule::string));
let pair = unwrap1(pair);
match pair.as_rule() {
Rule::literal => build_literal(pair),
// TODO: escaping stuff?
Rule::quoted => pair.as_str().trim_start_matches("\"").trim_end_matches("\"").replace("\\\"", "\"").to_owned(),
_ => unreachable!(),
}
}
fn parse_literal(s: impl AsRef<str>) -> ParseResult<String> {
@ -419,129 +453,3 @@ fn build_literal(pair: Pair<Rule>) -> String {
let literal_str = pairs.next().unwrap();
literal_str.as_str().to_owned()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::response::*;
use pest::Parser;
#[test]
fn test_literal() {
assert_eq!(parse_literal("{7}\r\nhellosu"), Ok("hellosu".to_owned()));
}
#[test]
#[rustfmt::skip]
fn test_capability() {
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());
}
#[test]
#[rustfmt::skip]
fn test_nil() {
assert!(Rfc3501::parse(Rule::nil, "NIL").is_ok());
assert!(Rfc3501::parse(Rule::nil, "anything else").is_err());
}
#[test]
fn test_section_8() {
// this little exchange is from section 8 of rfc3501
// https://tools.ietf.org/html/rfc3501#section-8
assert_eq!(
parse_response("* OK IMAP4rev1 Service Ready\r\n"),
Ok(Response::Data(ResponseData {
status: Status::Ok,
code: None,
information: Some("IMAP4rev1 Service Ready".to_owned()),
}))
);
assert_eq!(
parse_response("a001 OK LOGIN completed\r\n"),
Ok(Response::Done(ResponseDone {
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)))
);
assert_eq!(
parse_response("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"),
Ok(Response::MailboxData(MailboxData::Flags(vec![
MailboxFlag::Answered,
MailboxFlag::Flagged,
MailboxFlag::Deleted,
MailboxFlag::Seen,
MailboxFlag::Draft,
])))
);
assert_eq!(
parse_response("* 2 RECENT\r\n"),
Ok(Response::MailboxData(MailboxData::Recent(2)))
);
assert_eq!(
parse_response("* OK [UNSEEN 17] Message 17 is the first unseen message\r\n"),
Ok(Response::Data(ResponseData {
status: Status::Ok,
code: Some(ResponseCode::Unseen(17)),
information: Some("Message 17 is the first unseen message".to_owned()),
}))
);
assert_eq!(
parse_response("* OK [UIDVALIDITY 3857529045] UIDs valid\r\n"),
Ok(Response::Data(ResponseData {
status: Status::Ok,
code: Some(ResponseCode::UidValidity(3857529045)),
information: Some("UIDs valid".to_owned()),
}))
);
assert_eq!(
parse_response("a002 OK [READ-WRITE] SELECT completed\r\n"),
Ok(Response::Done(ResponseDone {
tag: "a002".to_owned(),
status: Status::Ok,
code: Some(ResponseCode::ReadWrite),
information: Some("SELECT completed".to_owned()),
}))
);
assert_eq!(
parse_response(concat!(
r#"* 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" "IMAP4rev1 WG mtg summary and minutes" (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) ((NIL NIL "imap" "cac.washington.edu")) ((NIL NIL "minutes" "CNRI.Reston.VA.US")("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL "<B27397-0100000@cac.washington.edu>") BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 302892))"#,
"\r\n",
)),
Ok(Response::Fetch(
12,
vec![
AttributeValue::Flags(vec![MailboxFlag::Seen]),
AttributeValue::InternalDate("\"17-Jul-1996 02:44:25 -0700\"".to_owned()),
AttributeValue::Rfc822Size(4286),
AttributeValue::Envelope(Envelope::default()),
AttributeValue::BodySection {
section: None,
index: None,
data: None,
},
]
))
);
}
}

View file

@ -41,7 +41,9 @@ 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 }
// TODO: date_time is a real date time
// date_time = { dquote ~ date_day_fixed ~ "-" ~ date_month ~ "-" ~ date_year ~ sp ~ time ~ sp ~ zone ~ dquote }
date_time = ${ string }
date_year = @{ digit{4} }
digit_nz = @{ '\x31'..'\x39' }
env_bcc = { "(" ~ address{1,} ~ ")" | nil }

133
imap/src/parser/tests.rs Normal file
View file

@ -0,0 +1,133 @@
use super::*;
use crate::response::*;
use pest::Parser;
#[test]
fn test_literal() {
assert_eq!(parse_literal("{7}\r\nhellosu"), Ok("hellosu".to_owned()));
}
#[test]
#[rustfmt::skip]
fn test_capability() {
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());
}
#[test]
#[rustfmt::skip]
fn test_nil() {
assert!(Rfc3501::parse(Rule::nil, "NIL").is_ok());
assert!(Rfc3501::parse(Rule::nil, "anything else").is_err());
}
#[test]
fn test_section_8() {
// this little exchange is from section 8 of rfc3501
// https://tools.ietf.org/html/rfc3501#section-8
assert_eq!(
parse_response("* OK IMAP4rev1 Service Ready\r\n"),
Ok(Response::Data(ResponseData {
status: Status::Ok,
code: None,
information: Some("IMAP4rev1 Service Ready".to_owned()),
}))
);
assert_eq!(
parse_response("a001 OK LOGIN completed\r\n"),
Ok(Response::Done(ResponseDone {
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)))
);
assert_eq!(
parse_response("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"),
Ok(Response::MailboxData(MailboxData::Flags(vec![
MailboxFlag::Answered,
MailboxFlag::Flagged,
MailboxFlag::Deleted,
MailboxFlag::Seen,
MailboxFlag::Draft,
])))
);
assert_eq!(
parse_response("* 2 RECENT\r\n"),
Ok(Response::MailboxData(MailboxData::Recent(2)))
);
assert_eq!(
parse_response("* OK [UNSEEN 17] Message 17 is the first unseen message\r\n"),
Ok(Response::Data(ResponseData {
status: Status::Ok,
code: Some(ResponseCode::Unseen(17)),
information: Some("Message 17 is the first unseen message".to_owned()),
}))
);
assert_eq!(
parse_response("* OK [UIDVALIDITY 3857529045] UIDs valid\r\n"),
Ok(Response::Data(ResponseData {
status: Status::Ok,
code: Some(ResponseCode::UidValidity(3857529045)),
information: Some("UIDs valid".to_owned()),
}))
);
assert_eq!(
parse_response("a002 OK [READ-WRITE] SELECT completed\r\n"),
Ok(Response::Done(ResponseDone {
tag: "a002".to_owned(),
status: Status::Ok,
code: Some(ResponseCode::ReadWrite),
information: Some("SELECT completed".to_owned()),
}))
);
assert_eq!(
parse_response(concat!(
r#"* 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" "IMAP4rev1 WG mtg summary and minutes" (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) ((NIL NIL "imap" "cac.washington.edu")) ((NIL NIL "minutes" "CNRI.Reston.VA.US")("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL "<B27397-0100000@cac.washington.edu>") BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 302892))"#,
"\r\n",
)),
Ok(Response::Fetch(
12,
vec![
AttributeValue::Flags(vec![MailboxFlag::Seen]),
AttributeValue::InternalDate("17-Jul-1996 02:44:25 -0700".to_owned()),
AttributeValue::Rfc822Size(4286),
AttributeValue::Envelope(Envelope {
date: Some("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)".to_owned()),
subject: Some("IMAP4rev1 WG mtg summary and minutes".to_owned()),
from: None,
sender: None,
reply_to: None,
to: None,
cc: None,
bcc: None,
in_reply_to: None,
message_id: Some("<B27397-0100000@cac.washington.edu>".to_owned()),
}),
AttributeValue::BodySection {
section: None,
index: None,
data: None,
},
]
))
);
}