add more envelope fields
This commit is contained in:
parent
7cd69bd6a8
commit
345a9ab25f
4 changed files with 185 additions and 138 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
]
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
133
imap/src/parser/tests.rs
Normal 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,
|
||||
},
|
||||
]
|
||||
))
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue