From 7959adb8e970c9006ea9799b5ddd6b2c9aac217a Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Tue, 12 Jul 2022 17:32:57 +0200 Subject: [PATCH] implement STORE --- src/imap/command/selected.rs | 20 ++++-- src/imap/mailbox_view.rs | 128 ++++++++++++++++++++++++++++------- 2 files changed, 119 insertions(+), 29 deletions(-) diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs index 848302c..a6fa645 100644 --- a/src/imap/command/selected.rs +++ b/src/imap/command/selected.rs @@ -67,13 +67,21 @@ impl<'a> SelectedContext<'a> { async fn store( self, - _sequence_set: &SequenceSet, - _kind: &StoreType, - _response: &StoreResponse, - _flags: &[Flag], - _uid: &bool, + sequence_set: &SequenceSet, + kind: &StoreType, + response: &StoreResponse, + flags: &[Flag], + uid: &bool, ) -> Result<(Response, flow::Transition)> { - Ok((Response::bad("Not implemented")?, flow::Transition::None)) + let data = self + .mailbox + .store(sequence_set, kind, response, flags, uid) + .await?; + + Ok(( + Response::ok("STORE completed")?.with_body(data), + flow::Transition::None, + )) } async fn copy( diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs index e4df3d0..93d3b4d 100644 --- a/src/imap/mailbox_view.rs +++ b/src/imap/mailbox_view.rs @@ -13,13 +13,14 @@ use imap_codec::types::core::{Atom, IString, NString}; use imap_codec::types::datetime::MyDateTime; use imap_codec::types::envelope::Envelope; use imap_codec::types::fetch_attributes::{FetchAttribute, MacroOrFetchAttributes}; -use imap_codec::types::flag::Flag; +use imap_codec::types::flag::{Flag, StoreResponse, StoreType}; use imap_codec::types::response::{Code, Data, MessageAttribute, Status}; use imap_codec::types::sequence::{self, SequenceSet}; use mail_parser::*; use crate::mail::mailbox::Mailbox; use crate::mail::uidindex::{ImapUid, ImapUidvalidity, UidIndex}; +use crate::mail::unique_ident::UniqueIdent; const DEFAULT_FLAGS: [Flag; 5] = [ Flag::Seen, @@ -144,6 +145,60 @@ impl MailboxView { Ok(data) } + pub async fn store( + &mut self, + sequence_set: &SequenceSet, + kind: &StoreType, + _response: &StoreResponse, + flags: &[Flag], + uid: &bool, + ) -> Result> { + if *uid { + bail!("UID STORE not implemented"); + } + + let flags = flags.iter().map(|x| x.to_string()).collect::>(); + + let mails = self.get_mail_ids(sequence_set)?; + for (i, uid, uuid) in mails.iter() { + match kind { + StoreType::Add => { + self.mailbox.add_flags(*uuid, &flags[..]).await?; + } + StoreType::Remove => { + self.mailbox.del_flags(*uuid, &flags[..]).await?; + } + StoreType::Replace => { + let old_flags = &self + .known_state + .table + .get(uuid) + .ok_or(anyhow!( + "Missing message: {} (UID {}, UUID {})", + i, + uid, + uuid + ))? + .1; + let to_remove = old_flags + .iter() + .filter(|x| !flags.contains(&x)) + .cloned() + .collect::>(); + let to_add = flags + .iter() + .filter(|x| !old_flags.contains(&x)) + .cloned() + .collect::>(); + self.mailbox.add_flags(*uuid, &to_add[..]).await?; + self.mailbox.del_flags(*uuid, &to_remove[..]).await?; + } + } + } + + self.update().await + } + /// Looks up state changes in the mailbox and produces a set of IMAP /// responses describing the new state. pub async fn fetch( @@ -156,28 +211,11 @@ impl MailboxView { bail!("UID FETCH not implemented"); } - let mail_vec = self - .known_state - .idx_by_uid - .iter() - .map(|(uid, uuid)| (*uid, *uuid)) - .collect::>(); - - let mut mails = vec![]; - let iter_strat = sequence::Strategy::Naive { - largest: NonZeroU32::try_from((self.known_state.idx_by_uid.len() + 1) as u32).unwrap(), - }; - for i in sequence_set.iter(iter_strat) { - if let Some(mail) = mail_vec.get(i.get() as usize - 1) { - mails.push((i, *mail)); - } else { - bail!("No such mail: {}", i); - } - } + let mails = self.get_mail_ids(sequence_set)?; let mails_uuid = mails .iter() - .map(|(_i, (_uid, uuid))| *uuid) + .map(|(_i, _uid, uuid)| *uuid) .collect::>(); let mails_meta = self.mailbox.fetch_meta(&mails_uuid).await?; @@ -200,7 +238,7 @@ impl MailboxView { let mut iter = mails .into_iter() .zip(mails_meta.into_iter()) - .map(|((i, (uid, uuid)), meta)| async move { + .map(|((i, uid, uuid), meta)| async move { let body = self.mailbox.fetch_full(uuid, &meta.message_key).await?; Ok::<_, anyhow::Error>((i, uid, uuid, meta, Some(body))) }) @@ -214,7 +252,7 @@ impl MailboxView { mails .into_iter() .zip(mails_meta.into_iter()) - .map(|((i, (uid, uuid)), meta)| (i, uid, uuid, meta, None)) + .map(|((i, uid, uuid), meta)| (i, uid, uuid, meta, None)) .collect::>() }; @@ -314,6 +352,36 @@ impl MailboxView { // ---- + // Gets the UIDs and UUIDs of mails identified by a SequenceSet of + // sequence numbers + fn get_mail_ids( + &self, + sequence_set: &SequenceSet, + ) -> Result> { + let mail_vec = self + .known_state + .idx_by_uid + .iter() + .map(|(uid, uuid)| (*uid, *uuid)) + .collect::>(); + + let mut mails = vec![]; + let iter_strat = sequence::Strategy::Naive { + largest: NonZeroU32::try_from((self.known_state.idx_by_uid.len() + 1) as u32).unwrap(), + }; + for i in sequence_set.iter(iter_strat) { + if let Some(mail) = mail_vec.get(i.get() as usize - 1) { + mails.push((i, mail.0, mail.1)); + } else { + bail!("No such mail: {}", i); + } + } + + Ok(mails) + } + + // ---- + /// Produce an OK [UIDVALIDITY _] message corresponding to `known_state` fn uidvalidity_status(&self) -> Result { let uid_validity = Status::ok( @@ -420,7 +488,21 @@ impl MailboxView { fn string_to_flag(f: &str) -> Option { match f.chars().next() { - Some('\\') => None, + Some('\\') => match f { + "\\Seen" => Some(Flag::Seen), + "\\Answered" => Some(Flag::Answered), + "\\Flagged" => Some(Flag::Flagged), + "\\Deleted" => Some(Flag::Deleted), + "\\Draft" => Some(Flag::Draft), + "\\Recent" => Some(Flag::Recent), + _ => match Atom::try_from(f.strip_prefix('\\').unwrap().clone()) { + Err(_) => { + tracing::error!(flag=%f, "Unable to encode flag as IMAP atom"); + None + } + Ok(a) => Some(Flag::Extension(a)), + }, + }, Some('$') if f == "$unseen" => None, Some(_) => match Atom::try_from(f.clone()) { Err(_) => {