aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/imap/command/anonymous.rs8
-rw-r--r--src/imap/command/authenticated.rs11
-rw-r--r--src/imap/command/selected.rs5
-rw-r--r--src/imap/flow.rs8
-rw-r--r--src/mail/mailbox.rs165
-rw-r--r--src/mail/mod.rs151
-rw-r--r--src/mail/user.rs31
7 files changed, 213 insertions, 166 deletions
diff --git a/src/imap/command/anonymous.rs b/src/imap/command/anonymous.rs
index 7e72458..5f982ba 100644
--- a/src/imap/command/anonymous.rs
+++ b/src/imap/command/anonymous.rs
@@ -5,6 +5,7 @@ use imap_codec::types::core::{AString};
use imap_codec::types::response::{Capability, Data, Status};
use crate::imap::flow;
+use crate::mail::user::User;
use crate::login::ArcLoginProvider;
//--- dispatching
@@ -68,10 +69,9 @@ impl<'a> AnonymousContext<'a> {
Ok(c) => c,
};
- let user = flow::User {
- creds,
- name: u.clone(),
- };
+ let s3_client = creds.s3_client();
+ let k2v_client = creds.k2v_client();
+ let user = User::new(u.clone(), creds)?;
tracing::info!(username=%u, "connected");
Ok((
diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs
index 392069f..b79865f 100644
--- a/src/imap/command/authenticated.rs
+++ b/src/imap/command/authenticated.rs
@@ -9,7 +9,8 @@ use imap_codec::types::response::{Code, Data, Status};
use crate::imap::command::anonymous;
use crate::imap::flow;
-use crate::mail::Mailbox;
+use crate::mail::mailbox::Mailbox;
+use crate::mail::user::User;
const DEFAULT_FLAGS: [Flag; 5] = [
Flag::Seen,
@@ -21,7 +22,7 @@ const DEFAULT_FLAGS: [Flag; 5] = [
pub struct AuthenticatedContext<'a> {
pub req: &'a Request,
- pub user: &'a flow::User,
+ pub user: &'a User,
}
pub async fn dispatch<'a>(ctx: AuthenticatedContext<'a>) -> Result<(Response, flow::Transition)> {
@@ -95,8 +96,8 @@ impl<'a> AuthenticatedContext<'a> {
async fn select(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
let name = String::try_from(mailbox.clone())?;
- let mut mb = Mailbox::new(&self.user.creds, name.clone())?;
- tracing::info!(username=%self.user.name, mailbox=%name, "mailbox.selected");
+ let mut mb = self.user.open_mailbox(name.clone())?;
+ tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.selected");
let sum = mb.summary().await?;
tracing::trace!(summary=%sum, "mailbox.summary");
@@ -112,7 +113,7 @@ impl<'a> AuthenticatedContext<'a> {
Some('$') if f == "$unseen" => None,
Some(_) => match Atom::try_from(f.clone()) {
Err(_) => {
- tracing::error!(username=%self.user.name, mailbox=%name, flag=%f, "Unable to encode flag as IMAP atom");
+ tracing::error!(username=%self.user.username, mailbox=%name, flag=%f, "Unable to encode flag as IMAP atom");
None
},
Ok(a) => Some(Flag::Keyword(a)),
diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs
index d3dddd4..bd46bd5 100644
--- a/src/imap/command/selected.rs
+++ b/src/imap/command/selected.rs
@@ -10,11 +10,12 @@ use imap_codec::types::sequence::SequenceSet;
use crate::imap::command::authenticated;
use crate::imap::flow;
-use crate::mail::Mailbox;
+use crate::mail::mailbox::Mailbox;
+use crate::mail::user::User;
pub struct SelectedContext<'a> {
pub req: &'a Request,
- pub user: &'a flow::User,
+ pub user: &'a User,
pub mailbox: &'a mut Mailbox,
}
diff --git a/src/imap/flow.rs b/src/imap/flow.rs
index 7370bd1..369bee6 100644
--- a/src/imap/flow.rs
+++ b/src/imap/flow.rs
@@ -1,13 +1,9 @@
use std::error::Error as StdError;
use std::fmt;
-use crate::login::Credentials;
-use crate::mail::Mailbox;
+use crate::mail::mailbox::Mailbox;
+use crate::mail::user::User;
-pub struct User {
- pub name: String,
- pub creds: Credentials,
-}
#[derive(Debug)]
pub enum Error {
diff --git a/src/mail/mailbox.rs b/src/mail/mailbox.rs
new file mode 100644
index 0000000..e19dfd8
--- /dev/null
+++ b/src/mail/mailbox.rs
@@ -0,0 +1,165 @@
+use std::convert::TryFrom;
+
+use anyhow::Result;
+use k2v_client::K2vClient;
+use rusoto_s3::S3Client;
+
+use crate::bayou::Bayou;
+use crate::cryptoblob::Key;
+use crate::login::Credentials;
+use crate::mail::mail_ident::*;
+use crate::mail::uidindex::*;
+use crate::mail::IMF;
+
+pub struct Summary<'a> {
+ pub validity: ImapUidvalidity,
+ pub next: ImapUid,
+ pub exists: u32,
+ pub recent: u32,
+ pub flags: FlagIter<'a>,
+ pub unseen: Option<&'a ImapUid>,
+}
+impl std::fmt::Display for Summary<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(
+ f,
+ "uidvalidity: {}, uidnext: {}, exists: {}",
+ self.validity, self.next, self.exists
+ )
+ }
+}
+
+// Non standard but common flags:
+// https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml
+pub struct Mailbox {
+ bucket: String,
+ pub name: String,
+ key: Key,
+
+ k2v: K2vClient,
+ s3: S3Client,
+
+ uid_index: Bayou<UidIndex>,
+ mail_path: String,
+}
+
+impl Mailbox {
+ pub(super) fn new(creds: &Credentials, name: String) -> Result<Self> {
+ let index_path = format!("index/{}", name);
+ let mail_path = format!("mail/{}", name);
+ let uid_index = Bayou::<UidIndex>::new(creds, index_path)?;
+
+ Ok(Self {
+ bucket: creds.bucket().to_string(),
+ name,
+ key: creds.keys.master.clone(),
+ k2v: creds.k2v_client()?,
+ s3: creds.s3_client()?,
+ uid_index,
+ mail_path,
+ })
+ }
+
+ // Get a summary of the mailbox, useful for the SELECT command for example
+ pub async fn summary(&mut self) -> Result<Summary> {
+ self.uid_index.sync().await?;
+ let state = self.uid_index.state();
+
+ let unseen = state
+ .idx_by_flag
+ .get(&"$unseen".to_string())
+ .and_then(|os| os.get_min());
+ let recent = state
+ .idx_by_flag
+ .get(&"\\Recent".to_string())
+ .map(|os| os.len())
+ .unwrap_or(0);
+
+ return Ok(Summary {
+ validity: state.uidvalidity,
+ next: state.uidnext,
+ exists: u32::try_from(state.idx_by_uid.len())?,
+ recent: u32::try_from(recent)?,
+ flags: state.idx_by_flag.flags(),
+ unseen,
+ });
+ }
+
+ // Insert an email in the mailbox
+ pub async fn append(&mut self, _msg: IMF) -> Result<()> {
+ Ok(())
+ }
+
+ // Copy an email from an external to this mailbox
+ // @FIXME is it needed or could we implement it with append?
+ pub async fn copy(&mut self, _mailbox: String, _uid: ImapUid) -> Result<()> {
+ Ok(())
+ }
+
+ // Delete all emails with the \Delete flag in the mailbox
+ // Can be called by CLOSE and EXPUNGE
+ // @FIXME do we want to implement this feature or a simpler "delete" command
+ // The controller could then "fetch \Delete" and call delete on each email?
+ pub async fn expunge(&mut self) -> Result<()> {
+ Ok(())
+ }
+
+ // Update flags of a range of emails
+ pub async fn store(&mut self) -> Result<()> {
+ Ok(())
+ }
+
+ pub async fn fetch(&mut self) -> Result<()> {
+ Ok(())
+ }
+
+ pub async fn test(&mut self) -> Result<()> {
+ self.uid_index.sync().await?;
+
+ dump(&self.uid_index);
+
+ let add_mail_op = self
+ .uid_index
+ .state()
+ .op_mail_add(gen_ident(), vec!["\\Unseen".into()]);
+ self.uid_index.push(add_mail_op).await?;
+
+ dump(&self.uid_index);
+
+ if self.uid_index.state().idx_by_uid.len() > 6 {
+ for i in 0..2 {
+ let (_, ident) = self
+ .uid_index
+ .state()
+ .idx_by_uid
+ .iter()
+ .skip(3 + i)
+ .next()
+ .unwrap();
+ let del_mail_op = self.uid_index.state().op_mail_del(*ident);
+ self.uid_index.push(del_mail_op).await?;
+
+ dump(&self.uid_index);
+ }
+ }
+
+ Ok(())
+ }
+}
+
+fn dump(uid_index: &Bayou<UidIndex>) {
+ let s = uid_index.state();
+ println!("---- MAILBOX STATE ----");
+ println!("UIDVALIDITY {}", s.uidvalidity);
+ println!("UIDNEXT {}", s.uidnext);
+ println!("INTERNALSEQ {}", s.internalseq);
+ for (uid, ident) in s.idx_by_uid.iter() {
+ println!(
+ "{} {} {}",
+ uid,
+ hex::encode(ident.0),
+ s.table.get(ident).cloned().unwrap().1.join(", ")
+ );
+ }
+ println!("");
+}
diff --git a/src/mail/mod.rs b/src/mail/mod.rs
index a130073..f696f6d 100644
--- a/src/mail/mod.rs
+++ b/src/mail/mod.rs
@@ -1,4 +1,6 @@
pub mod mail_ident;
+pub mod user;
+pub mod mailbox;
mod uidindex;
use std::convert::TryFrom;
@@ -16,152 +18,3 @@ use crate::mail::uidindex::*;
// Internet Message Format
// aka RFC 822 - RFC 2822 - RFC 5322
pub struct IMF(Vec<u8>);
-
-pub struct Summary<'a> {
- pub validity: ImapUidvalidity,
- pub next: ImapUid,
- pub exists: u32,
- pub recent: u32,
- pub flags: FlagIter<'a>,
- pub unseen: Option<&'a ImapUid>,
-}
-impl std::fmt::Display for Summary<'_> {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(
- f,
- "uidvalidity: {}, uidnext: {}, exists: {}",
- self.validity, self.next, self.exists
- )
- }
-}
-
-// Non standard but common flags:
-// https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml
-pub struct Mailbox {
- bucket: String,
- pub name: String,
- key: Key,
-
- k2v: K2vClient,
- s3: S3Client,
-
- uid_index: Bayou<UidIndex>,
-}
-
-impl Mailbox {
- pub fn new(creds: &Credentials, name: String) -> Result<Self> {
- let uid_index = Bayou::<UidIndex>::new(creds, name.clone())?;
-
- Ok(Self {
- bucket: creds.bucket().to_string(),
- name,
- key: creds.keys.master.clone(),
- k2v: creds.k2v_client()?,
- s3: creds.s3_client()?,
- uid_index,
- })
- }
-
- // Get a summary of the mailbox, useful for the SELECT command for example
- pub async fn summary(&mut self) -> Result<Summary> {
- self.uid_index.sync().await?;
- let state = self.uid_index.state();
-
- let unseen = state
- .idx_by_flag
- .get(&"$unseen".to_string())
- .and_then(|os| os.get_min());
- let recent = state
- .idx_by_flag
- .get(&"\\Recent".to_string())
- .map(|os| os.len())
- .unwrap_or(0);
-
- return Ok(Summary {
- validity: state.uidvalidity,
- next: state.uidnext,
- exists: u32::try_from(state.idx_by_uid.len())?,
- recent: u32::try_from(recent)?,
- flags: state.idx_by_flag.flags(),
- unseen,
- });
- }
-
- // Insert an email in the mailbox
- pub async fn append(&mut self, _msg: IMF) -> Result<()> {
- Ok(())
- }
-
- // Copy an email from an external to this mailbox
- // @FIXME is it needed or could we implement it with append?
- pub async fn copy(&mut self, _mailbox: String, _uid: ImapUid) -> Result<()> {
- Ok(())
- }
-
- // Delete all emails with the \Delete flag in the mailbox
- // Can be called by CLOSE and EXPUNGE
- // @FIXME do we want to implement this feature or a simpler "delete" command
- // The controller could then "fetch \Delete" and call delete on each email?
- pub async fn expunge(&mut self) -> Result<()> {
- Ok(())
- }
-
- // Update flags of a range of emails
- pub async fn store(&mut self) -> Result<()> {
- Ok(())
- }
-
- pub async fn fetch(&mut self) -> Result<()> {
- Ok(())
- }
-
- pub async fn test(&mut self) -> Result<()> {
- self.uid_index.sync().await?;
-
- dump(&self.uid_index);
-
- let add_mail_op = self
- .uid_index
- .state()
- .op_mail_add(gen_ident(), vec!["\\Unseen".into()]);
- self.uid_index.push(add_mail_op).await?;
-
- dump(&self.uid_index);
-
- if self.uid_index.state().idx_by_uid.len() > 6 {
- for i in 0..2 {
- let (_, ident) = self
- .uid_index
- .state()
- .idx_by_uid
- .iter()
- .skip(3 + i)
- .next()
- .unwrap();
- let del_mail_op = self.uid_index.state().op_mail_del(*ident);
- self.uid_index.push(del_mail_op).await?;
-
- dump(&self.uid_index);
- }
- }
-
- Ok(())
- }
-}
-
-fn dump(uid_index: &Bayou<UidIndex>) {
- let s = uid_index.state();
- println!("---- MAILBOX STATE ----");
- println!("UIDVALIDITY {}", s.uidvalidity);
- println!("UIDNEXT {}", s.uidnext);
- println!("INTERNALSEQ {}", s.internalseq);
- for (uid, ident) in s.idx_by_uid.iter() {
- println!(
- "{} {} {}",
- uid,
- hex::encode(ident.0),
- s.table.get(ident).cloned().unwrap().1.join(", ")
- );
- }
- println!("");
-}
diff --git a/src/mail/user.rs b/src/mail/user.rs
new file mode 100644
index 0000000..7465ab0
--- /dev/null
+++ b/src/mail/user.rs
@@ -0,0 +1,31 @@
+use anyhow::Result;
+
+use k2v_client::K2vClient;
+use rusoto_s3::S3Client;
+
+use crate::login::Credentials;
+use crate::mail::mailbox::Mailbox;
+
+pub struct User {
+ pub username: String,
+ pub creds: Credentials,
+ pub s3_client: S3Client,
+ pub k2v_client: K2vClient,
+}
+
+impl User {
+ pub fn new(username: String, creds: Credentials) -> Result<Self> {
+ let s3_client = creds.s3_client()?;
+ let k2v_client = creds.k2v_client()?;
+ Ok(Self {
+ username,
+ creds,
+ s3_client,
+ k2v_client,
+ })
+ }
+
+ pub fn open_mailbox(&self, name: String) -> Result<Mailbox> {
+ Mailbox::new(&self.creds, name)
+ }
+}