From daa3b8cd61d7a468b1c440db8bd51033f8ec76dc Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Tue, 9 Mar 2021 05:55:31 -0600 Subject: [PATCH] add address parsing --- imap/src/parser/mod.rs | 65 +++++++++++++++++++++++++++++------- imap/src/parser/rfc3501.pest | 13 ++++---- imap/src/parser/tests.rs | 50 ++++++++++++++++++++++++--- src/ui/mail_tab.rs | 6 ++-- 4 files changed, 108 insertions(+), 26 deletions(-) diff --git a/imap/src/parser/mod.rs b/imap/src/parser/mod.rs index abc5a48..9a29748 100644 --- a/imap/src/parser/mod.rs +++ b/imap/src/parser/mod.rs @@ -174,24 +174,35 @@ fn build_envelope(pair: Pair) -> Envelope { 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 address1 = |r: Rule, pair: Pair| -> Option> { + assert!(matches!(pair.as_rule(), r)); + let pair = unwrap1(pair); + match pair.as_rule() { + Rule::nil => None, + Rule::env_address1 => Some(pair.into_inner().map(build_address).collect()), + _ => unreachable!("{:?}", pair), + } + }; + + let from = address1(Rule::env_from, pairs.next().unwrap()); + let sender = address1(Rule::env_sender, pairs.next().unwrap()); + let reply_to = address1(Rule::env_reply_to, pairs.next().unwrap()); + let to = address1(Rule::env_to, pairs.next().unwrap()); + let cc = address1(Rule::env_cc, pairs.next().unwrap()); + let bcc = address1(Rule::env_bcc, pairs.next().unwrap()); 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, + from, + sender, + reply_to, + to, + cc, + bcc, in_reply_to, message_id, } @@ -483,6 +494,8 @@ fn build_zone(pair: Pair) -> FixedOffset { } fn build_date_time(pair: Pair) -> DateTime { + assert!(matches!(pair.as_rule(), Rule::date_time)); + let mut pairs = pair.into_inner(); let pair = pairs.next().unwrap(); assert!(matches!(pair.as_rule(), Rule::date_day_fixed)); @@ -523,3 +536,31 @@ fn build_date_time(pair: Pair) -> DateTime { zone.ymd(year, month, day).and_hms(hour, minute, second) } + +fn build_address(pair: Pair) -> Address { + assert!(matches!(pair.as_rule(), Rule::address)); + + let mut pairs = pair.into_inner(); + let pair = pairs.next().unwrap(); + assert!(matches!(pair.as_rule(), Rule::addr_name)); + let name = build_nstring(unwrap1(pair)); + + let pair = pairs.next().unwrap(); + assert!(matches!(pair.as_rule(), Rule::addr_adl)); + let adl = build_nstring(unwrap1(pair)); + + let pair = pairs.next().unwrap(); + assert!(matches!(pair.as_rule(), Rule::addr_mailbox)); + let mailbox = build_nstring(unwrap1(pair)); + + let pair = pairs.next().unwrap(); + assert!(matches!(pair.as_rule(), Rule::addr_host)); + let host = build_nstring(unwrap1(pair)); + + Address { + name, + adl, + mailbox, + host, + } +} diff --git a/imap/src/parser/rfc3501.pest b/imap/src/parser/rfc3501.pest index 144dbc1..7390104 100644 --- a/imap/src/parser/rfc3501.pest +++ b/imap/src/parser/rfc3501.pest @@ -44,16 +44,17 @@ date_month = { "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | " 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_address1 = { "(" ~ address{1,} ~ ")" } +env_bcc = { env_address1 | nil } +env_cc = { env_address1 | nil } env_date = { nstring } -env_from = { "(" ~ address{1,} ~ ")" | nil } +env_from = { env_address1 | nil } env_in_reply_to = { nstring } env_message_id = { nstring } -env_reply_to = { "(" ~ address{1,} ~ ")" | nil } -env_sender = { "(" ~ address{1,} ~ ")" | nil } +env_reply_to = { env_address1 | nil } +env_sender = { env_address1 | nil } env_subject = { nstring } -env_to = { "(" ~ address{1,} ~ ")" | nil } +env_to = { env_address1 | 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 } diff --git a/imap/src/parser/tests.rs b/imap/src/parser/tests.rs index e559632..3ce2d09 100644 --- a/imap/src/parser/tests.rs +++ b/imap/src/parser/tests.rs @@ -22,6 +22,21 @@ fn test_literal() { assert_eq!(p("{7}\r\nhellosu"), Ok("hellosu".to_owned())); } +#[test] +fn test_address() -> Result<()> { + let p = parse(Rule::address, build_address); + assert_eq!( + p(r#"("Terry Gray" NIL "gray" "cac.washington.edu")"#)?, + Address { + name: Some("Terry Gray".to_owned()), + adl: None, + mailbox: Some("gray".to_owned()), + host: Some("cac.washington.edu".to_owned()), + } + ); + Ok(()) +} + #[test] fn test_zone() { let p = parse(Rule::zone, build_zone); @@ -133,6 +148,31 @@ fn test_section_8() { })) ); + let terry_addr = Address { + name: Some("Terry Gray".to_owned()), + adl: None, + mailbox: Some("gray".to_owned()), + host: Some("cac.washington.edu".to_owned()), + }; + let imap_addr = Address { + name: None, + adl: None, + mailbox: Some("imap".to_owned()), + host: Some("cac.washington.edu".to_owned()), + }; + let minutes_addr = Address { + name: None, + adl: None, + mailbox: Some("minutes".to_owned()), + host: Some("CNRI.Reston.VA.US".to_owned()), + }; + let john_addr = Address { + name: Some("John Klensin".to_owned()), + adl: None, + mailbox: Some("KLENSIN".to_owned()), + host: Some("MIT.EDU".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 "") BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 302892))"#, @@ -149,11 +189,11 @@ fn test_section_8() { 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, + from: Some(vec![terry_addr.clone()]), + sender: Some(vec![terry_addr.clone()]), + reply_to: Some(vec![terry_addr.clone()]), + to: Some(vec![imap_addr.clone()]), + cc: Some(vec![minutes_addr.clone(), john_addr.clone()]), bcc: None, in_reply_to: None, message_id: Some("".to_owned()), diff --git a/src/ui/mail_tab.rs b/src/ui/mail_tab.rs index 9f5bfa1..4be8ee2 100644 --- a/src/ui/mail_tab.rs +++ b/src/ui/mail_tab.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use chrono::{DateTime, Duration, Local, Datelike}; +use chrono::{DateTime, Datelike, Duration, Local}; use chrono_humanize::HumanTime; use panorama_imap::response::Envelope; use tui::{ @@ -35,7 +35,7 @@ fn humanize_timestamp(date: DateTime) -> String { if diff < Duration::days(1) { HumanTime::from(date).to_string() - }else if date.year() == now.year() { + } else if date.year() == now.year() { date.format("%b %e %T").to_string() } else { date.to_rfc2822() @@ -73,7 +73,7 @@ impl MailTabState { "".to_owned(), id.to_string(), meta.map(|m| humanize_timestamp(m.date)).unwrap_or_default(), - "".to_owned(), + meta.map(|m| m.from.clone()).unwrap_or_default(), meta.map(|m| m.subject.clone()).unwrap_or_default(), ]) })