diff --git a/src/imap/imf_view.rs b/src/imap/imf_view.rs index b56e27d..a4ca2e8 100644 --- a/src/imap/imf_view.rs +++ b/src/imap/imf_view.rs @@ -40,40 +40,40 @@ impl<'a> ImfView<'a> { Envelope { date: NString( - msg.date - .as_ref() - .map(|d| IString::try_from(d.to_rfc3339()).unwrap()), - ), - subject: NString( - msg.subject - .as_ref() - .map(|d| IString::try_from(d.to_string()).unwrap()), - ), - sender: msg - .sender - .as_ref() - .map(|v| vec![convert_mbx(v)]) - .unwrap_or(from.clone()), - reply_to: if msg.reply_to.is_empty() { - from.clone() - } else { - convert_addresses(&msg.reply_to) - }, - from, - to: convert_addresses(&msg.to), - cc: convert_addresses(&msg.cc), - bcc: convert_addresses(&msg.bcc), - in_reply_to: NString( - msg.in_reply_to - .iter() - .next() - .map(|d| IString::try_from(d.to_string()).unwrap()), - ), - message_id: NString( - msg.msg_id - .as_ref() - .map(|d| IString::try_from(d.to_string()).unwrap()), - ), + msg.date + .as_ref() + .map(|d| IString::try_from(d.to_rfc3339()).unwrap()), + ), + subject: NString( + msg.subject + .as_ref() + .map(|d| IString::try_from(d.to_string()).unwrap()), + ), + sender: msg + .sender + .as_ref() + .map(|v| vec![convert_mbx(v)]) + .unwrap_or(from.clone()), + reply_to: if msg.reply_to.is_empty() { + from.clone() + } else { + convert_addresses(&msg.reply_to) + }, + from, + to: convert_addresses(&msg.to), + cc: convert_addresses(&msg.cc), + bcc: convert_addresses(&msg.bcc), + in_reply_to: NString( + msg.in_reply_to + .iter() + .next() + .map(|d| IString::try_from(d.to_string()).unwrap()), + ), + message_id: NString( + msg.msg_id + .as_ref() + .map(|d| IString::try_from(d.to_string()).unwrap()), + ), } } } diff --git a/src/imap/index.rs b/src/imap/index.rs index 48d5ebd..8ec3cca 100644 --- a/src/imap/index.rs +++ b/src/imap/index.rs @@ -1,7 +1,7 @@ use std::num::NonZeroU32; use anyhow::{anyhow, bail, Result}; -use imap_codec::imap_types::sequence::{self, Sequence, SequenceSet, SeqOrUid}; +use imap_codec::imap_types::sequence::{self, SeqOrUid, Sequence, SequenceSet}; use crate::mail::uidindex::{ImapUid, UidIndex}; use crate::mail::unique_ident::UniqueIdent; @@ -96,18 +96,20 @@ pub struct MailIndex<'a> { } impl<'a> MailIndex<'a> { - // The following functions are used to implement the SEARCH command + // The following functions are used to implement the SEARCH command pub fn is_in_sequence_i(&self, seq: &Sequence) -> bool { match seq { Sequence::Single(SeqOrUid::Asterisk) => true, Sequence::Single(SeqOrUid::Value(target)) => target == &self.i, - Sequence::Range(SeqOrUid::Asterisk, SeqOrUid::Value(x)) - | Sequence::Range(SeqOrUid::Value(x), SeqOrUid::Asterisk) => x <= &self.i, - Sequence::Range(SeqOrUid::Value(x1), SeqOrUid::Value(x2)) => if x1 < x2 { - x1 <= &self.i && &self.i <= x2 - } else { - x1 >= &self.i && &self.i >= x2 - }, + Sequence::Range(SeqOrUid::Asterisk, SeqOrUid::Value(x)) + | Sequence::Range(SeqOrUid::Value(x), SeqOrUid::Asterisk) => x <= &self.i, + Sequence::Range(SeqOrUid::Value(x1), SeqOrUid::Value(x2)) => { + if x1 < x2 { + x1 <= &self.i && &self.i <= x2 + } else { + x1 >= &self.i && &self.i >= x2 + } + } Sequence::Range(SeqOrUid::Asterisk, SeqOrUid::Asterisk) => true, } } @@ -117,17 +119,21 @@ impl<'a> MailIndex<'a> { Sequence::Single(SeqOrUid::Asterisk) => true, Sequence::Single(SeqOrUid::Value(target)) => target == &self.uid, Sequence::Range(SeqOrUid::Asterisk, SeqOrUid::Value(x)) - | Sequence::Range(SeqOrUid::Value(x),SeqOrUid::Asterisk) => x <= &self.uid, - Sequence::Range(SeqOrUid::Value(x1), SeqOrUid::Value(x2)) => if x1 < x2 { - x1 <= &self.uid && &self.uid <= x2 - } else { - x1 >= &self.uid && &self.uid >= x2 - }, + | Sequence::Range(SeqOrUid::Value(x), SeqOrUid::Asterisk) => x <= &self.uid, + Sequence::Range(SeqOrUid::Value(x1), SeqOrUid::Value(x2)) => { + if x1 < x2 { + x1 <= &self.uid && &self.uid <= x2 + } else { + x1 >= &self.uid && &self.uid >= x2 + } + } Sequence::Range(SeqOrUid::Asterisk, SeqOrUid::Asterisk) => true, } } pub fn is_flag_set(&self, flag: &str) -> bool { - self.flags.iter().any(|candidate| candidate.as_str() == flag) + self.flags + .iter() + .any(|candidate| candidate.as_str() == flag) } } diff --git a/src/imap/mail_view.rs b/src/imap/mail_view.rs index baeb2af..2c8723e 100644 --- a/src/imap/mail_view.rs +++ b/src/imap/mail_view.rs @@ -1,7 +1,7 @@ use std::num::NonZeroU32; use anyhow::{anyhow, bail, Result}; -use chrono::{Offset, TimeZone, Utc, DateTime as ChronoDateTime, Local, naive::NaiveDate}; +use chrono::{naive::NaiveDate, DateTime as ChronoDateTime, Local, Offset, TimeZone, Utc}; use imap_codec::imap_types::core::NString; use imap_codec::imap_types::datetime::DateTime; @@ -167,7 +167,11 @@ impl<'a> MailView<'a> { } fn envelope(&self) -> MessageDataItem<'static> { - MessageDataItem::Envelope(self.imf().expect("an imf object is derivable from fetchedmail").message_envelope()) + MessageDataItem::Envelope( + self.imf() + .expect("an imf object is derivable from fetchedmail") + .message_envelope(), + ) } fn body(&self) -> Result> { @@ -237,7 +241,6 @@ impl<'a> MailView<'a> { .ok_or(anyhow!("Unable to parse internal date"))?; Ok(MessageDataItem::InternalDate(DateTime::unvalidated(dt))) } - } pub enum SeenFlag { diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs index 362e2e2..2841b7b 100644 --- a/src/imap/mailbox_view.rs +++ b/src/imap/mailbox_view.rs @@ -323,10 +323,7 @@ impl MailboxView { // 4. Fetch additional info about the emails let query_scope = crit.query_scope(); - let uuids = to_fetch - .iter() - .map(|midx| midx.uuid) - .collect::>(); + let uuids = to_fetch.iter().map(|midx| midx.uuid).collect::>(); let query_result = self.0.query(&uuids, query_scope).fetch().await?; // 5. If needed, filter the selection based on the body diff --git a/src/imap/search.rs b/src/imap/search.rs index fb889d7..2b0b34b 100644 --- a/src/imap/search.rs +++ b/src/imap/search.rs @@ -5,9 +5,9 @@ use imap_codec::imap_types::core::NonEmptyVec; use imap_codec::imap_types::search::SearchKey; use imap_codec::imap_types::sequence::{SeqOrUid, Sequence, SequenceSet}; -use crate::mail::query::{QueryScope, QueryResult}; use crate::imap::index::MailIndex; use crate::imap::mail_view::MailView; +use crate::mail::query::{QueryResult, QueryScope}; pub enum SeqType { Undefined, @@ -20,7 +20,6 @@ impl SeqType { } } - pub struct Criteria<'a>(pub &'a SearchKey<'a>); impl<'a> Criteria<'a> { /// Returns a set of email identifiers that is greater or equal @@ -87,11 +86,16 @@ impl<'a> Criteria<'a> { use SearchKey::*; match self.0 { // Combinators - And(and_list) => and_list.as_ref().iter().fold(QueryScope::Index, |prev, sk| { - prev.union(&Criteria(sk).query_scope()) - }), + And(and_list) => and_list + .as_ref() + .iter() + .fold(QueryScope::Index, |prev, sk| { + prev.union(&Criteria(sk).query_scope()) + }), Not(inner) => Criteria(inner).query_scope(), - Or(left, right) => Criteria(left).query_scope().union(&Criteria(right).query_scope()), + Or(left, right) => Criteria(left) + .query_scope() + .union(&Criteria(right).query_scope()), All => QueryScope::Index, // IMF Headers @@ -109,9 +113,12 @@ impl<'a> Criteria<'a> { } /// Returns emails that we now for sure we want to keep - /// but also a second list of emails we need to investigate further by + /// but also a second list of emails we need to investigate further by /// fetching some remote data - pub fn filter_on_idx<'b>(&self, midx_list: &[MailIndex<'b>]) -> (Vec>, Vec>) { + pub fn filter_on_idx<'b>( + &self, + midx_list: &[MailIndex<'b>], + ) -> (Vec>, Vec>) { let (p1, p2): (Vec<_>, Vec<_>) = midx_list .iter() .map(|x| (x, self.is_keep_on_idx(x))) @@ -124,7 +131,11 @@ impl<'a> Criteria<'a> { (to_keep, to_fetch) } - pub fn filter_on_query<'b>(&self, midx_list: &[MailIndex<'b>], query_result: &'b Vec>) -> Result>> { + pub fn filter_on_query<'b>( + &self, + midx_list: &[MailIndex<'b>], + query_result: &'b Vec>, + ) -> Result>> { Ok(midx_list .iter() .zip(query_result.iter()) @@ -137,8 +148,8 @@ impl<'a> Criteria<'a> { } // ---- - - /// Here we are doing a partial filtering: we do not have access + + /// Here we are doing a partial filtering: we do not have access /// to the headers or to the body, so every time we encounter a rule /// based on them, we need to keep it. /// @@ -151,7 +162,9 @@ impl<'a> Criteria<'a> { And(expr_list) => expr_list .as_ref() .iter() - .fold(PartialDecision::Keep, |acc, cur| acc.and(&Criteria(cur).is_keep_on_idx(midx))), + .fold(PartialDecision::Keep, |acc, cur| { + acc.and(&Criteria(cur).is_keep_on_idx(midx)) + }), Or(left, right) => { let left_decision = Criteria(left).is_keep_on_idx(midx); let right_decision = Criteria(right).is_keep_on_idx(midx); @@ -163,7 +176,7 @@ impl<'a> Criteria<'a> { // Sequence logic maybe_seq if is_sk_seq(maybe_seq) => is_keep_seq(maybe_seq, midx).into(), maybe_flag if is_sk_flag(maybe_flag) => is_keep_flag(maybe_flag, midx).into(), - + // All the stuff we can't evaluate yet Bcc(_) | Cc(_) | From(_) | Header(..) | SentBefore(_) | SentOn(_) | SentSince(_) | Subject(_) | To(_) | Before(_) | On(_) | Since(_) | Larger(_) | Smaller(_) @@ -172,16 +185,15 @@ impl<'a> Criteria<'a> { unknown => { tracing::error!("Unknown filter {:?}", unknown); PartialDecision::Discard - }, + } } } - /// @TODO we re-eveluate twice the same logic. The correct way would be, on each pass, /// to simplify the searck query, by removing the elements that were already checked. /// For example if we have AND(OR(seqid(X), body(Y)), body(X)), we can't keep for sure /// the email, as body(x) might be false. So we need to check it. But as seqid(x) is true, - /// we could simplify the request to just body(x) and truncate the first OR. Today, we are + /// we could simplify the request to just body(x) and truncate the first OR. Today, we are /// not doing that, and thus we reevaluate everything. fn is_keep_on_query(&self, mail_view: &MailView) -> bool { use SearchKey::*; @@ -192,7 +204,8 @@ impl<'a> Criteria<'a> { .iter() .all(|cur| Criteria(cur).is_keep_on_query(mail_view)), Or(left, right) => { - Criteria(left).is_keep_on_query(mail_view) || Criteria(right).is_keep_on_query(mail_view) + Criteria(left).is_keep_on_query(mail_view) + || Criteria(right).is_keep_on_query(mail_view) } Not(expr) => !Criteria(expr).is_keep_on_query(mail_view), All => true, @@ -209,38 +222,82 @@ impl<'a> Criteria<'a> { On(search_naive) => match mail_view.stored_naive_date() { Ok(msg_naive) => &msg_naive == search_naive.as_ref(), _ => false, - }, + }, Since(search_naive) => match mail_view.stored_naive_date() { Ok(msg_naive) => &msg_naive > search_naive.as_ref(), _ => false, }, // Message size is also stored in MailMeta - Larger(size_ref) => mail_view.query_result.metadata().expect("metadata were fetched").rfc822_size > *size_ref as usize, - Smaller(size_ref) => mail_view.query_result.metadata().expect("metadata were fetched").rfc822_size < *size_ref as usize, + Larger(size_ref) => { + mail_view + .query_result + .metadata() + .expect("metadata were fetched") + .rfc822_size + > *size_ref as usize + } + Smaller(size_ref) => { + mail_view + .query_result + .metadata() + .expect("metadata were fetched") + .rfc822_size + < *size_ref as usize + } // Filter on well-known headers - Bcc(txt) => mail_view.is_header_contains_pattern(&b"bcc"[..], txt.as_ref()), - Cc(txt) => mail_view.is_header_contains_pattern(&b"cc"[..], txt.as_ref()), + Bcc(txt) => mail_view.is_header_contains_pattern(&b"bcc"[..], txt.as_ref()), + Cc(txt) => mail_view.is_header_contains_pattern(&b"cc"[..], txt.as_ref()), From(txt) => mail_view.is_header_contains_pattern(&b"from"[..], txt.as_ref()), - Subject(txt)=> mail_view.is_header_contains_pattern(&b"subject"[..], txt.as_ref()), + Subject(txt) => mail_view.is_header_contains_pattern(&b"subject"[..], txt.as_ref()), To(txt) => mail_view.is_header_contains_pattern(&b"to"[..], txt.as_ref()), - Header(hdr, txt) => mail_view.is_header_contains_pattern(hdr.as_ref(), txt.as_ref()), + Header(hdr, txt) => mail_view.is_header_contains_pattern(hdr.as_ref(), txt.as_ref()), // Filter on Date header - SentBefore(search_naive) => mail_view.imf().map(|imf| imf.naive_date().ok()).flatten().map(|msg_naive| &msg_naive < search_naive.as_ref()).unwrap_or(false), - SentOn(search_naive) => mail_view.imf().map(|imf| imf.naive_date().ok()).flatten().map(|msg_naive| &msg_naive == search_naive.as_ref()).unwrap_or(false), - SentSince(search_naive) => mail_view.imf().map(|imf| imf.naive_date().ok()).flatten().map(|msg_naive| &msg_naive > search_naive.as_ref()).unwrap_or(false), - + SentBefore(search_naive) => mail_view + .imf() + .map(|imf| imf.naive_date().ok()) + .flatten() + .map(|msg_naive| &msg_naive < search_naive.as_ref()) + .unwrap_or(false), + SentOn(search_naive) => mail_view + .imf() + .map(|imf| imf.naive_date().ok()) + .flatten() + .map(|msg_naive| &msg_naive == search_naive.as_ref()) + .unwrap_or(false), + SentSince(search_naive) => mail_view + .imf() + .map(|imf| imf.naive_date().ok()) + .flatten() + .map(|msg_naive| &msg_naive > search_naive.as_ref()) + .unwrap_or(false), // Filter on the full content of the email - Text(txt) => mail_view.content.as_msg().map(|msg| msg.raw_part.windows(txt.as_ref().len()).any(|win| win == txt.as_ref())).unwrap_or(false), - Body(txt) => mail_view.content.as_msg().map(|msg| msg.raw_body.windows(txt.as_ref().len()).any(|win| win == txt.as_ref())).unwrap_or(false), + Text(txt) => mail_view + .content + .as_msg() + .map(|msg| { + msg.raw_part + .windows(txt.as_ref().len()) + .any(|win| win == txt.as_ref()) + }) + .unwrap_or(false), + Body(txt) => mail_view + .content + .as_msg() + .map(|msg| { + msg.raw_body + .windows(txt.as_ref().len()) + .any(|win| win == txt.as_ref()) + }) + .unwrap_or(false), unknown => { tracing::error!("Unknown filter {:?}", unknown); false - }, + } } } } @@ -281,7 +338,7 @@ enum PartialDecision { Discard, Postpone, } -impl From for PartialDecision { +impl From for PartialDecision { fn from(x: bool) -> Self { match x { true => PartialDecision::Keep, @@ -323,9 +380,8 @@ impl PartialDecision { fn is_sk_flag(sk: &SearchKey) -> bool { use SearchKey::*; match sk { - Answered | Deleted | Draft | Flagged | Keyword(..) | New | Old - | Recent | Seen | Unanswered | Undeleted | Undraft - | Unflagged | Unkeyword(..) | Unseen => true, + Answered | Deleted | Draft | Flagged | Keyword(..) | New | Old | Recent | Seen + | Unanswered | Undeleted | Undraft | Unflagged | Unkeyword(..) | Unseen => true, _ => false, } } @@ -342,37 +398,37 @@ fn is_keep_flag(sk: &SearchKey, midx: &MailIndex) -> bool { let is_recent = midx.is_flag_set("\\Recent"); let is_seen = midx.is_flag_set("\\Seen"); is_recent && !is_seen - }, + } Old => { let is_recent = midx.is_flag_set("\\Recent"); !is_recent - }, - Recent => midx.is_flag_set("\\Recent"), - Seen => midx.is_flag_set("\\Seen"), - Unanswered => { + } + Recent => midx.is_flag_set("\\Recent"), + Seen => midx.is_flag_set("\\Seen"), + Unanswered => { let is_answered = midx.is_flag_set("\\Recent"); !is_answered - }, + } Undeleted => { let is_deleted = midx.is_flag_set("\\Deleted"); !is_deleted - }, + } Undraft => { let is_draft = midx.is_flag_set("\\Draft"); !is_draft - }, + } Unflagged => { let is_flagged = midx.is_flag_set("\\Flagged"); !is_flagged - }, + } Unkeyword(kw) => { let is_keyword_set = midx.is_flag_set(kw.inner()); !is_keyword_set - }, + } Unseen => { let is_seen = midx.is_flag_set("\\Seen"); !is_seen - }, + } // Not flag logic _ => unreachable!(), @@ -389,8 +445,16 @@ fn is_sk_seq(sk: &SearchKey) -> bool { fn is_keep_seq(sk: &SearchKey, midx: &MailIndex) -> bool { use SearchKey::*; match sk { - SequenceSet(seq_set) => seq_set.0.as_ref().iter().any(|seq| midx.is_in_sequence_i(seq)), - Uid(seq_set) => seq_set.0.as_ref().iter().any(|seq| midx.is_in_sequence_uid(seq)), + SequenceSet(seq_set) => seq_set + .0 + .as_ref() + .iter() + .any(|seq| midx.is_in_sequence_i(seq)), + Uid(seq_set) => seq_set + .0 + .as_ref() + .iter() + .any(|seq| midx.is_in_sequence_uid(seq)), _ => unreachable!(), } }