aboutsummaryrefslogtreecommitdiff
path: root/src/imap/search.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap/search.rs')
-rw-r--r--src/imap/search.rs160
1 files changed, 112 insertions, 48 deletions
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<MailIndex<'b>>, Vec<MailIndex<'b>>) {
+ pub fn filter_on_idx<'b>(
+ &self,
+ midx_list: &[MailIndex<'b>],
+ ) -> (Vec<MailIndex<'b>>, Vec<MailIndex<'b>>) {
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<QueryResult<'b>>) -> Result<Vec<MailIndex<'b>>> {
+ pub fn filter_on_query<'b>(
+ &self,
+ midx_list: &[MailIndex<'b>],
+ query_result: &'b Vec<QueryResult<'b>>,
+ ) -> Result<Vec<MailIndex<'b>>> {
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<bool> for PartialDecision {
+impl From<bool> 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!(),
}
}