2022-07-12 13:31:29 +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 ;
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 } ;
2022-06-29 17:24:21 +00:00
use futures ::stream ::{ FuturesOrdered , StreamExt } ;
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-07-25 08:59:48 +00:00
use eml_codec ::{
imf ::{ self as imf } ,
part ::{ AnyPart } ,
part ::discrete ::{ Text , Binary } ,
part ::composite ::{ Message , Multipart } ,
mime ,
} ;
2022-06-29 13:39:54 +00:00
2022-06-29 13:52:09 +00:00
use crate ::mail ::mailbox ::Mailbox ;
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-07-25 08:59:48 +00:00
const BODY_CHECK : & str = " body attribute asked but only header is fetched, logic error " ;
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 ) ? ;
2022-07-13 09:19:08 +00:00
for ( _i , _uid , uuid ) in mails . iter ( ) {
2022-07-12 15:32:57 +00:00
match kind {
StoreType ::Add = > {
self . mailbox . add_flags ( * uuid , & flags [ .. ] ) . await ? ;
}
StoreType ::Remove = > {
self . mailbox . del_flags ( * uuid , & flags [ .. ] ) . await ? ;
}
StoreType ::Replace = > {
2022-07-13 09:00:35 +00:00
self . mailbox . set_flags ( * 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! [ ] ;
for ( _i , _uid , uuid ) in mails . iter ( ) {
new_uuids . push ( to . copy_from ( & self . mailbox , * uuid ) . await ? ) ;
}
let mut ret = vec! [ ] ;
let to_state = to . current_uid_index ( ) . await ;
for ( ( _i , uid , _uuid ) , new_uuid ) in mails . iter ( ) . zip ( new_uuids . iter ( ) ) {
let dest_uid = to_state
. table
. get ( new_uuid )
. ok_or ( anyhow! ( " copied mail not in destination mailbox " ) ) ?
. 0 ;
ret . push ( ( * uid , dest_uid ) ) ;
}
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 > > {
2022-07-13 13:00:13 +00:00
let mails = self . get_mail_ids ( sequence_set , * is_uid_fetch ) ? ;
2022-06-29 17:24:21 +00:00
let mails_uuid = mails
. iter ( )
2022-07-12 15:32:57 +00:00
. map ( | ( _i , _uid , uuid ) | * uuid )
2022-06-29 17:24:21 +00:00
. collect ::< Vec < _ > > ( ) ;
let mails_meta = self . mailbox . fetch_meta ( & mails_uuid ) . await ? ;
2022-07-13 13:00:13 +00:00
let mut fetch_attrs = match attributes {
2022-06-29 17:24:21 +00:00
MacroOrFetchAttributes ::Macro ( m ) = > m . expand ( ) ,
MacroOrFetchAttributes ::FetchAttributes ( a ) = > a . clone ( ) ,
} ;
2022-07-13 13:00:13 +00:00
if * is_uid_fetch & & ! fetch_attrs . contains ( & FetchAttribute ::Uid ) {
fetch_attrs . push ( FetchAttribute ::Uid ) ;
}
2022-06-29 17:24:21 +00:00
let need_body = fetch_attrs . iter ( ) . any ( | x | {
matches! (
x ,
FetchAttribute ::Body
| FetchAttribute ::BodyExt { .. }
| FetchAttribute ::Rfc822
| FetchAttribute ::Rfc822Text
| FetchAttribute ::BodyStructure
)
} ) ;
let mails = if need_body {
let mut iter = mails
. into_iter ( )
. zip ( mails_meta . into_iter ( ) )
2022-07-12 15:32:57 +00:00
. map ( | ( ( i , uid , uuid ) , meta ) | async move {
2022-06-29 17:24:21 +00:00
let body = self . mailbox . fetch_full ( uuid , & meta . message_key ) . await ? ;
Ok ::< _ , anyhow ::Error > ( ( i , uid , uuid , meta , Some ( body ) ) )
} )
. collect ::< FuturesOrdered < _ > > ( ) ;
let mut mails = vec! [ ] ;
while let Some ( m ) = iter . next ( ) . await {
mails . push ( m ? ) ;
}
mails
} else {
mails
. into_iter ( )
. zip ( mails_meta . into_iter ( ) )
2022-07-12 15:32:57 +00:00
. map ( | ( ( i , uid , uuid ) , meta ) | ( i , uid , uuid , meta , None ) )
2022-06-29 17:24:21 +00:00
. collect ::< Vec < _ > > ( )
} ;
let mut ret = vec! [ ] ;
2022-06-29 18:00:38 +00:00
for ( i , uid , uuid , meta , body ) in mails {
2022-07-13 09:32:47 +00:00
let mut attributes = vec! [ ] ;
2022-06-29 17:24:21 +00:00
2022-06-29 17:27:32 +00:00
let ( _uid2 , flags ) = self
2022-06-29 17:24:21 +00:00
. known_state
. table
. get ( & uuid )
. ok_or_else ( | | anyhow! ( " Mail not in uidindex table: {} " , uuid ) ) ? ;
2023-07-25 08:59:48 +00:00
let ( parts , imf ) = match & body {
2022-06-29 18:00:38 +00:00
Some ( m ) = > {
2023-07-25 08:59:48 +00:00
let eml = eml_codec ::parse_message ( m ) . or ( Err ( anyhow! ( " Invalid mail body " ) ) ) ? . 1 ;
( Some ( eml . child ) , eml . imf )
}
None = > {
let imf = eml_codec ::parse_imf ( & meta . headers ) . or ( Err ( anyhow! ( " Invalid mail headers " ) ) ) ? . 1 ;
( None , imf )
2022-06-29 18:00:38 +00:00
}
} ;
2022-06-29 17:24:21 +00:00
for attr in fetch_attrs . iter ( ) {
match attr {
2022-07-13 09:32:47 +00:00
FetchAttribute ::Uid = > attributes . push ( MessageAttribute ::Uid ( uid ) ) ,
2022-06-29 17:24:21 +00:00
FetchAttribute ::Flags = > {
attributes . push ( MessageAttribute ::Flags (
flags . iter ( ) . filter_map ( | f | string_to_flag ( f ) ) . collect ( ) ,
) ) ;
}
FetchAttribute ::Rfc822Size = > {
attributes . push ( MessageAttribute ::Rfc822Size ( meta . rfc822_size as u32 ) )
}
2022-07-15 14:15:48 +00:00
FetchAttribute ::Rfc822Header = > {
attributes . push ( MessageAttribute ::Rfc822Header ( NString (
meta . headers . to_vec ( ) . try_into ( ) . ok ( ) . map ( IString ::Literal ) ,
) ) )
}
2022-06-30 11:36:21 +00:00
FetchAttribute ::Rfc822Text = > {
2023-07-25 08:59:48 +00:00
//@FIXME this is not efficient, this is a hack as we need to patch
// eml_codec to correctly implement this behavior
let txt = eml_codec ::parse_imf ( body . as_ref ( ) . expect ( BODY_CHECK ) . as_slice ( ) )
. map ( | ( x , _ ) | x )
. unwrap_or ( b " " ) ;
2022-06-30 11:36:21 +00:00
2022-07-15 14:15:48 +00:00
attributes . push ( MessageAttribute ::Rfc822Text ( NString (
2023-07-25 08:59:48 +00:00
txt . try_into ( ) . ok ( ) . map ( IString ::Literal ) ,
2022-07-15 14:15:48 +00:00
) ) ) ;
2022-06-29 18:10:42 +00:00
}
2022-07-15 14:15:48 +00:00
FetchAttribute ::Rfc822 = > attributes . push ( MessageAttribute ::Rfc822 ( NString (
body . as_ref ( )
2023-07-25 08:59:48 +00:00
. expect ( BODY_CHECK )
2022-07-15 14:15:48 +00:00
. clone ( )
. try_into ( )
. ok ( )
. map ( IString ::Literal ) ,
) ) ) ,
2022-06-29 18:00:38 +00:00
FetchAttribute ::Envelope = > {
2023-07-25 08:59:48 +00:00
attributes . push ( MessageAttribute ::Envelope ( message_envelope ( & imf ) ) )
2022-06-29 18:00:38 +00:00
}
2022-07-04 16:14:19 +00:00
FetchAttribute ::Body = > attributes . push ( MessageAttribute ::Body (
2023-07-25 08:59:48 +00:00
build_imap_email_struct ( parts . as_ref ( ) . expect ( BODY_CHECK ) . as_ref ( ) ) ? ,
2022-07-04 16:14:19 +00:00
) ) ,
FetchAttribute ::BodyStructure = > attributes . push ( MessageAttribute ::Body (
2023-07-25 08:59:48 +00:00
build_imap_email_struct ( parts . as_ref ( ) . expect ( BODY_CHECK ) . as_ref ( ) ) ? ,
2022-07-04 16:14:19 +00:00
) ) ,
2022-06-30 11:36:21 +00:00
FetchAttribute ::BodyExt {
2022-07-13 15:05:07 +00:00
section ,
partial ,
peek ,
2022-06-30 11:36:21 +00:00
} = > {
2023-07-25 08:59:48 +00:00
// @FIXME deactivated while eml_codec is integrated
todo! ( ) ;
2022-07-13 15:05:07 +00:00
// @TODO Add missing section specifiers
2023-07-25 08:59:48 +00:00
/* match get_message_section(&parts.expect("body attribute asked but only header is fetched, logic error"), section) {
2022-07-15 14:15:48 +00:00
Ok ( text ) = > {
let seen_flag = Flag ::Seen . to_string ( ) ;
if ! peek & & ! flags . iter ( ) . any ( | x | * x = = seen_flag ) {
// Add \Seen flag
self . mailbox . add_flags ( uuid , & [ seen_flag ] ) . await ? ;
}
let ( text , origin ) = match partial {
Some ( ( begin , len ) ) = > {
if * begin as usize > text . len ( ) {
( & [ ] [ .. ] , Some ( * begin ) )
} else if ( * begin + len . get ( ) ) as usize > = text . len ( ) {
( & text [ * begin as usize .. ] , Some ( * begin ) )
} else {
(
& text [ * begin as usize
.. ( * begin + len . get ( ) ) as usize ] ,
Some ( * begin ) ,
)
}
}
None = > ( & text [ .. ] , None ) ,
} ;
let data =
NString ( text . to_vec ( ) . try_into ( ) . ok ( ) . map ( IString ::Literal ) ) ;
attributes . push ( MessageAttribute ::BodyExt {
section : section . clone ( ) ,
origin ,
data ,
} )
2022-07-13 15:05:07 +00:00
}
2022-07-15 14:15:48 +00:00
Err ( e ) = > {
tracing ::error! (
" Could not get section {:?} of message {}: {} " ,
section ,
uuid ,
e
) ;
2022-07-13 15:05:07 +00:00
}
}
2023-07-25 08:59:48 +00:00
* /
2022-06-30 11:36:21 +00:00
}
FetchAttribute ::InternalDate = > {
2023-05-15 16:23:23 +00:00
let dt = Utc . fix ( ) . timestamp_opt ( i64 ::try_from ( meta . internaldate / 1000 ) ? , 0 ) . earliest ( ) . ok_or ( anyhow! ( " Unable to parse internal date " ) ) ? ;
attributes . push ( MessageAttribute ::InternalDate ( MyDateTime ( dt ) ) ) ;
2022-06-30 11:36:21 +00:00
}
2022-06-29 17:24:21 +00:00
}
}
ret . push ( Body ::Data ( Data ::Fetch {
seq_or_uid : i ,
attributes ,
} ) ) ;
}
Ok ( ret )
}
2022-06-29 13:39:54 +00:00
// ----
2022-07-12 15:32:57 +00:00
// Gets the UIDs and UUIDs of mails identified by a SequenceSet of
// sequence numbers
fn get_mail_ids (
& self ,
sequence_set : & SequenceSet ,
2022-07-13 13:00:13 +00:00
by_uid : bool ,
2022-07-12 15:32:57 +00:00
) -> Result < Vec < ( NonZeroU32 , ImapUid , UniqueIdent ) > > {
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 {
mails . push ( ( NonZeroU32 ::try_from ( i as u32 + 1 ) . unwrap ( ) , mail . 0 , mail . 1 ) ) ;
}
} 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 ) {
mails . push ( ( i , mail . 0 , mail . 1 ) ) ;
} 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 {
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 08:59:48 +00:00
from : msg . from . iter ( ) . map ( convert_mbx ) . collect ( ) ,
sender : msg . sender . iter ( ) . map ( convert_mbx ) . collect ( ) , //@FIXME put from[0] if empty
reply_to : convert_addresses ( & msg . reply_to ) , //@FIXME put from if empty
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 ) = > {
let subtype = x . interpreted . parsed . ctype . as_ref ( )
. map ( | x | IString ::try_from ( String ::from_utf8_lossy ( x . sub ) . to_string ( ) ) . ok ( ) )
. flatten ( )
. 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 ) = > {
//@FIXME check if we must really guess a charset if none is provided, if so we must
//update this code
let basic = basic_fields ( & x . interpreted . parsed ) ? ;
let subtype = x . interpreted . parsed . ctype . as_ref ( )
. map ( | x | IString ::try_from ( String ::from_utf8_lossy ( x . sub ) . to_string ( ) ) . ok ( ) )
. flatten ( )
2022-07-20 13:14:34 +00:00
. unwrap_or ( unchecked_istring ( " plain " ) ) ;
2023-07-25 08:59:48 +00:00
let number_of_lines = x . body . iter ( )
. filter ( | x | * * x = = b '\n' )
. count ( )
. try_into ( )
. unwrap_or ( 0 ) ;
2022-07-20 13:14:34 +00:00
Ok ( BodyStructure ::Single {
body : FetchBody {
basic ,
specific : SpecificFields ::Text {
subtype ,
number_of_lines ,
} ,
} ,
extension : None ,
} )
}
2023-07-25 08:59:48 +00:00
AnyPart ::Bin ( x ) = > {
//let (_, basic) = headers_to_basic_fields(part, bp.len())?;
let basic = basic_fields ( & x . interpreted . parsed ) ? ;
let default = mime ::r#type ::NaiveType { main : & [ ] , sub : & [ ] , params : vec ! [ ] } ;
let ct = x . interpreted . parsed . ctype . as_ref ( ) . unwrap_or ( & default ) ;
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 ) = > {
let basic = basic_fields ( & x . interpreted . parsed ) ? ;
2023-02-07 10:27:17 +00:00
// We do not count the number of lines but the number of line
// feeds to have the same behavior as Dovecot and Cyrus.
// 2 lines = 1 line feed.
2023-07-25 08:59:48 +00:00
//let nol = inner.raw_message().iter().filter(|&c| c == &b'\n').count();
let nol = 0 ; // @FIXME broken for now
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 ( ) ) ? ) ,
number_of_lines : u32 ::try_from ( nol ) ? ,
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
/// 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 08:59:48 +00:00
fn basic_fields ( m : & mime ::NaiveMIME ) -> Result < BasicFields > {
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...
size : u32 ::try_from ( 0 ) ? ,
} )
2022-07-05 15:08:12 +00:00
}
2023-07-25 08:59:48 +00:00
/*
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-02-07 10:27:17 +00:00
let rp = parsed . root_part ( ) ;
2022-07-20 13:14:34 +00:00
Ok ( parsed
. raw_message
. get ( rp . offset_body .. rp . offset_end )
. ok_or ( Error ::msg (
" Unable to extract email body, cursors out of bound. This is a bug. " ,
) ) ?
. into ( ) )
}
2022-07-15 14:15:48 +00:00
Some ( FetchSection ::Text ( Some ( part ) ) ) = > {
2022-07-15 15:55:04 +00:00
map_subpart_msg ( parsed , part . 0. as_slice ( ) , | part_msg | {
2023-02-07 10:27:17 +00:00
let rp = part_msg . root_part ( ) ;
2022-07-15 14:15:48 +00:00
Ok ( part_msg
. raw_message
2022-07-20 13:14:34 +00:00
. get ( rp . offset_body .. rp . offset_end )
2022-07-15 14:15:48 +00:00
. ok_or ( Error ::msg (
" Unable to extract email body, cursors out of bound. This is a bug. " ,
) ) ?
. to_vec ( )
. into ( ) )
} )
}
2022-07-15 15:55:04 +00:00
Some ( FetchSection ::Header ( part ) ) = > map_subpart_msg (
2022-07-15 14:15:48 +00:00
parsed ,
part . as_ref ( ) . map ( | p | p . 0. as_slice ( ) ) . unwrap_or ( & [ ] ) ,
| part_msg | {
2023-02-07 10:27:17 +00:00
let rp = part_msg . root_part ( ) ;
2022-07-15 14:15:48 +00:00
Ok ( part_msg
. raw_message
2022-07-20 13:14:34 +00:00
. get ( .. rp . offset_body )
2022-07-15 14:15:48 +00:00
. ok_or ( Error ::msg (
" Unable to extract email header, cursors out of bound. This is a bug. " ,
) ) ?
. to_vec ( )
. into ( ) )
} ,
) ,
2022-07-15 16:16:06 +00:00
Some (
FetchSection ::HeaderFields ( part , fields ) | FetchSection ::HeaderFieldsNot ( part , fields ) ,
) = > {
let invert = matches! ( section , Some ( FetchSection ::HeaderFieldsNot ( _ , _ ) ) ) ;
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 < _ > > ( ) ;
map_subpart_msg (
parsed ,
part . as_ref ( ) . map ( | p | p . 0. as_slice ( ) ) . unwrap_or ( & [ ] ) ,
| part_msg | {
let mut ret = vec! [ ] ;
2023-02-07 10:27:17 +00:00
for ( hn , hv ) in part_msg . headers_raw ( ) {
2022-07-15 16:16:06 +00:00
if fields
. as_slice ( )
. iter ( )
2023-02-07 10:27:17 +00:00
. any ( | x | ( * x = = hn . as_bytes ( ) ) ^ invert )
2022-07-15 16:16:06 +00:00
{
2023-02-07 10:27:17 +00:00
ret . extend ( hn . as_bytes ( ) ) ;
2022-07-15 16:16:06 +00:00
ret . extend ( b " : " ) ;
ret . extend ( hv . as_bytes ( ) ) ;
}
}
ret . extend ( b " \r \n " ) ;
Ok ( ret . into ( ) )
} ,
)
}
2022-07-15 15:55:04 +00:00
Some ( FetchSection ::Part ( part ) ) = > map_subpart ( parsed , part . 0. as_slice ( ) , | _msg , part | {
2022-07-20 13:14:34 +00:00
let bytes = match & part . body {
2023-07-25 08:59:48 +00:00
AnyPart ::Txt ( p ) = > p . as_bytes ( ) . to_vec ( ) ,
AnyPart ::Bin ( p ) = > p . to_vec ( ) ,
AnyPart ::Msg ( p ) = > p . raw_message . to_vec ( ) ,
AnyPart ::Multipart ( _ ) = > bail! ( " Multipart part has no body " ) ,
2022-07-15 14:15:48 +00:00
} ;
Ok ( bytes . into ( ) )
} ) ,
2022-07-15 15:55:04 +00:00
Some ( FetchSection ::Mime ( part ) ) = > map_subpart ( parsed , part . 0. as_slice ( ) , | msg , part | {
2022-07-15 14:15:48 +00:00
let mut ret = vec! [ ] ;
2023-02-07 10:27:17 +00:00
for head in part . headers . iter ( ) {
ret . extend ( head . name . as_str ( ) . as_bytes ( ) ) ;
2022-07-15 14:15:48 +00:00
ret . extend ( b " : " ) ;
2023-02-07 10:27:17 +00:00
ret . extend ( & msg . raw_message [ head . offset_start .. head . offset_end ] ) ;
2022-07-15 14:15:48 +00:00
}
ret . extend ( b " \r \n " ) ;
Ok ( ret . into ( ) )
} ) ,
None = > Ok ( parsed . raw_message . clone ( ) ) ,
}
}
2023-05-15 16:23:23 +00:00
fn map_subpart_msg < F , R > ( msg : & Message < '_ > , path : & [ NonZeroU32 ] , f : F ) -> Result < R >
2022-07-15 14:15:48 +00:00
where
F : FnOnce ( & Message < '_ > ) -> Result < R > ,
{
if path . is_empty ( ) {
f ( msg )
} else {
let part = msg
. parts
. get ( path [ 0 ] . get ( ) as usize - 1 )
. ok_or ( anyhow! ( " No such subpart: {} " , path [ 0 ] ) ) ? ;
2023-02-07 10:27:17 +00:00
if let PartType ::Message ( msg_attach ) = & part . body {
2023-05-15 16:23:23 +00:00
map_subpart_msg ( msg_attach , & path [ 1 .. ] , f )
2022-07-15 14:15:48 +00:00
} else {
bail! ( " Subpart is not a message: {} " , path [ 0 ] ) ;
}
}
}
2023-05-15 16:23:23 +00:00
fn map_subpart < F , R > ( msg : & Message < '_ > , path : & [ NonZeroU32 ] , f : F ) -> Result < R >
2022-07-15 14:15:48 +00:00
where
F : FnOnce ( & Message < '_ > , & MessagePart < '_ > ) -> Result < R > ,
{
if path . is_empty ( ) {
bail! ( " Unexpected empty path " ) ;
} else {
let part = msg
. parts
. get ( path [ 0 ] . get ( ) as usize - 1 )
. ok_or ( anyhow! ( " No such subpart: {} " , path [ 0 ] ) ) ? ;
if path . len ( ) = = 1 {
f ( msg , part )
2023-05-15 16:23:23 +00:00
} else if let PartType ::Message ( msg_attach ) = & part . body {
map_subpart ( msg_attach , & path [ 1 .. ] , f )
2022-07-15 14:15:48 +00:00
} else {
2023-05-15 16:23:23 +00:00
bail! ( " Subpart is not a message: {} " , path [ 0 ] ) ;
2022-07-15 14:15:48 +00:00
}
}
2023-07-25 08:59:48 +00:00
} * /
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 = [
" 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-03-09 10:30:44 +00:00
// wrong. base64?
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 " ,
// panic - thread 'imap::mailbox_view::tests::fetch_body' panicked at 'range end index 128 out of range for slice of length 127', src/imap/mailbox_view.rs:798:64
2022-07-08 15:48:51 +00:00
//"tests/emails/dxflrs/0007_raw_msg_in_rfc822",
2023-03-09 10:30:44 +00:00
// broken, wrong mimetype text, should be audio
// "tests/emails/rfc/000",
2022-07-08 15:48:51 +00:00
// "tests/emails/rfc/001", // broken
2023-07-25 08:59:48 +00:00
// "tests/emails/rfc/002", // broken: dovecot adds \r when it is missing and count it as
2022-07-08 15:48:51 +00:00
// a character. Difference on how lines are counted too.
/* "tests / emails / rfc / 003", / / broken for the same reason
" tests/emails/thirdparty/000 " ,
" tests/emails/thirdparty/001 " ,
" tests/emails/thirdparty/002 " ,
2022-07-08 15:39:23 +00:00
* /
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 08:59:48 +00:00
let message = eml_codec ::email ( & txt ) . unwrap ( ) ;
2022-07-04 10:07:48 +00:00
2022-07-05 15:48:10 +00:00
let mut resp = Vec ::new ( ) ;
2023-02-07 11:22:54 +00:00
MessageAttribute ::Body ( build_imap_email_struct ( & message , message . root_part ( ) ) ? )
2022-07-05 15:48:10 +00:00
. encode ( & mut resp ) ;
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 ( ( ) )
}
}