2023-08-16 16:01:22 +00:00
use std ::borrow ::Cow ;
2022-06-29 15:58:31 +00:00
use std ::num ::NonZeroU32 ;
2022-06-29 13:39:54 +00:00
use std ::sync ::Arc ;
2023-10-09 10:00:16 +00:00
use std ::iter ::zip ;
2022-06-29 13:39:54 +00:00
2022-06-29 17:24:21 +00:00
use anyhow ::{ anyhow , bail , Error , Result } ;
2022-06-29 14:05:34 +00:00
use boitalettres ::proto ::res ::body ::Data as Body ;
2022-07-04 10:07:48 +00:00
use chrono ::{ Offset , TimeZone , Utc } ;
2023-10-09 10:00:16 +00:00
2022-06-29 17:24:21 +00:00
use futures ::stream ::{ FuturesOrdered , StreamExt } ;
2023-10-09 10:00:16 +00:00
2022-06-29 18:00:38 +00:00
use imap_codec ::types ::address ::Address ;
2022-07-04 10:07:48 +00:00
use imap_codec ::types ::body ::{ BasicFields , Body as FetchBody , BodyStructure , SpecificFields } ;
2022-07-15 16:16:06 +00:00
use imap_codec ::types ::core ::{ AString , Atom , IString , NString } ;
2022-07-04 10:07:48 +00:00
use imap_codec ::types ::datetime ::MyDateTime ;
2022-06-29 18:00:38 +00:00
use imap_codec ::types ::envelope ::Envelope ;
2022-07-13 15:05:07 +00:00
use imap_codec ::types ::fetch_attributes ::{
2022-07-15 15:55:04 +00:00
FetchAttribute , MacroOrFetchAttributes , Section as FetchSection ,
2022-07-13 15:05:07 +00:00
} ;
2022-07-12 15:32:57 +00:00
use imap_codec ::types ::flag ::{ Flag , StoreResponse , StoreType } ;
2022-06-29 15:58:31 +00:00
use imap_codec ::types ::response ::{ Code , Data , MessageAttribute , Status } ;
2022-06-29 17:24:21 +00:00
use imap_codec ::types ::sequence ::{ self , SequenceSet } ;
2023-10-09 10:00:16 +00:00
2023-07-25 08:59:48 +00:00
use eml_codec ::{
2023-09-21 09:27:33 +00:00
header ,
2023-08-16 16:01:22 +00:00
imf ,
part ::{ AnyPart , composite ::Message } ,
2023-07-25 17:08:48 +00:00
mime ::r#type ::Deductible ,
2023-07-25 08:59:48 +00:00
mime ,
} ;
2022-06-29 13:39:54 +00:00
2023-10-09 10:00:16 +00:00
use crate ::cryptoblob ::Key ;
use crate ::mail ::mailbox ::{ Mailbox , MailMeta } ;
2022-07-12 13:59:13 +00:00
use crate ::mail ::uidindex ::{ ImapUid , ImapUidvalidity , UidIndex } ;
2022-07-12 15:32:57 +00:00
use crate ::mail ::unique_ident ::UniqueIdent ;
2022-06-29 13:39:54 +00:00
const DEFAULT_FLAGS : [ Flag ; 5 ] = [
Flag ::Seen ,
Flag ::Answered ,
Flag ::Flagged ,
Flag ::Deleted ,
Flag ::Draft ,
] ;
2023-08-16 16:01:22 +00:00
enum FetchedMail < ' a > {
Partial ( imf ::Imf < ' a > ) ,
Full ( Message < ' a > ) ,
}
impl < ' a > FetchedMail < ' a > {
fn as_full ( & self ) -> Result < & Message < ' a > > {
match self {
FetchedMail ::Full ( x ) = > Ok ( & x ) ,
_ = > bail! ( " The full message must be fetched, not only its headers: it's a logic error " ) ,
}
}
fn imf ( & self ) -> & imf ::Imf < ' a > {
match self {
FetchedMail ::Full ( x ) = > & x . imf ,
FetchedMail ::Partial ( x ) = > & x ,
}
}
}
2023-09-28 09:57:46 +00:00
pub struct AttributesProxy {
2023-10-09 10:00:16 +00:00
attrs : Vec < FetchAttribute >
2023-09-28 09:57:46 +00:00
}
2023-10-09 10:00:16 +00:00
impl AttributesProxy {
2023-09-28 09:57:46 +00:00
fn new ( attrs : & MacroOrFetchAttributes , is_uid_fetch : bool ) -> Self {
// Expand macros
2023-10-09 10:00:16 +00:00
let mut fetch_attrs = match attrs {
2023-09-28 09:57:46 +00:00
MacroOrFetchAttributes ::Macro ( m ) = > m . expand ( ) ,
MacroOrFetchAttributes ::FetchAttributes ( a ) = > a . clone ( ) ,
} ;
// Handle uids
2023-10-10 15:59:34 +00:00
if is_uid_fetch & & ! fetch_attrs . contains ( & FetchAttribute ::Uid ) {
2023-09-28 09:57:46 +00:00
fetch_attrs . push ( FetchAttribute ::Uid ) ;
}
Self { attrs : fetch_attrs }
}
fn need_body ( & self ) -> bool {
self . attrs . iter ( ) . any ( | x | {
matches! (
x ,
FetchAttribute ::Body
| FetchAttribute ::BodyExt { .. }
| FetchAttribute ::Rfc822
| FetchAttribute ::Rfc822Text
| FetchAttribute ::BodyStructure
)
} )
}
}
2023-10-09 10:00:16 +00:00
pub struct MailIdentifiers {
i : NonZeroU32 ,
uid : ImapUid ,
uuid : UniqueIdent ,
}
2023-10-10 15:59:34 +00:00
struct MailIdentifiersList ( Vec < MailIdentifiers > ) ;
impl MailIdentifiersList {
fn uuids ( & self ) -> Vec < UniqueIdent > {
self . 0. iter ( ) . map ( | mi | mi . uuid ) . collect ( )
}
}
2023-10-09 10:00:16 +00:00
pub struct MailView < ' a > {
ids : & ' a MailIdentifiers ,
meta : & ' a MailMeta ,
2023-10-10 15:59:34 +00:00
flags : & ' a Vec < String > ,
2023-10-09 10:00:16 +00:00
content : FetchedMail < ' a > ,
add_seen : bool
}
impl < ' a > MailView < ' a > {
fn uid ( & self ) -> MessageAttribute {
MessageAttribute ::Uid ( self . ids . uid )
}
fn flags ( & self ) -> MessageAttribute {
MessageAttribute ::Flags (
self . flags . iter ( ) . filter_map ( | f | string_to_flag ( f ) ) . collect ( ) ,
)
}
fn rfc_822_size ( & self ) -> MessageAttribute {
MessageAttribute ::Rfc822Size ( self . meta . rfc822_size as u32 )
}
fn rfc_822_header ( & self ) -> MessageAttribute {
MessageAttribute ::Rfc822Header ( NString ( self . meta . headers . to_vec ( ) . try_into ( ) . ok ( ) . map ( IString ::Literal ) ) )
}
fn rfc_822_text ( & self ) -> Result < MessageAttribute > {
Ok ( MessageAttribute ::Rfc822Text ( NString (
self . content . as_full ( ) ? . raw_body . try_into ( ) . ok ( ) . map ( IString ::Literal ) ,
) ) )
}
fn rfc822 ( & self ) -> Result < MessageAttribute > {
Ok ( MessageAttribute ::Rfc822 ( NString (
self . content . as_full ( ) ? . raw_body . clone ( ) . try_into ( ) . ok ( ) . map ( IString ::Literal ) ) ) )
}
fn envelope ( & self ) -> MessageAttribute {
2023-10-10 15:59:34 +00:00
MessageAttribute ::Envelope ( message_envelope ( self . content . imf ( ) ) )
2023-10-09 10:00:16 +00:00
}
fn body ( & self ) -> Result < MessageAttribute > {
Ok ( MessageAttribute ::Body (
build_imap_email_struct ( self . content . as_full ( ) ? . child . as_ref ( ) ) ? ,
) )
}
fn body_structure ( & self ) -> Result < MessageAttribute > {
Ok ( MessageAttribute ::Body (
build_imap_email_struct ( self . content . as_full ( ) ? . child . as_ref ( ) ) ? ,
) )
}
/// maps to BODY[<section>]<<partial>> and BODY.PEEK[<section>]<<partial>>
/// peek does not implicitly set the \Seen flag
/// eg. BODY[HEADER.FIELDS (DATE FROM)]
/// eg. BODY[]<0.2048>
2023-10-10 15:59:34 +00:00
fn body_ext ( & mut self , section : & Option < FetchSection > , partial : & Option < ( u32 , NonZeroU32 ) > , peek : & bool ) -> Result < MessageAttribute > {
2023-10-09 10:00:16 +00:00
// Extract message section
2023-10-10 15:59:34 +00:00
let text = get_message_section ( self . content . as_full ( ) ? , section ) ? ;
2023-10-09 10:00:16 +00:00
2023-10-10 15:59:34 +00:00
let seen_flag = Flag ::Seen . to_string ( ) ;
if ! peek & & ! self . flags . iter ( ) . any ( | x | * x = = seen_flag ) {
// Add \Seen flag
//self.mailbox.add_flags(uuid, &[seen_flag]).await?;
self . add_seen = true ;
}
2023-10-09 10:00:16 +00:00
2023-10-10 15:59:34 +00:00
// Handle <<partial>> which cut the message bytes
let ( text , origin ) = apply_partial ( partial , & text ) ;
let data = NString ( text . to_vec ( ) . try_into ( ) . ok ( ) . map ( IString ::Literal ) ) ;
return Ok ( MessageAttribute ::BodyExt {
section : section . clone ( ) ,
origin ,
data ,
} )
2023-10-09 10:00:16 +00:00
}
fn internal_date ( & self ) -> Result < MessageAttribute > {
let dt = Utc . fix ( ) . timestamp_opt ( i64 ::try_from ( self . meta . internaldate / 1000 ) ? , 0 ) . earliest ( ) . ok_or ( anyhow! ( " Unable to parse internal date " ) ) ? ;
Ok ( MessageAttribute ::InternalDate ( MyDateTime ( dt ) ) )
}
2023-10-10 15:59:34 +00:00
fn filter ( & mut self , ap : & AttributesProxy ) -> Result < Body > {
let res_attrs = ap . attrs . iter ( ) . map ( | attr | match attr {
FetchAttribute ::Uid = > Ok ( self . uid ( ) ) ,
FetchAttribute ::Flags = > Ok ( self . flags ( ) ) ,
FetchAttribute ::Rfc822Size = > Ok ( self . rfc_822_size ( ) ) ,
FetchAttribute ::Rfc822Header = > Ok ( self . rfc_822_header ( ) ) ,
FetchAttribute ::Rfc822Text = > self . rfc_822_text ( ) ,
FetchAttribute ::Rfc822 = > self . rfc822 ( ) ,
FetchAttribute ::Envelope = > Ok ( self . envelope ( ) ) ,
FetchAttribute ::Body = > self . body ( ) ,
FetchAttribute ::BodyStructure = > self . body_structure ( ) ,
FetchAttribute ::BodyExt { section , partial , peek } = > self . body_ext ( section , partial , peek ) ,
FetchAttribute ::InternalDate = > self . internal_date ( ) ,
} ) . collect ::< Result < Vec < _ > , _ > > ( ) ? ;
Ok ( Body ::Data ( Data ::Fetch {
2023-10-09 10:00:16 +00:00
seq_or_uid : self . ids . i ,
2023-10-10 15:59:34 +00:00
attributes : res_attrs ,
} ) )
2023-10-09 10:00:16 +00:00
}
}
2023-10-10 15:59:34 +00:00
fn apply_partial < ' a > ( partial : & '_ Option < ( u32 , NonZeroU32 ) > , text : & ' a [ u8 ] ) -> ( & ' a [ u8 ] , Option < u32 > ) {
2023-10-09 10:00:16 +00:00
match partial {
Some ( ( begin , len ) ) = > {
if * begin as usize > text . len ( ) {
( & [ ] [ .. ] , Some ( * begin ) )
2023-10-10 15:59:34 +00:00
} else if ( begin + len . get ( ) ) as usize > = text . len ( ) {
2023-10-09 10:00:16 +00:00
( & text [ * begin as usize .. ] , Some ( * begin ) )
} else {
(
2023-10-10 15:59:34 +00:00
& text [ * begin as usize .. ( begin + len . get ( ) ) as usize ] ,
2023-10-09 10:00:16 +00:00
Some ( * begin ) ,
)
}
}
None = > ( & text [ .. ] , None ) ,
2023-10-10 15:59:34 +00:00
}
2023-10-09 10:00:16 +00:00
}
pub struct BodyIdentifier < ' a > {
msg_uuid : & ' a UniqueIdent ,
msg_key : & ' a Key ,
}
2023-09-28 09:57:46 +00:00
#[ derive(Default) ]
2023-10-09 10:00:16 +00:00
pub struct MailSelectionBuilder < ' a > {
//attrs: AttributeProxy,
mail_count : usize ,
need_body : bool ,
mi : & ' a [ MailIdentifiers ] ,
meta : & ' a [ MailMeta ] ,
2023-10-10 15:59:34 +00:00
flags : & ' a [ & ' a Vec < String > ] ,
bodies : & ' a [ Vec < u8 > ] ,
2023-09-28 09:57:46 +00:00
}
2023-10-09 10:00:16 +00:00
impl < ' a > MailSelectionBuilder < ' a > {
fn new ( need_body : bool , mail_count : usize ) -> Self {
Self {
mail_count ,
need_body ,
.. MailSelectionBuilder ::default ( )
2023-09-28 09:57:46 +00:00
}
}
2023-10-10 15:59:34 +00:00
fn with_mail_identifiers ( & mut self , mi : & ' a [ MailIdentifiers ] ) -> & mut Self {
2023-09-28 09:57:46 +00:00
self . mi = mi ;
self
}
2023-10-10 15:59:34 +00:00
fn with_metadata ( & mut self , meta : & ' a [ MailMeta ] ) -> & mut Self {
2023-09-28 09:57:46 +00:00
self . meta = meta ;
self
}
2023-10-10 15:59:34 +00:00
fn with_flags ( & mut self , flags : & ' a [ & ' a Vec < String > ] ) -> & mut Self {
2023-09-28 09:57:46 +00:00
self . flags = flags ;
self
}
2023-10-09 10:00:16 +00:00
fn bodies_to_collect ( & self ) -> Vec < BodyIdentifier > {
2023-10-10 15:59:34 +00:00
if ! self . need_body {
2023-10-09 10:00:16 +00:00
return vec! [ ]
}
2023-10-10 15:59:34 +00:00
zip ( self . mi , self . meta ) . map ( | ( mi , meta ) | BodyIdentifier { msg_uuid : & mi . uuid , msg_key : & meta . message_key } ) . collect ::< Vec < _ > > ( )
2023-10-09 10:00:16 +00:00
}
2023-09-28 09:57:46 +00:00
2023-10-10 15:59:34 +00:00
fn with_bodies ( & mut self , rbodies : & ' a [ Vec < u8 > ] ) -> & mut Self {
self . bodies = rbodies ;
2023-09-28 09:57:46 +00:00
self
}
2023-10-10 15:59:34 +00:00
fn build ( & self ) -> Result < Vec < MailView < ' a > > > {
let mut bodies = vec! [ ] ;
if ! self . need_body {
2023-10-09 10:00:16 +00:00
for m in self . meta . iter ( ) {
2023-10-10 15:59:34 +00:00
let ( _ , hdrs ) = eml_codec ::parse_imf ( & m . headers ) . or ( Err ( anyhow! ( " Invalid mail headers " ) ) ) ? ;
bodies . push ( FetchedMail ::Partial ( hdrs ) ) ;
}
} else {
for rb in self . bodies . iter ( ) {
let ( _ , p ) = eml_codec ::parse_message ( & rb ) . or ( Err ( anyhow! ( " Invalid mail body " ) ) ) ? ;
bodies . push ( FetchedMail ::Full ( p ) ) ;
2023-10-09 10:00:16 +00:00
}
}
2023-09-28 09:57:46 +00:00
2023-10-10 15:59:34 +00:00
if self . mi . len ( ) ! = self . mail_count & & self . meta . len ( ) ! = self . mail_count | | self . flags . len ( ) ! = self . mail_count | | bodies . len ( ) ! = self . mail_count {
2023-10-09 10:00:16 +00:00
return Err ( anyhow! ( " Can't build a mail view selection as parts were not correctly registered into the builder. " ) )
}
2023-10-10 15:59:34 +00:00
Ok ( zip ( self . mi , zip ( self . meta , zip ( self . flags , bodies ) ) )
. map ( | ( ids , ( meta , ( flags , content ) ) ) | MailView { ids , meta , flags , content , add_seen : false } )
. collect ( ) )
2023-09-28 09:57:46 +00:00
}
}
2022-06-29 13:39:54 +00:00
/// A MailboxView is responsible for giving the client the information
/// it needs about a mailbox, such as an initial summary of the mailbox's
/// content and continuous updates indicating when the content
/// of the mailbox has been changed.
/// To do this, it keeps a variable `known_state` that corresponds to
/// what the client knows, and produces IMAP messages to be sent to the
/// client that go along updates to `known_state`.
pub struct MailboxView {
2022-07-12 14:35:11 +00:00
pub ( crate ) mailbox : Arc < Mailbox > ,
2022-06-29 13:39:54 +00:00
known_state : UidIndex ,
}
impl MailboxView {
/// Creates a new IMAP view into a mailbox.
/// Generates the necessary IMAP messages so that the client
/// has a satisfactory summary of the current mailbox's state.
2022-06-29 13:52:09 +00:00
/// These are the messages that are sent in response to a SELECT command.
2022-06-29 13:39:54 +00:00
pub async fn new ( mailbox : Arc < Mailbox > ) -> Result < ( Self , Vec < Body > ) > {
let state = mailbox . current_uid_index ( ) . await ;
let new_view = Self {
mailbox ,
known_state : state ,
} ;
let mut data = Vec ::< Body > ::new ( ) ;
2022-07-12 13:59:13 +00:00
data . push ( new_view . exists_status ( ) ? ) ;
data . push ( new_view . recent_status ( ) ? ) ;
data . extend ( new_view . flags_status ( ) ? . into_iter ( ) ) ;
data . push ( new_view . uidvalidity_status ( ) ? ) ;
data . push ( new_view . uidnext_status ( ) ? ) ;
2022-06-29 13:39:54 +00:00
Ok ( ( new_view , data ) )
}
2022-06-30 09:28:03 +00:00
/// Produces a set of IMAP responses describing the change between
/// what the client knows and what is actually in the mailbox.
2022-07-13 12:21:14 +00:00
/// This does NOT trigger a sync, it bases itself on what is currently
/// loaded in RAM by Bayou.
2022-06-30 09:28:03 +00:00
pub async fn update ( & mut self ) -> Result < Vec < Body > > {
2022-06-29 15:58:31 +00:00
let new_view = MailboxView {
mailbox : self . mailbox . clone ( ) ,
known_state : self . mailbox . current_uid_index ( ) . await ,
} ;
let mut data = Vec ::< Body > ::new ( ) ;
2022-07-13 09:39:13 +00:00
// Calculate diff between two mailbox states
// See example in IMAP RFC in section on NOOP command:
// we want to produce something like this:
// C: a047 NOOP
// S: * 22 EXPUNGE
// S: * 23 EXISTS
// S: * 14 FETCH (UID 1305 FLAGS (\Seen \Deleted))
// S: a047 OK Noop completed
// In other words:
// - notify client of expunged mails
// - if new mails arrived, notify client of number of existing mails
// - if flags changed for existing mails, tell client
// (for this last step: if uidvalidity changed, do nothing,
// just notify of new uidvalidity and they will resync)
// - notify client of expunged mails
let mut n_expunge = 0 ;
for ( i , ( _uid , uuid ) ) in self . known_state . idx_by_uid . iter ( ) . enumerate ( ) {
if ! new_view . known_state . table . contains_key ( uuid ) {
data . push ( Body ::Data ( Data ::Expunge (
NonZeroU32 ::try_from ( ( i + 1 - n_expunge ) as u32 ) . unwrap ( ) ,
) ) ) ;
n_expunge + = 1 ;
}
}
// - if new mails arrived, notify client of number of existing mails
if new_view . known_state . table . len ( ) ! = self . known_state . table . len ( ) - n_expunge
| | new_view . known_state . uidvalidity ! = self . known_state . uidvalidity
{
data . push ( new_view . exists_status ( ) ? ) ;
}
2022-06-29 15:58:31 +00:00
if new_view . known_state . uidvalidity ! = self . known_state . uidvalidity {
// TODO: do we want to push less/more info than this?
2022-07-12 13:59:13 +00:00
data . push ( new_view . uidvalidity_status ( ) ? ) ;
data . push ( new_view . uidnext_status ( ) ? ) ;
2022-06-29 15:58:31 +00:00
} else {
// - if flags changed for existing mails, tell client
2022-06-29 17:27:32 +00:00
for ( i , ( _uid , uuid ) ) in new_view . known_state . idx_by_uid . iter ( ) . enumerate ( ) {
2022-06-29 15:58:31 +00:00
let old_mail = self . known_state . table . get ( uuid ) ;
let new_mail = new_view . known_state . table . get ( uuid ) ;
if old_mail . is_some ( ) & & old_mail ! = new_mail {
if let Some ( ( uid , flags ) ) = new_mail {
data . push ( Body ::Data ( Data ::Fetch {
seq_or_uid : NonZeroU32 ::try_from ( ( i + 1 ) as u32 ) . unwrap ( ) ,
attributes : vec ! [
2023-05-15 16:23:23 +00:00
MessageAttribute ::Uid ( * uid ) ,
2022-06-29 15:58:31 +00:00
MessageAttribute ::Flags (
flags . iter ( ) . filter_map ( | f | string_to_flag ( f ) ) . collect ( ) ,
) ,
] ,
} ) ) ;
}
}
}
}
* self = new_view ;
Ok ( data )
}
2022-07-12 15:32:57 +00:00
pub async fn store (
& mut self ,
sequence_set : & SequenceSet ,
kind : & StoreType ,
_response : & StoreResponse ,
flags : & [ Flag ] ,
2022-07-13 13:00:13 +00:00
is_uid_store : & bool ,
2022-07-12 15:32:57 +00:00
) -> Result < Vec < Body > > {
2022-07-13 12:21:14 +00:00
self . mailbox . opportunistic_sync ( ) . await ? ;
2022-07-12 15:32:57 +00:00
let flags = flags . iter ( ) . map ( | x | x . to_string ( ) ) . collect ::< Vec < _ > > ( ) ;
2022-07-13 13:00:13 +00:00
let mails = self . get_mail_ids ( sequence_set , * is_uid_store ) ? ;
2023-10-10 15:59:34 +00:00
for mi in mails . iter ( ) {
2022-07-12 15:32:57 +00:00
match kind {
StoreType ::Add = > {
2023-10-10 15:59:34 +00:00
self . mailbox . add_flags ( mi . uuid , & flags [ .. ] ) . await ? ;
2022-07-12 15:32:57 +00:00
}
StoreType ::Remove = > {
2023-10-10 15:59:34 +00:00
self . mailbox . del_flags ( mi . uuid , & flags [ .. ] ) . await ? ;
2022-07-12 15:32:57 +00:00
}
StoreType ::Replace = > {
2023-10-10 15:59:34 +00:00
self . mailbox . set_flags ( mi . uuid , & flags [ .. ] ) . await ? ;
2022-07-12 15:32:57 +00:00
}
}
}
2022-07-21 10:44:58 +00:00
// @TODO: handle _response
2022-07-12 15:32:57 +00:00
self . update ( ) . await
}
2022-07-13 09:00:35 +00:00
pub async fn expunge ( & mut self ) -> Result < Vec < Body > > {
2022-07-13 12:21:14 +00:00
self . mailbox . opportunistic_sync ( ) . await ? ;
2022-07-13 09:19:08 +00:00
let deleted_flag = Flag ::Deleted . to_string ( ) ;
2022-07-13 12:21:14 +00:00
let state = self . mailbox . current_uid_index ( ) . await ;
let msgs = state
2022-07-13 09:19:08 +00:00
. table
. iter ( )
. filter ( | ( _uuid , ( _uid , flags ) ) | flags . iter ( ) . any ( | x | * x = = deleted_flag ) )
. map ( | ( uuid , _ ) | * uuid ) ;
for msg in msgs {
self . mailbox . delete ( msg ) . await ? ;
}
self . update ( ) . await
2022-07-13 09:00:35 +00:00
}
2022-07-21 10:44:58 +00:00
pub async fn copy (
& self ,
sequence_set : & SequenceSet ,
to : Arc < Mailbox > ,
is_uid_copy : & bool ,
) -> Result < ( ImapUidvalidity , Vec < ( ImapUid , ImapUid ) > ) > {
let mails = self . get_mail_ids ( sequence_set , * is_uid_copy ) ? ;
let mut new_uuids = vec! [ ] ;
2023-10-10 15:59:34 +00:00
for mi in mails . iter ( ) {
new_uuids . push ( to . copy_from ( & self . mailbox , mi . uuid ) . await ? ) ;
2022-07-21 10:44:58 +00:00
}
let mut ret = vec! [ ] ;
let to_state = to . current_uid_index ( ) . await ;
2023-10-10 15:59:34 +00:00
for ( mi , new_uuid ) in mails . iter ( ) . zip ( new_uuids . iter ( ) ) {
2022-07-21 10:44:58 +00:00
let dest_uid = to_state
. table
. get ( new_uuid )
. ok_or ( anyhow! ( " copied mail not in destination mailbox " ) ) ?
. 0 ;
2023-10-10 15:59:34 +00:00
ret . push ( ( mi . uid , dest_uid ) ) ;
2022-07-21 10:44:58 +00:00
}
Ok ( ( to_state . uidvalidity , ret ) )
}
2022-06-29 17:24:21 +00:00
/// Looks up state changes in the mailbox and produces a set of IMAP
/// responses describing the new state.
pub async fn fetch (
& self ,
sequence_set : & SequenceSet ,
attributes : & MacroOrFetchAttributes ,
2022-07-13 13:00:13 +00:00
is_uid_fetch : & bool ,
2022-06-29 17:24:21 +00:00
) -> Result < Vec < Body > > {
2023-09-28 09:57:46 +00:00
2023-10-09 10:00:16 +00:00
let ap = AttributesProxy ::new ( attributes , * is_uid_fetch ) ;
2022-06-29 17:24:21 +00:00
2023-10-10 15:59:34 +00:00
// Prepare data
let mids = MailIdentifiersList ( self . get_mail_ids ( sequence_set , * is_uid_fetch ) ? ) ;
let mail_count = mids . 0. len ( ) ;
let uuids = mids . uuids ( ) ;
let meta = self . mailbox . fetch_meta ( & uuids ) . await ? ;
let flags = uuids . iter ( ) . map ( | uuid | self . known_state . table . get ( uuid ) . map ( | ( _uuid , f ) | f ) . ok_or ( anyhow! ( " missing email from the flag table " ) ) ) . collect ::< Result < Vec < _ > , _ > > ( ) ? ;
// Start filling data to build the view
let mut selection = MailSelectionBuilder ::new ( ap . need_body ( ) , mail_count ) ;
selection
. with_mail_identifiers ( & mids . 0 )
. with_metadata ( & meta )
. with_flags ( & flags ) ;
2023-09-28 09:57:46 +00:00
2023-10-09 10:00:16 +00:00
// Asynchronously fetch full bodies (if needed)
2023-10-10 15:59:34 +00:00
let btc = selection . bodies_to_collect ( ) ;
let future_bodies = btc . iter ( ) . map ( | bi | async move {
let body = self . mailbox . fetch_full ( * bi . msg_uuid , bi . msg_key ) . await ? ;
Ok ::< _ , anyhow ::Error > ( body )
2023-10-09 10:00:16 +00:00
} ) . collect ::< FuturesOrdered < _ > > ( ) ;
2023-10-10 15:59:34 +00:00
let bodies = future_bodies . collect ::< Vec < _ > > ( ) . await . into_iter ( ) . collect ::< Result < Vec < _ > , _ > > ( ) ? ;
2023-10-09 10:00:16 +00:00
2023-10-10 15:59:34 +00:00
// Add bodies
selection . with_bodies ( bodies . as_slice ( ) ) ;
2023-10-09 10:00:16 +00:00
// Build mail selection views
2023-10-10 15:59:34 +00:00
let mut views = selection . build ( ) ? ;
2023-10-09 10:00:16 +00:00
// Filter views to build the result
2023-10-10 15:59:34 +00:00
let ret = views . iter_mut ( ) . filter_map ( | mv | mv . filter ( & ap ) . ok ( ) ) . collect ::< Vec < _ > > ( ) ;
2022-06-29 17:24:21 +00:00
2023-10-09 10:00:16 +00:00
// Register seen flags
let future_flags = views . iter ( ) . filter ( | mv | mv . add_seen ) . map ( | mv | async move {
let seen_flag = Flag ::Seen . to_string ( ) ;
2023-10-10 15:59:34 +00:00
self . mailbox . add_flags ( mv . ids . uuid , & [ seen_flag ] ) . await ? ;
2023-10-09 10:00:16 +00:00
Ok ::< _ , anyhow ::Error > ( ( ) )
} ) . collect ::< FuturesOrdered < _ > > ( ) ;
2023-10-10 15:59:34 +00:00
future_flags . collect ::< Vec < _ > > ( ) . await . into_iter ( ) . collect ::< Result < _ , _ > > ( ) ? ;
2023-10-09 10:00:16 +00:00
2022-06-29 17:24:21 +00:00
Ok ( ret )
}
2022-06-29 13:39:54 +00:00
// ----
2023-09-28 09:57:46 +00:00
// Gets the IMAP ID, the IMAP UIDs and, the Aerogramme UUIDs of mails identified by a SequenceSet of
// sequence numbers (~ IMAP selector)
2022-07-12 15:32:57 +00:00
fn get_mail_ids (
& self ,
sequence_set : & SequenceSet ,
2022-07-13 13:00:13 +00:00
by_uid : bool ,
2023-10-09 10:00:16 +00:00
) -> Result < Vec < MailIdentifiers > > {
2022-07-12 15:32:57 +00:00
let mail_vec = self
. known_state
. idx_by_uid
. iter ( )
. map ( | ( uid , uuid ) | ( * uid , * uuid ) )
. collect ::< Vec < _ > > ( ) ;
let mut mails = vec! [ ] ;
2022-07-13 13:00:13 +00:00
if by_uid {
if mail_vec . is_empty ( ) {
return Ok ( vec! [ ] ) ;
}
let iter_strat = sequence ::Strategy ::Naive {
largest : mail_vec . last ( ) . unwrap ( ) . 0 ,
} ;
let mut i = 0 ;
for uid in sequence_set . iter ( iter_strat ) {
while mail_vec . get ( i ) . map ( | mail | mail . 0 < uid ) . unwrap_or ( false ) {
i + = 1 ;
}
if let Some ( mail ) = mail_vec . get ( i ) {
if mail . 0 = = uid {
2023-10-10 15:59:34 +00:00
mails . push ( MailIdentifiers { i : NonZeroU32 ::try_from ( i as u32 + 1 ) . unwrap ( ) , uid : mail . 0 , uuid : mail . 1 } ) ;
2022-07-13 13:00:13 +00:00
}
} else {
break ;
}
}
} else {
if mail_vec . is_empty ( ) {
bail! ( " No such message (mailbox is empty) " ) ;
}
let iter_strat = sequence ::Strategy ::Naive {
largest : NonZeroU32 ::try_from ( ( mail_vec . len ( ) ) as u32 ) . unwrap ( ) ,
} ;
for i in sequence_set . iter ( iter_strat ) {
if let Some ( mail ) = mail_vec . get ( i . get ( ) as usize - 1 ) {
2023-10-10 15:59:34 +00:00
mails . push ( MailIdentifiers { i , uid : mail . 0 , uuid : mail . 1 } ) ;
2022-07-13 13:00:13 +00:00
} else {
bail! ( " No such mail: {} " , i ) ;
}
2022-07-12 15:32:57 +00:00
}
}
Ok ( mails )
}
// ----
2022-06-29 13:39:54 +00:00
/// Produce an OK [UIDVALIDITY _] message corresponding to `known_state`
2022-07-12 13:59:13 +00:00
fn uidvalidity_status ( & self ) -> Result < Body > {
2022-06-29 13:39:54 +00:00
let uid_validity = Status ::ok (
None ,
2022-07-12 13:59:13 +00:00
Some ( Code ::UidValidity ( self . uidvalidity ( ) ) ) ,
2022-06-29 13:39:54 +00:00
" UIDs valid " ,
)
. map_err ( Error ::msg ) ? ;
Ok ( Body ::Status ( uid_validity ) )
}
2022-07-12 13:59:13 +00:00
pub ( crate ) fn uidvalidity ( & self ) -> ImapUidvalidity {
self . known_state . uidvalidity
}
2022-06-29 13:39:54 +00:00
/// Produce an OK [UIDNEXT _] message corresponding to `known_state`
2022-07-12 13:59:13 +00:00
fn uidnext_status ( & self ) -> Result < Body > {
2022-06-29 13:39:54 +00:00
let next_uid = Status ::ok (
None ,
2022-07-12 13:59:13 +00:00
Some ( Code ::UidNext ( self . uidnext ( ) ) ) ,
2022-06-29 13:39:54 +00:00
" Predict next UID " ,
)
. map_err ( Error ::msg ) ? ;
Ok ( Body ::Status ( next_uid ) )
}
2022-07-12 13:59:13 +00:00
pub ( crate ) fn uidnext ( & self ) -> ImapUid {
self . known_state . uidnext
}
2022-06-29 13:39:54 +00:00
/// Produce an EXISTS message corresponding to the number of mails
/// in `known_state`
2022-07-12 13:59:13 +00:00
fn exists_status ( & self ) -> Result < Body > {
Ok ( Body ::Data ( Data ::Exists ( self . exists ( ) ? ) ) )
}
pub ( crate ) fn exists ( & self ) -> Result < u32 > {
Ok ( u32 ::try_from ( self . known_state . idx_by_uid . len ( ) ) ? )
2022-06-29 13:39:54 +00:00
}
/// Produce a RECENT message corresponding to the number of
/// recent mails in `known_state`
2022-07-12 13:59:13 +00:00
fn recent_status ( & self ) -> Result < Body > {
Ok ( Body ::Data ( Data ::Recent ( self . recent ( ) ? ) ) )
}
pub ( crate ) fn recent ( & self ) -> Result < u32 > {
2022-06-29 13:39:54 +00:00
let recent = self
. known_state
. idx_by_flag
. get ( & " \\ Recent " . to_string ( ) )
. map ( | os | os . len ( ) )
. unwrap_or ( 0 ) ;
2022-07-12 13:59:13 +00:00
Ok ( u32 ::try_from ( recent ) ? )
2022-06-29 13:39:54 +00:00
}
/// Produce a FLAGS and a PERMANENTFLAGS message that indicates
/// the flags that are in `known_state` + default flags
2022-07-12 13:59:13 +00:00
fn flags_status ( & self ) -> Result < Vec < Body > > {
2022-06-29 13:39:54 +00:00
let mut flags : Vec < Flag > = self
. known_state
. idx_by_flag
. flags ( )
2023-05-15 16:23:23 +00:00
. filter_map ( | f | string_to_flag ( f ) )
2022-06-29 13:39:54 +00:00
. collect ( ) ;
2022-06-29 13:52:09 +00:00
for f in DEFAULT_FLAGS . iter ( ) {
if ! flags . contains ( f ) {
flags . push ( f . clone ( ) ) ;
}
}
2022-06-29 13:39:54 +00:00
let mut ret = vec! [ Body ::Data ( Data ::Flags ( flags . clone ( ) ) ) ] ;
flags . push ( Flag ::Permanent ) ;
let permanent_flags =
Status ::ok ( None , Some ( Code ::PermanentFlags ( flags ) ) , " Flags permitted " )
. map_err ( Error ::msg ) ? ;
ret . push ( Body ::Status ( permanent_flags ) ) ;
Ok ( ret )
}
2022-07-13 13:39:52 +00:00
pub ( crate ) fn unseen_count ( & self ) -> usize {
let total = self . known_state . table . len ( ) ;
let seen = self
. known_state
. idx_by_flag
. get ( & Flag ::Seen . to_string ( ) )
. map ( | x | x . len ( ) )
. unwrap_or ( 0 ) ;
total - seen
}
2022-06-29 13:39:54 +00:00
}
2022-06-29 15:58:31 +00:00
fn string_to_flag ( f : & str ) -> Option < Flag > {
match f . chars ( ) . next ( ) {
2022-07-12 15:32:57 +00:00
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 ) ,
2023-05-15 16:23:23 +00:00
_ = > match Atom ::try_from ( f . strip_prefix ( '\\' ) . unwrap ( ) . to_string ( ) ) {
2022-07-12 15:32:57 +00:00
Err ( _ ) = > {
tracing ::error! ( flag = % f , " Unable to encode flag as IMAP atom " ) ;
None
}
Ok ( a ) = > Some ( Flag ::Extension ( a ) ) ,
} ,
} ,
2023-05-15 16:23:23 +00:00
Some ( _ ) = > match Atom ::try_from ( f . to_string ( ) ) {
2022-06-29 15:58:31 +00:00
Err ( _ ) = > {
tracing ::error! ( flag = % f , " Unable to encode flag as IMAP atom " ) ;
None
}
Ok ( a ) = > Some ( Flag ::Keyword ( a ) ) ,
} ,
None = > None ,
}
}
2022-06-29 18:00:38 +00:00
2022-07-06 16:42:37 +00:00
/// Envelope rules are defined in RFC 3501, section 7.4.2
/// https://datatracker.ietf.org/doc/html/rfc3501#section-7.4.2
///
/// Some important notes:
///
/// If the Sender or Reply-To lines are absent in the [RFC-2822]
/// header, or are present but empty, the server sets the
/// corresponding member of the envelope to be the same value as
/// the from member (the client is not expected to know to do
/// this). Note: [RFC-2822] requires that all messages have a valid
/// From header. Therefore, the from, sender, and reply-to
/// members in the envelope can not be NIL.
///
/// If the Date, Subject, In-Reply-To, and Message-ID header lines
/// are absent in the [RFC-2822] header, the corresponding member
/// of the envelope is NIL; if these header lines are present but
/// empty the corresponding member of the envelope is the empty
/// string.
//@FIXME return an error if the envelope is invalid instead of panicking
//@FIXME some fields must be defaulted if there are not set.
2023-07-25 08:59:48 +00:00
fn message_envelope ( msg : & imf ::Imf ) -> Envelope {
2023-07-25 17:08:48 +00:00
let from = msg . from . iter ( ) . map ( convert_mbx ) . collect ::< Vec < _ > > ( ) ;
2022-06-29 18:00:38 +00:00
Envelope {
date : NString (
2023-07-25 08:59:48 +00:00
msg . date . as_ref ( )
2023-02-07 10:27:17 +00:00
. map ( | d | IString ::try_from ( d . to_rfc3339 ( ) ) . unwrap ( ) ) ,
2022-06-29 18:00:38 +00:00
) ,
subject : NString (
2023-07-25 08:59:48 +00:00
msg . subject . as_ref ( )
2022-06-29 18:00:38 +00:00
. map ( | d | IString ::try_from ( d . to_string ( ) ) . unwrap ( ) ) ,
) ,
2023-07-25 17:08:48 +00:00
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 )
} ,
2023-10-10 15:59:34 +00:00
from ,
2023-07-25 08:59:48 +00:00
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 ( ) ) ) ,
2022-06-29 18:00:38 +00:00
message_id : NString (
2023-07-25 08:59:48 +00:00
msg . msg_id . as_ref ( ) . map ( | d | IString ::try_from ( d . to_string ( ) ) . unwrap ( ) ) ,
2022-06-29 18:00:38 +00:00
) ,
}
}
2023-07-25 08:59:48 +00:00
fn convert_addresses ( addrlist : & Vec < imf ::address ::AddressRef > ) -> Vec < Address > {
let mut acc = vec! [ ] ;
for item in addrlist {
match item {
imf ::address ::AddressRef ::Single ( a ) = > acc . push ( convert_mbx ( a ) ) ,
imf ::address ::AddressRef ::Many ( l ) = > acc . extend ( l . participants . iter ( ) . map ( convert_mbx ) )
2022-06-29 18:00:38 +00:00
}
}
2023-07-25 08:59:48 +00:00
return acc
2022-06-29 18:00:38 +00:00
}
2023-07-25 08:59:48 +00:00
fn convert_mbx ( addr : & imf ::mailbox ::MailboxRef ) -> Address {
2022-06-29 18:00:38 +00:00
Address ::new (
2023-07-25 08:59:48 +00:00
NString ( addr . name . as_ref ( ) . map ( | x | IString ::try_from ( x . to_string ( ) ) . unwrap ( ) ) ) ,
2022-07-06 16:42:37 +00:00
// SMTP at-domain-list (source route) seems obsolete since at least 1991
// https://www.mhonarc.org/archive/html/ietf-822/1991-06/msg00060.html
2022-06-29 18:00:38 +00:00
NString ( None ) ,
2023-07-25 08:59:48 +00:00
NString ( Some ( IString ::try_from ( addr . addrspec . local_part . to_string ( ) ) . unwrap ( ) ) ) ,
NString ( Some ( IString ::try_from ( addr . addrspec . domain . to_string ( ) ) . unwrap ( ) ) ) ,
2022-06-29 18:00:38 +00:00
)
}
2022-07-04 10:07:48 +00:00
2022-07-04 16:14:19 +00:00
/*
- - CAPTURE - -
b fetch 29878 :29879 ( BODY )
* 29878 FETCH ( BODY ( ( " text " " plain " ( " charset " " utf-8 " ) NIL NIL " quoted-printable " 3264 82 ) ( " text " " html " ( " charset " " utf-8 " ) NIL NIL " quoted-printable " 31834 643 ) " alternative " ) )
* 29879 FETCH ( BODY ( " text " " html " ( " charset " " us-ascii " ) NIL NIL " 7bit " 4107 131 ) )
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | number of lines
| | | | | size
| | | | content transfer encoding
| | | description
| | id
| parameter list
b OK Fetch completed ( 0.001 + 0.000 secs ) .
* /
2023-07-25 08:59:48 +00:00
fn build_imap_email_struct < ' a > ( part : & AnyPart < ' a > ) -> Result < BodyStructure > {
match part {
AnyPart ::Mult ( x ) = > {
2023-07-25 17:08:48 +00:00
let itype = & x . mime . interpreted_type ;
let subtype = IString ::try_from ( itype . subtype . to_string ( ) ) . unwrap_or ( unchecked_istring ( " alternative " ) ) ;
2022-07-20 13:14:34 +00:00
Ok ( BodyStructure ::Multi {
2023-07-25 08:59:48 +00:00
bodies : x . children
2022-07-20 13:14:34 +00:00
. iter ( )
2023-07-25 08:59:48 +00:00
. filter_map ( | inner | build_imap_email_struct ( & inner ) . ok ( ) )
. collect ( ) ,
2022-07-20 13:14:34 +00:00
subtype ,
extension_data : None ,
/* Some(MultipartExtensionData {
parameter_list : vec ! [ ] ,
disposition : None ,
language : None ,
location : None ,
extension : vec ! [ ] ,
} ) * /
} )
}
2023-07-25 08:59:48 +00:00
AnyPart ::Txt ( x ) = > {
2023-07-25 17:08:48 +00:00
let mut basic = basic_fields ( & x . mime . fields , x . body . len ( ) ) ? ;
2023-07-25 08:59:48 +00:00
2023-07-25 17:08:48 +00:00
// Get the interpreted content type, set it
let itype = match & x . mime . interpreted_type {
Deductible ::Inferred ( v ) | Deductible ::Explicit ( v ) = > v
} ;
let subtype = IString ::try_from ( itype . subtype . to_string ( ) ) . unwrap_or ( unchecked_istring ( " plain " ) ) ;
// Add charset to the list of parameters if we know it has been inferred as it will be
// missing from the parsed content.
if let Deductible ::Inferred ( charset ) = & itype . charset {
basic . parameter_list . push ( (
unchecked_istring ( " charset " ) ,
IString ::try_from ( charset . to_string ( ) ) . unwrap_or ( unchecked_istring ( " us-ascii " ) )
) ) ;
}
2022-07-20 13:14:34 +00:00
Ok ( BodyStructure ::Single {
body : FetchBody {
basic ,
specific : SpecificFields ::Text {
subtype ,
2023-07-25 17:08:48 +00:00
number_of_lines : nol ( x . body ) ,
2022-07-20 13:14:34 +00:00
} ,
} ,
extension : None ,
} )
}
2023-07-25 08:59:48 +00:00
AnyPart ::Bin ( x ) = > {
2023-07-25 17:08:48 +00:00
let basic = basic_fields ( & x . mime . fields , x . body . len ( ) ) ? ;
let default = mime ::r#type ::NaiveType { main : & b " application " [ .. ] , sub : & b " octet-stream " [ .. ] , params : vec ! [ ] } ;
let ct = x . mime . fields . ctype . as_ref ( ) . unwrap_or ( & default ) ;
2023-07-25 08:59:48 +00:00
let type_ = IString ::try_from ( String ::from_utf8_lossy ( ct . main ) . to_string ( ) )
. or ( Err ( anyhow! ( " Unable to build IString from given Content-Type type given " ) ) ) ? ;
let subtype = IString ::try_from ( String ::from_utf8_lossy ( ct . sub ) . to_string ( ) )
. or ( Err ( anyhow! ( " Unable to build IString from given Content-Type subtype given " ) ) ) ? ;
2022-07-20 13:14:34 +00:00
Ok ( BodyStructure ::Single {
body : FetchBody {
basic ,
specific : SpecificFields ::Basic { type_ , subtype } ,
} ,
extension : None ,
} )
}
2023-07-25 08:59:48 +00:00
AnyPart ::Msg ( x ) = > {
2023-07-25 17:08:48 +00:00
let basic = basic_fields ( & x . mime . fields , x . raw_part . len ( ) ) ? ;
2023-02-07 10:27:17 +00:00
Ok ( BodyStructure ::Single {
body : FetchBody {
basic ,
specific : SpecificFields ::Message {
2023-07-25 08:59:48 +00:00
envelope : message_envelope ( & x . imf ) ,
body_structure : Box ::new ( build_imap_email_struct ( x . child . as_ref ( ) ) ? ) ,
2023-07-25 17:08:48 +00:00
number_of_lines : nol ( x . raw_part ) ,
2023-02-07 10:27:17 +00:00
} ,
} ,
extension : None ,
} )
2022-07-04 10:07:48 +00:00
}
2022-07-20 13:14:34 +00:00
}
}
2022-07-04 10:07:48 +00:00
2023-07-25 17:08:48 +00:00
fn nol ( input : & [ u8 ] ) -> u32 {
input . iter ( )
. filter ( | x | * * x = = b '\n' )
. count ( )
. try_into ( )
. unwrap_or ( 0 )
}
2022-07-04 10:07:48 +00:00
/// s is set to static to ensure that only compile time values
2022-07-05 15:08:12 +00:00
/// checked by developpers are passed.
2022-07-04 10:07:48 +00:00
fn unchecked_istring ( s : & 'static str ) -> IString {
IString ::try_from ( s ) . expect ( " this value is expected to be a valid imap-codec::IString " )
}
2023-07-25 17:08:48 +00:00
fn basic_fields ( m : & mime ::NaiveMIME , sz : usize ) -> Result < BasicFields > {
2023-07-25 08:59:48 +00:00
let parameter_list = m . ctype
. as_ref ( )
. map ( | x | x . params . iter ( )
. map ( | p | ( IString ::try_from ( String ::from_utf8_lossy ( p . name ) . to_string ( ) ) , IString ::try_from ( p . value . to_string ( ) ) ) )
. filter ( | ( k , v ) | k . is_ok ( ) & & v . is_ok ( ) )
. map ( | ( k , v ) | ( k . unwrap ( ) , v . unwrap ( ) ) )
. collect ( ) )
. unwrap_or ( vec! [ ] ) ;
Ok ( BasicFields {
2022-07-05 15:08:12 +00:00
parameter_list ,
id : NString (
2023-07-25 08:59:48 +00:00
m . id . as_ref ( )
2023-05-15 16:23:23 +00:00
. and_then ( | ci | IString ::try_from ( ci . to_string ( ) ) . ok ( ) ) ,
2023-07-25 08:59:48 +00:00
) ,
2022-07-05 15:08:12 +00:00
description : NString (
2023-07-25 08:59:48 +00:00
m . description . as_ref ( )
2023-05-15 16:23:23 +00:00
. and_then ( | cd | IString ::try_from ( cd . to_string ( ) ) . ok ( ) ) ,
2022-07-05 15:08:12 +00:00
) ,
2023-07-25 08:59:48 +00:00
content_transfer_encoding : match m . transfer_encoding {
mime ::mechanism ::Mechanism ::_8Bit = > unchecked_istring ( " 8bit " ) ,
mime ::mechanism ::Mechanism ::Binary = > unchecked_istring ( " binary " ) ,
mime ::mechanism ::Mechanism ::QuotedPrintable = > unchecked_istring ( " quoted-printable " ) ,
mime ::mechanism ::Mechanism ::Base64 = > unchecked_istring ( " base64 " ) ,
_ = > unchecked_istring ( " 7bit " ) ,
} ,
// @FIXME we can't compute the size of the message currently...
2023-07-25 17:08:48 +00:00
size : u32 ::try_from ( sz ) ? ,
2023-07-25 08:59:48 +00:00
} )
2022-07-05 15:08:12 +00:00
}
2023-08-16 16:01:22 +00:00
/// Extract message section for section identifier passed by the FETCH BODY[<section>]<<partial>>
/// request
///
/// Example of message sections:
///
/// ```
/// HEADER ([RFC-2822] header of the message)
/// TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED
/// 1 TEXT/PLAIN
/// 2 APPLICATION/OCTET-STREAM
/// 3 MESSAGE/RFC822
/// 3.HEADER ([RFC-2822] header of the message)
/// 3.TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED
/// 3.1 TEXT/PLAIN
/// 3.2 APPLICATION/OCTET-STREAM
/// 4 MULTIPART/MIXED
/// 4.1 IMAGE/GIF
/// 4.1.MIME ([MIME-IMB] header for the IMAGE/GIF)
/// 4.2 MESSAGE/RFC822
/// 4.2.HEADER ([RFC-2822] header of the message)
/// 4.2.TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED
/// 4.2.1 TEXT/PLAIN
/// 4.2.2 MULTIPART/ALTERNATIVE
/// 4.2.2.1 TEXT/PLAIN
/// 4.2.2.2 TEXT/RICHTEXT
/// ```
2022-07-15 14:15:48 +00:00
fn get_message_section < ' a > (
parsed : & ' a Message < ' a > ,
section : & Option < FetchSection > ,
) -> Result < Cow < ' a , [ u8 ] > > {
match section {
2022-07-20 13:14:34 +00:00
Some ( FetchSection ::Text ( None ) ) = > {
2023-08-16 16:01:22 +00:00
Ok ( parsed . raw_body . into ( ) )
2022-07-20 13:14:34 +00:00
}
2022-07-15 14:15:48 +00:00
Some ( FetchSection ::Text ( Some ( part ) ) ) = > {
2023-08-16 16:01:22 +00:00
map_subpart ( parsed . child . as_ref ( ) , part . 0. as_slice ( ) , | part_msg | {
Ok ( part_msg . as_message ( ) . ok_or ( Error ::msg ( " Not a message/rfc822 part while expected by request (xxx.TEXT) " ) ) ?
. raw_body
2022-07-15 14:15:48 +00:00
. into ( ) )
} )
}
2023-08-16 16:01:22 +00:00
Some ( FetchSection ::Header ( part ) ) = > map_subpart (
parsed . child . as_ref ( ) ,
2022-07-15 14:15:48 +00:00
part . as_ref ( ) . map ( | p | p . 0. as_slice ( ) ) . unwrap_or ( & [ ] ) ,
| part_msg | {
2023-08-16 16:01:22 +00:00
Ok ( part_msg . as_message ( ) . ok_or ( Error ::msg ( " Not a message/rfc822 part while expected by request (xxx.TEXT) " ) ) ?
. raw_headers
2022-07-15 14:15:48 +00:00
. into ( ) )
} ,
) ,
2022-07-15 16:16:06 +00:00
Some (
FetchSection ::HeaderFields ( part , fields ) | FetchSection ::HeaderFieldsNot ( part , fields ) ,
) = > {
2023-09-21 09:27:33 +00:00
let invert = matches! ( section , Some ( FetchSection ::HeaderFieldsNot ( _ , _ ) ) ) ;
2022-07-15 16:16:06 +00:00
let fields = fields
. iter ( )
. map ( | x | match x {
AString ::Atom ( a ) = > a . as_bytes ( ) ,
AString ::String ( IString ::Literal ( l ) ) = > l . as_slice ( ) ,
AString ::String ( IString ::Quoted ( q ) ) = > q . as_bytes ( ) ,
} )
. collect ::< Vec < _ > > ( ) ;
2023-09-21 09:27:33 +00:00
map_subpart (
parsed . child . as_ref ( ) ,
2022-07-15 16:16:06 +00:00
part . as_ref ( ) . map ( | p | p . 0. as_slice ( ) ) . unwrap_or ( & [ ] ) ,
| part_msg | {
let mut ret = vec! [ ] ;
2023-09-21 09:27:33 +00:00
for f in & part_msg . mime ( ) . kv {
let ( k , v ) = match f {
header ::Field ::Good ( header ::Kv2 ( k , v ) ) = > ( k , v ) ,
_ = > continue ,
} ;
2022-07-15 16:16:06 +00:00
if fields
. as_slice ( )
. iter ( )
2023-09-21 09:27:33 +00:00
. any ( | x | ( x = = k ) ^ invert )
2022-07-15 16:16:06 +00:00
{
2023-09-21 09:27:33 +00:00
ret . extend ( * k ) ;
2022-07-15 16:16:06 +00:00
ret . extend ( b " : " ) ;
2023-09-21 09:27:33 +00:00
ret . extend ( * v ) ;
ret . extend ( b " \r \n " ) ;
2022-07-15 16:16:06 +00:00
}
}
ret . extend ( b " \r \n " ) ;
Ok ( ret . into ( ) )
} ,
2023-09-21 09:27:33 +00:00
)
2022-07-15 16:16:06 +00:00
}
2023-08-16 16:01:22 +00:00
Some ( FetchSection ::Part ( part ) ) = > map_subpart ( parsed . child . as_ref ( ) , part . 0. as_slice ( ) , | part | {
let bytes = match & part {
AnyPart ::Txt ( p ) = > p . body ,
AnyPart ::Bin ( p ) = > p . body ,
AnyPart ::Msg ( p ) = > p . raw_part ,
AnyPart ::Mult ( _ ) = > bail! ( " Multipart part has no body " ) ,
2022-07-15 14:15:48 +00:00
} ;
2023-08-16 16:01:22 +00:00
Ok ( bytes . to_vec ( ) . into ( ) )
2022-07-15 14:15:48 +00:00
} ) ,
2023-08-16 16:01:22 +00:00
Some ( FetchSection ::Mime ( part ) ) = > map_subpart ( parsed . child . as_ref ( ) , part . 0. as_slice ( ) , | part | {
let bytes = match & part {
AnyPart ::Txt ( p ) = > p . mime . fields . raw ,
AnyPart ::Bin ( p ) = > p . mime . fields . raw ,
AnyPart ::Msg ( p ) = > p . mime . fields . raw ,
AnyPart ::Mult ( p ) = > p . mime . fields . raw ,
} ;
Ok ( bytes . to_vec ( ) . into ( ) )
2022-07-15 14:15:48 +00:00
} ) ,
2023-08-16 16:01:22 +00:00
None = > Ok ( parsed . raw_part . into ( ) ) ,
2022-07-15 14:15:48 +00:00
}
}
2023-08-16 16:01:22 +00:00
/// Fetch a MIME SubPart
///
/// eg. FETCH BODY[4.2.2.1] -> [4, 2, 2, 1]
fn map_subpart < ' a , F , R > ( part : & AnyPart < ' a > , path : & [ NonZeroU32 ] , f : F ) -> Result < R >
2022-07-15 14:15:48 +00:00
where
2023-08-16 16:01:22 +00:00
F : FnOnce ( & AnyPart < ' a > ) -> Result < R > ,
2022-07-15 14:15:48 +00:00
{
if path . is_empty ( ) {
2023-08-16 16:01:22 +00:00
f ( part )
2022-07-15 14:15:48 +00:00
} else {
2023-08-16 16:01:22 +00:00
match part {
AnyPart ::Mult ( x ) = > map_subpart (
x . children
. get ( path [ 0 ] . get ( ) as usize - 1 )
. ok_or ( anyhow! ( " Unable to resolve subpath {:?}, current multipart has only {} elements " , path , x . children . len ( ) ) ) ? ,
& path [ 1 .. ] ,
f ) ,
AnyPart ::Msg ( x ) = > map_subpart ( x . child . as_ref ( ) , path , f ) ,
_ = > bail! ( " You tried to access a subpart on an atomic part (text or binary). Unresolved subpath {:?} " , path ) ,
2022-07-15 14:15:48 +00:00
}
}
}
2022-07-04 10:07:48 +00:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2022-07-05 13:21:14 +00:00
use imap_codec ::codec ::Encode ;
use std ::fs ;
2022-07-04 10:07:48 +00:00
2022-07-05 16:27:49 +00:00
/// Future automated test. We use lossy utf8 conversion + lowercase everything,
2022-07-05 13:21:14 +00:00
/// so this test might allow invalid results. But at least it allows us to quickly test a
/// large variety of emails.
/// Keep in mind that special cases must still be tested manually!
2022-07-04 10:07:48 +00:00
#[ test ]
2022-07-05 13:21:14 +00:00
fn fetch_body ( ) -> Result < ( ) > {
2022-07-05 15:48:10 +00:00
let prefixes = [
2023-07-25 17:08:48 +00:00
/* * * * MY OWN DATASET * * * */
2022-07-05 15:48:10 +00:00
" tests/emails/dxflrs/0001_simple " ,
" tests/emails/dxflrs/0002_mime " ,
2022-07-05 16:27:49 +00:00
" tests/emails/dxflrs/0003_mime-in-mime " ,
2022-07-20 13:14:34 +00:00
" tests/emails/dxflrs/0004_msg-in-msg " ,
2023-07-25 17:08:48 +00:00
// eml_codec do not support continuation for the moment
2022-07-20 11:58:24 +00:00
//"tests/emails/dxflrs/0005_mail-parser-readme",
2023-03-09 10:30:44 +00:00
" tests/emails/dxflrs/0006_single-mime " ,
2023-07-25 17:08:48 +00:00
" tests/emails/dxflrs/0007_raw_msg_in_rfc822 " ,
/* * * * (STRANGE) RFC * * * */
//"tests/emails/rfc/000", // must return text/enriched, we return text/plain
//"tests/emails/rfc/001", // does not recognize the multipart/external-body, breaks the
// whole parsing
//"tests/emails/rfc/002", // wrong date in email
//"tests/emails/rfc/003", // dovecot fixes \r\r: the bytes number is wrong + text/enriched
/* * * * THIRD PARTY * * * */
//"tests/emails/thirdparty/000", // dovecot fixes \r\r: the bytes number is wrong
//"tests/emails/thirdparty/001", // same
" tests/emails/thirdparty/002 " , // same
2023-03-09 10:30:44 +00:00
2023-07-25 17:08:48 +00:00
/* * * * LEGACY * * * */
//"tests/emails/legacy/000", // same issue with \r\r
2022-07-05 15:48:10 +00:00
] ;
2022-07-04 10:07:48 +00:00
2022-07-05 15:48:10 +00:00
for pref in prefixes . iter ( ) {
println! ( " {} " , pref ) ;
let txt = fs ::read ( format! ( " {} .eml " , pref ) ) ? ;
2022-07-08 08:23:07 +00:00
let exp = fs ::read ( format! ( " {} .dovecot.body " , pref ) ) ? ;
2023-07-25 17:08:48 +00:00
let message = eml_codec ::parse_message ( & txt ) . unwrap ( ) . 1 ;
2022-07-04 10:07:48 +00:00
2022-07-05 15:48:10 +00:00
let mut resp = Vec ::new ( ) ;
2023-07-25 17:08:48 +00:00
MessageAttribute ::Body ( build_imap_email_struct ( & message . child ) ? ) . encode ( & mut resp ) . unwrap ( ) ;
2022-07-04 10:07:48 +00:00
2022-07-05 15:48:10 +00:00
let resp_str = String ::from_utf8_lossy ( & resp ) . to_lowercase ( ) ;
2022-07-05 13:21:14 +00:00
2022-07-05 15:48:10 +00:00
let exp_no_parenthesis = & exp [ 1 .. exp . len ( ) - 1 ] ;
let exp_str = String ::from_utf8_lossy ( exp_no_parenthesis ) . to_lowercase ( ) ;
2023-02-07 11:22:54 +00:00
println! ( " aerogramme: {} \n \n dovecot: {} \n \n " , resp_str , exp_str ) ;
2022-07-08 08:23:07 +00:00
//println!("\n\n {} \n\n", String::from_utf8_lossy(&resp));
2022-07-05 15:48:10 +00:00
assert_eq! ( resp_str , exp_str ) ;
}
2022-07-04 10:07:48 +00:00
Ok ( ( ) )
}
}