diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs index e10eab6..b3a2ffd 100644 --- a/src/imap/command/selected.rs +++ b/src/imap/command/selected.rs @@ -46,12 +46,13 @@ impl<'a> SelectedContext<'a> { attributes: &MacroOrFetchAttributes, uid: &bool, ) -> Result<(Response, flow::Transition)> { - let resp = self.mailbox.fetch(sequence_set, attributes, uid).await?; - - Ok(( - Response::ok("FETCH completed")?.with_body(resp), - flow::Transition::None, - )) + match self.mailbox.fetch(sequence_set, attributes, uid).await { + Ok(resp) => Ok(( + Response::ok("FETCH completed")?.with_body(resp), + flow::Transition::None, + )), + Err(e) => Ok((Response::no(&e.to_string())?, flow::Transition::None)), + } } pub async fn noop(self) -> Result<(Response, flow::Transition)> { diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs index 6b24c8a..9a956b6 100644 --- a/src/imap/mailbox_view.rs +++ b/src/imap/mailbox_view.rs @@ -1,10 +1,13 @@ +use std::borrow::Borrow; use std::num::NonZeroU32; use std::sync::Arc; use anyhow::{anyhow, bail, Error, Result}; use boitalettres::proto::res::body::Data as Body; use futures::stream::{FuturesOrdered, StreamExt}; +use imap_codec::types::address::Address; use imap_codec::types::core::{Atom, IString, NString}; +use imap_codec::types::envelope::Envelope; use imap_codec::types::fetch_attributes::{FetchAttribute, MacroOrFetchAttributes}; use imap_codec::types::flag::Flag; use imap_codec::types::response::{Code, Data, MessageAttribute, Status}; @@ -210,7 +213,7 @@ impl MailboxView { }; let mut ret = vec![]; - for (i, uid, uuid, meta, _body) in mails { + for (i, uid, uuid, meta, body) in mails { let mut attributes = vec![MessageAttribute::Uid(uid)]; let (_uid2, flags) = self @@ -219,6 +222,14 @@ impl MailboxView { .get(&uuid) .ok_or_else(|| anyhow!("Mail not in uidindex table: {}", uuid))?; + let parsed = match &body { + Some(m) => { + mail_parser::Message::parse(m).ok_or_else(|| anyhow!("Invalid mail body"))? + } + None => mail_parser::Message::parse(&meta.headers) + .ok_or_else(|| anyhow!("Invalid mail headers"))?, + }; + for attr in fetch_attrs.iter() { match attr { FetchAttribute::Uid => (), @@ -235,7 +246,9 @@ impl MailboxView { IString::Literal(meta.headers.clone().try_into().unwrap()), )))) } - + FetchAttribute::Envelope => { + attributes.push(MessageAttribute::Envelope(message_envelope(&parsed))) + } // TODO _ => (), } @@ -247,6 +260,7 @@ impl MailboxView { })); } + tracing::info!("Fetch result: {:?}", ret); Ok(ret) } @@ -354,3 +368,70 @@ fn string_to_flag(f: &str) -> Option { None => None, } } + +fn message_envelope(msg: &mail_parser::Message<'_>) -> Envelope { + Envelope { + date: NString( + msg.get_date() + .map(|d| IString::try_from(d.to_iso8601()).unwrap()), + ), + subject: NString( + msg.get_subject() + .map(|d| IString::try_from(d.to_string()).unwrap()), + ), + from: convert_addresses(msg.get_from()), + sender: convert_addresses(msg.get_sender()), + reply_to: convert_addresses(msg.get_reply_to()), + to: convert_addresses(msg.get_to()), + cc: convert_addresses(msg.get_cc()), + bcc: convert_addresses(msg.get_bcc()), + in_reply_to: NString(None), // TODO + message_id: NString( + msg.get_message_id() + .map(|d| IString::try_from(d.to_string()).unwrap()), + ), + } +} + +fn convert_addresses(a: &mail_parser::HeaderValue<'_>) -> Vec
{ + match a { + mail_parser::HeaderValue::Address(a) => vec![convert_address(a)], + mail_parser::HeaderValue::AddressList(a) => { + let mut ret = vec![]; + for aa in a { + ret.push(convert_address(aa)); + } + ret + } + mail_parser::HeaderValue::Empty => vec![], + mail_parser::HeaderValue::Collection(c) => { + let mut ret = vec![]; + for cc in c.iter() { + ret.extend(convert_addresses(cc).into_iter()); + } + ret + } + _ => panic!("Invalid address header"), + } +} + +fn convert_address(a: &mail_parser::Addr<'_>) -> Address { + let (user, host) = match &a.address { + None => (None, None), + Some(x) => match x.split_once('@') { + Some((u, h)) => (Some(u.to_string()), Some(h.to_string())), + None => (Some(x.to_string()), None), + }, + }; + + Address::new( + NString( + a.name + .as_ref() + .map(|x| IString::try_from(x.to_string()).unwrap()), + ), + NString(None), + NString(user.map(|x| IString::try_from(x).unwrap())), + NString(host.map(|x| IString::try_from(x).unwrap())), + ) +} diff --git a/src/mail/mailbox.rs b/src/mail/mailbox.rs index f559d5c..bb1ca7a 100644 --- a/src/mail/mailbox.rs +++ b/src/mail/mailbox.rs @@ -1,9 +1,7 @@ use anyhow::{anyhow, bail, Result}; use k2v_client::K2vClient; use k2v_client::{BatchReadOp, Filter, K2vValue}; -use rusoto_s3::{ - DeleteObjectRequest, GetObjectRequest, PutObjectRequest, S3Client, S3, -}; +use rusoto_s3::{DeleteObjectRequest, GetObjectRequest, PutObjectRequest, S3Client, S3}; use serde::{Deserialize, Serialize}; use tokio::io::AsyncReadExt; use tokio::sync::RwLock; @@ -44,7 +42,7 @@ impl Mailbox { /// Sync data with backing store pub async fn sync(&self) -> Result<()> { - self.mbox.write().await.uid_index.sync().await + self.mbox.write().await.sync().await } /// Get a clone of the current UID Index of this mailbox @@ -60,7 +58,13 @@ impl Mailbox { /// Copy an email from an other Mailbox to this mailbox /// (use this when possible, as it allows for a certain number of storage optimizations) - pub async fn copy(&self, _from: &Mailbox, _uid: ImapUid) -> Result<()> { + pub async fn copy_from(&self, _from: &Mailbox, _uuid: UniqueIdent) -> Result<()> { + unimplemented!() + } + + /// Move an email from an other Mailbox to this mailbox + /// (use this when possible, as it allows for a certain number of storage optimizations) + pub async fn move_from(&self, _from: &Mailbox, _uuid: UniqueIdent) -> Result<()> { unimplemented!() } @@ -98,6 +102,11 @@ struct MailboxInternal { } impl MailboxInternal { + async fn sync(&mut self) -> Result<()> { + self.uid_index.sync().await?; + Ok(()) + } + async fn fetch_meta(&self, ids: &[UniqueIdent]) -> Result> { let ids = ids.iter().map(|x| x.to_string()).collect::>(); let ops = ids diff --git a/src/main.rs b/src/main.rs index 5d139b6..6744157 100644 --- a/src/main.rs +++ b/src/main.rs @@ -118,6 +118,12 @@ async fn main() -> Result<()> { std::env::set_var("RUST_LOG", "main=info,mailrage=info,k2v_client=info") } + // Abort on panic (same behavior as in Go) + std::panic::set_hook(Box::new(|panic_info| { + tracing::error!("{}", panic_info.to_string()); + std::process::abort(); + })); + tracing_subscriber::fmt::init(); let args = Args::parse();