From 0162d39deb193903c6139dc094c27d63ac2eac15 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Tue, 9 Mar 2021 03:12:07 -0600 Subject: [PATCH] add date_time parser --- Cargo.lock | 72 +++++++++++++++++----------------- Cargo.toml | 8 ++-- imap/Cargo.toml | 5 +-- imap/src/client/inner.rs | 17 +------- imap/src/codec.rs | 6 +-- imap/src/parser/literal.rs | 19 ++------- imap/src/parser/mod.rs | 76 +++++++++++++++++++++++++++++------- imap/src/parser/rfc3501.pest | 5 ++- imap/src/parser/tests.rs | 54 ++++++++++++++++++++----- imap/src/response.rs | 4 +- src/ui/mod.rs | 16 ++++++-- 11 files changed, 172 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a37628a..4befe6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,13 +70,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.42" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf" dependencies = [ "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -527,7 +527,7 @@ dependencies = [ "proc-macro2", "quote 1.0.9", "strsim 0.9.3", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -538,7 +538,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -549,7 +549,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -562,7 +562,7 @@ dependencies = [ "derive_builder_core", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -574,7 +574,7 @@ dependencies = [ "darling", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -615,7 +615,7 @@ checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce" dependencies = [ "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -708,9 +708,9 @@ dependencies = [ [[package]] name = "format-bytes" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc35f5e45d6b31053cea13078ffc6fa52fa8617aa54b7ac2011720d9c009e04f" +checksum = "1c4e89040c7fd7b4e6ba2820ac705a45def8a0c098ec78d170ae88f1ef1d5762" dependencies = [ "format-bytes-macros", "proc-macro-hack", @@ -725,7 +725,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -800,7 +800,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -1086,9 +1086,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.86" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" [[package]] name = "libgit2-sys" @@ -1336,9 +1336,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.7.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10acf907b94fc1b1a152d08ef97e7759650268cf986bf127f387e602b02c7e5a" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "opaque-debug" @@ -1426,6 +1426,7 @@ dependencies = [ "assert_matches", "async-trait", "bytes", + "chrono", "derive_builder", "futures", "log", @@ -1434,7 +1435,6 @@ dependencies = [ "pest_derive", "tokio", "tokio-rustls", - "tokio-stream", "tokio-util", "webpki-roots", ] @@ -1507,7 +1507,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -1523,9 +1523,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" [[package]] name = "pin-utils" @@ -1576,7 +1576,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", "version_check", ] @@ -1858,22 +1858,22 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.123" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.123" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" dependencies = [ "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -1904,7 +1904,7 @@ checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -2027,7 +2027,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -2059,9 +2059,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.60" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "123a78a3596b24fee53a6464ce52d8ecbf62241e6294c7e7fe12086cd161f512" dependencies = [ "proc-macro2", "quote 1.0.9", @@ -2182,7 +2182,7 @@ checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" dependencies = [ "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -2413,7 +2413,7 @@ dependencies = [ "log", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", "wasm-bindgen-shared", ] @@ -2435,7 +2435,7 @@ checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" dependencies = [ "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2584,7 +2584,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] [[package]] @@ -2608,5 +2608,5 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.62", ] diff --git a/Cargo.toml b/Cargo.toml index 7d037ba..b84102d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,18 +14,18 @@ members = ["imap", "smtp"] [dependencies] crossterm = "0.19.0" anyhow = "1.0.38" -async-trait = "0.1.42" +async-trait = "0.1.48" cfg-if = "1.0.0" chrono = "0.4.19" fern = { version = "0.6.0", features = ["colored"] } -format-bytes = "0.2.0" +format-bytes = "0.2.2" futures = "0.3.13" inotify = { version = "0.9.2", features = ["stream"] } log = "0.4.14" -notify-rust = { version = "4.2.2", default-features = false, features = ["z"] } +notify-rust = { version = "4.3.0", default-features = false, features = ["z"] } parking_lot = "0.11.1" # pgp = "0.7.1" -serde = { version = "1.0.123", features = ["derive"] } +serde = { version = "1.0.124", features = ["derive"] } structopt = "0.3.21" tokio = { version = "1.2.0", features = ["full"] } tokio-rustls = "0.22.0" diff --git a/imap/Cargo.toml b/imap/Cargo.toml index 6bf5fe5..c15fbe1 100644 --- a/imap/Cargo.toml +++ b/imap/Cargo.toml @@ -8,13 +8,11 @@ categories = ["email", "network-programming", "parser-implementations"] license = "MIT OR Apache-2.0" edition = "2018" -[badges] -maintenance = { status = "passively-maintained" } - [dependencies] anyhow = "1.0.38" async-trait = "0.1.42" bytes = { version = "1.0.1" } +chrono = "0.4.19" derive_builder = "0.9.0" futures = "0.3.12" log = "0.4.14" @@ -25,7 +23,6 @@ pest = { git = "https://github.com/iptq/pest", rev = "6a4d3a3d10e42a3ee605ca979d pest_derive = { git = "https://github.com/iptq/pest", rev = "6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99" } tokio = { version = "1.1.1", features = ["full"] } tokio-rustls = "0.22.0" -tokio-stream = "0.1.3" tokio-util = { version = "0.6.3" } webpki-roots = "0.21.0" diff --git a/imap/src/client/inner.rs b/imap/src/client/inner.rs index 430ac2a..ea4a12d 100644 --- a/imap/src/client/inner.rs +++ b/imap/src/client/inner.rs @@ -184,7 +184,7 @@ async fn listen( conn: ReadHalf, mut cmd_rx: mpsc::UnboundedReceiver, greeting_tx: oneshot::Sender<()>, - mut exit_rx: oneshot::Receiver<()>, + exit_rx: oneshot::Receiver<()>, ) -> Result> where C: AsyncRead + Unpin, @@ -197,7 +197,7 @@ where let mut exit_rx = exit_rx.map_err(|_| ()).shared(); // let mut exit_fut = Some(exit_rx.fuse()); // let mut fut1 = None; - let mut cache = String::new(); + let cache = String::new(); loop { // let mut next_line = String::new(); @@ -227,19 +227,6 @@ where } resp = read_fut => { - // trace!("read line {:?}", next_line); - // res should not be None here - // cache += &next_line; - // let resp = match parse_response(&cache) { - // Ok(v) => { - // cache.clear(); - // v - // } - // Err(e) => { - // error!("parse error: {}", e); - // continue; - // } - // }; let resp = match resp { Some(Ok(v)) => v, a => { error!("failed: {:?}", a); bail!("fuck"); }, diff --git a/imap/src/codec.rs b/imap/src/codec.rs index 911c680..21f8201 100644 --- a/imap/src/codec.rs +++ b/imap/src/codec.rs @@ -13,16 +13,12 @@ impl Decoder for ImapCodec { fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { let s = std::str::from_utf8(src)?; - debug!("-------------------------"); - debug!("s: {:?}", s); match parse_streamed_response(s) { Ok((resp, len)) => { src.advance(len); return Ok(Some(resp)); } - Err(e) => { - warn!("failed to parse: {:?} {}", e, e); - } + Err(e) => {} }; Ok(None) diff --git a/imap/src/parser/literal.rs b/imap/src/parser/literal.rs index 4ced3ea..8b7f0fe 100644 --- a/imap/src/parser/literal.rs +++ b/imap/src/parser/literal.rs @@ -5,8 +5,7 @@ use super::Rule; type PSR<'a> = Box>; /// This is a hack around the literal syntax to allow us to parse characters statefully. -pub(crate) fn literal_internal(mut state: PSR) -> PestResult { - // debug!("STATE: {:?}", state); +pub(crate) fn literal_internal(state: PSR) -> PestResult { use pest::Atomicity; // yoinked from the generated code @@ -19,10 +18,7 @@ pub(crate) fn literal_internal(mut state: PSR) -> PestResult { #[allow(non_snake_case, unused_variables)] pub fn number(state: PSR) -> PestResult { state.rule(Rule::number, |state| { - state.sequence(|state| { - debug!("number::atomic::sequence"); - digit(state).and_then(|state| state.repeat(digit)) - }) + state.sequence(|state| digit(state).and_then(|state| state.repeat(digit))) }) } #[inline] @@ -37,10 +33,6 @@ pub(crate) fn literal_internal(mut state: PSR) -> PestResult { #[inline] #[allow(non_snake_case, unused_variables)] pub fn crlf(state: PSR) -> PestResult { - debug!( - "running rule 'crlf' {:?}", - state.queue().iter().rev().take(10).collect::>() - ); state.sequence(|state| state.match_string("\r")?.match_string("\n")) } @@ -63,15 +55,11 @@ pub(crate) fn literal_internal(mut state: PSR) -> PestResult { QueueableToken::Start { input_pos: pos, .. } => pos, _ => unreachable!(), }; - debug!("start_pos: {}, end_pos: {}", start_pos, end_pos); let inp = state.position().get_str(); let seg = &inp[start_pos..end_pos]; match seg.parse::() { - Ok(v) => { - debug!("got length: {}", v); - v - } + Ok(v) => v, Err(e) => { error!( "failed to parse int from {}..{} {:?}: {}", @@ -95,7 +83,6 @@ pub(crate) fn literal_internal(mut state: PSR) -> PestResult { state }) }) - // todo!("hit internal state: {:?}", state,); } pub(crate) fn noop(state: PSR) -> PestResult { diff --git a/imap/src/parser/mod.rs b/imap/src/parser/mod.rs index 7daaac5..abc5a48 100644 --- a/imap/src/parser/mod.rs +++ b/imap/src/parser/mod.rs @@ -8,6 +8,7 @@ mod tests; use std::fmt::Debug; use std::str::FromStr; +use chrono::{DateTime, FixedOffset, TimeZone}; use pest::{error::Error, iterators::Pair, Parser}; use crate::response::*; @@ -153,7 +154,7 @@ fn build_msg_att_static(pair: Pair) -> AttributeValue { match pair.as_rule() { Rule::msg_att_static_internaldate => { - AttributeValue::InternalDate(build_string(unwrap1(unwrap1(pair)))) + AttributeValue::InternalDate(build_date_time(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))), @@ -272,7 +273,6 @@ fn build_capabilities(pair: Pair) -> Vec { if !matches!(pair.as_rule(), Rule::capability_data) { unreachable!("{:#?}", pair); } - pair.into_inner().map(build_capability).collect() } @@ -323,9 +323,7 @@ fn build_flag(mut pair: Pair) -> MailboxFlag { } fn build_mailbox_data(pair: Pair) -> MailboxData { - if !matches!(pair.as_rule(), Rule::mailbox_data) { - unreachable!("{:#?}", pair); - } + assert!(matches!(pair.as_rule(), Rule::mailbox_data)); let mut pairs = pair.into_inner(); let pair = pairs.next().unwrap(); @@ -357,9 +355,7 @@ fn build_mailbox_data(pair: Pair) -> MailboxData { } fn build_mailbox_list(pair: Pair) -> (Vec, Option, String) { - if !matches!(pair.as_rule(), Rule::mailbox_list) { - unreachable!("{:#?}", pair); - } + assert!(matches!(pair.as_rule(), Rule::mailbox_list)); let mut pairs = pair.into_inner(); let mut pair = pairs.next().unwrap(); @@ -411,9 +407,7 @@ where T: FromStr, T::Err: Debug, { - if !matches!(pair.as_rule(), Rule::nz_number | Rule::number) { - unreachable!("not a number {:#?}", pair); - } + assert!(matches!(pair.as_rule(), Rule::nz_number | Rule::number)); pair.as_str().parse::().unwrap() } @@ -430,9 +424,6 @@ fn build_astring(pair: Pair) -> String { } } -/// Wrapper around [build_string][1], except return None for the `nil` case -/// -/// [1]: self::build_string fn build_nstring(pair: Pair) -> Option { assert!(matches!(pair.as_rule(), Rule::nstring)); let pair = unwrap1(pair); @@ -475,3 +466,60 @@ fn build_literal(pair: Pair) -> String { let literal_str = pairs.next().unwrap(); literal_str.as_str().to_owned() } + +fn parse_zone(s: impl AsRef) -> ParseResult { + let mut pairs = Rfc3501::parse(Rule::zone, s.as_ref())?; + let pair = pairs.next().unwrap(); + Ok(build_zone(pair)) +} + +fn build_zone(pair: Pair) -> FixedOffset { + assert!(matches!(pair.as_rule(), Rule::zone)); + let n = pair.as_str().parse::().unwrap(); + let sign = if n != 0 { n / n.abs() } else { 1 }; + let h = n.abs() / 100; + let m = n.abs() % 100; + FixedOffset::east(sign * (h * 60 + m) * 60) +} + +fn build_date_time(pair: Pair) -> DateTime { + let mut pairs = pair.into_inner(); + let pair = pairs.next().unwrap(); + assert!(matches!(pair.as_rule(), Rule::date_day_fixed)); + let day = pair.as_str().trim().parse::().unwrap(); + + let pair = pairs.next().unwrap(); + assert!(matches!(pair.as_rule(), Rule::date_month)); + let month = match pair.as_str() { + "Jan" => 1, + "Feb" => 2, + "Mar" => 3, + "Apr" => 4, + "May" => 5, + "Jun" => 6, + "Jul" => 7, + "Aug" => 8, + "Sep" => 9, + "Oct" => 10, + "Nov" => 11, + "Dec" => 12, + _ => unreachable!(), + }; + + let pair = pairs.next().unwrap(); + assert!(matches!(pair.as_rule(), Rule::date_year)); + let year = pair.as_str().trim().parse::().unwrap(); + + let pair = pairs.next().unwrap(); + assert!(matches!(pair.as_rule(), Rule::time)); + let mut parts = pair.as_str().split(':'); + let hour = parts.next().unwrap().parse::().unwrap(); + let minute = parts.next().unwrap().parse::().unwrap(); + let second = parts.next().unwrap().parse::().unwrap(); + + let pair = pairs.next().unwrap(); + assert!(matches!(pair.as_rule(), Rule::zone)); + let zone = build_zone(pair); + + zone.ymd(year, month, day).and_hms(hour, minute, second) +} diff --git a/imap/src/parser/rfc3501.pest b/imap/src/parser/rfc3501.pest index 6567b9d..e10b737 100644 --- a/imap/src/parser/rfc3501.pest +++ b/imap/src/parser/rfc3501.pest @@ -42,8 +42,8 @@ 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" } // 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_time = ${ string } +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 } @@ -151,6 +151,7 @@ crlf = _{ cr ~ lf } ctl = @{ '\x00'..'\x1f' | "\x7f" } digit = @{ '\x30'..'\x39' } dquote = @{ "\"" } +dquote_ = _{ "\"" } lf = _{ "\x0a" } sp = _{ " " } diff --git a/imap/src/parser/tests.rs b/imap/src/parser/tests.rs index 06ca4ed..e559632 100644 --- a/imap/src/parser/tests.rs +++ b/imap/src/parser/tests.rs @@ -1,22 +1,56 @@ +use anyhow::Result; +use chrono::FixedOffset; +use pest::Parser; + use super::*; use crate::response::*; -use pest::Parser; + +fn parse(r: Rule, f: F) -> impl Fn(&str) -> ParseResult +where + F: Fn(Pair) -> R, +{ + move |s: &str| { + let mut pairs = Rfc3501::parse(r, s.as_ref())?; + let pair = pairs.next().unwrap(); + Ok(f(pair)) + } +} #[test] fn test_literal() { - assert_eq!(parse_literal("{7}\r\nhellosu"), Ok("hellosu".to_owned())); + let p = parse(Rule::literal, build_literal); + assert_eq!(p("{7}\r\nhellosu"), Ok("hellosu".to_owned())); +} + +#[test] +fn test_zone() { + let p = parse(Rule::zone, build_zone); + assert_eq!(p("+0000"), Ok(FixedOffset::east(0))); + assert_eq!(p("-0200"), Ok(FixedOffset::west(7200))); + assert_eq!(p("+0330"), Ok(FixedOffset::east(12600))); +} + +#[test] +fn test_date_time() -> Result<()> { + let p = parse(Rule::date_time, build_date_time); + assert_eq!( + p("\"17-Jul-1996 02:44:25 -0700\"")?, + DateTime::parse_from_rfc3339("1996-07-17T02:44:25-07:00")? + ); + Ok(()) } #[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()))); + let p = parse(Rule::capability, build_capability); + assert_eq!(p("IMAP4rev1"), Ok(Capability::Imap4rev1)); + assert_eq!(p("LOGINDISABLED"), Ok(Capability::Atom("LOGINDISABLED".to_owned()))); + assert_eq!(p("AUTH=PLAIN"), Ok(Capability::Auth("PLAIN".to_owned()))); + assert_eq!(p("auth=plain"), Ok(Capability::Auth("PLAIN".to_owned()))); - assert!(parse_capability("(OSU)").is_err()); - assert!(parse_capability("\x01HELLO").is_err()); + assert!(p("(OSU)").is_err()); + assert!(p("\x01HELLO").is_err()); } #[test] @@ -108,7 +142,9 @@ fn test_section_8() { 12, vec![ AttributeValue::Flags(vec![MailboxFlag::Seen]), - AttributeValue::InternalDate("17-Jul-1996 02:44:25 -0700".to_owned()), + AttributeValue::InternalDate( + DateTime::parse_from_rfc3339("1996-07-17T02:44:25-07:00").unwrap() + ), AttributeValue::Rfc822Size(4286), AttributeValue::Envelope(Envelope { date: Some("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)".to_owned()), diff --git a/imap/src/response.rs b/imap/src/response.rs index 0de1eac..16faeaf 100644 --- a/imap/src/response.rs +++ b/imap/src/response.rs @@ -2,6 +2,8 @@ use std::ops::RangeInclusive; +use chrono::{DateTime, FixedOffset}; + #[derive(Clone, Debug, PartialEq, Eq)] pub enum Response { Capabilities(Vec), @@ -78,7 +80,7 @@ pub enum AttributeValue { BodyStructure(BodyStructure), Envelope(Envelope), Flags(Vec), - InternalDate(String), + InternalDate(DateTime), ModSeq(u64), // RFC 4551, section 3.3.2 Rfc822(Option), Rfc822Header(Option), diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 46c14d1..eae1582 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -50,16 +50,22 @@ pub async fn run_ui( let chunks = Layout::default() .direction(Direction::Vertical) .margin(0) - .constraints([Constraint::Length(1), Constraint::Max(5000)]) + .constraints([ + Constraint::Length(1), + Constraint::Max(5000), + Constraint::Length(1), + ]) .split(f.size()); // this is the title bar - let titles = vec!["hellosu"].into_iter().map(Spans::from).collect(); + let titles = vec!["OSU mail"].into_iter().map(Spans::from).collect(); let tabs = Tabs::new(titles); f.render_widget(tabs, chunks[0]); mail_tab.render(f, chunks[1]); - // render_mail_tab(f, chunks[1], &folders, &messages); + + let status = Paragraph::new("hellosu"); + f.render_widget(status, chunks[2]); })?; let event = if event::poll(FRAME_DURATION)? { @@ -69,7 +75,9 @@ pub async fn run_ui( if let Event::Key(KeyEvent { code, .. }) = event { let selected = mail_tab.message_list.selected(); let len = mail_tab.messages.len(); - let seln = selected.map(|x| if x < len - 1 { x + 1 } else { x }).unwrap_or(0); + let seln = selected + .map(|x| if x < len - 1 { x + 1 } else { x }) + .unwrap_or(0); let selp = selected.map(|x| if x > 0 { x - 1 } else { 0 }).unwrap_or(0); match code { KeyCode::Char('q') => break,