aboutsummaryrefslogtreecommitdiff
path: root/src/imap
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap')
-rw-r--r--src/imap/imf_view.rs68
-rw-r--r--src/imap/index.rs38
-rw-r--r--src/imap/mail_view.rs9
-rw-r--r--src/imap/mailbox_view.rs5
-rw-r--r--src/imap/search.rs160
5 files changed, 175 insertions, 105 deletions
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<MessageDataItem<'static>> {
@@ -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::<Vec<_>>();
+ let uuids = to_fetch.iter().map(|midx| midx.uuid).collect::<Vec<_>>();
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<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!(),
}
}