2018-07-13 18:38:57 +03:00
/*
2019-06-28 19:34:40 +03:00
* meli - imap module .
2018-07-13 18:38:57 +03:00
*
2019-06-28 19:34:40 +03:00
* Copyright 2019 Manos Pitsidianakis
2018-07-13 18:38:57 +03:00
*
* This file is part of meli .
*
* meli is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* meli is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with meli . If not , see < http ://www.gnu.org/licenses/>.
* /
2020-02-06 01:49:18 +02:00
use crate ::get_path_hash ;
2020-01-08 17:04:44 +02:00
use smallvec ::SmallVec ;
2019-06-28 19:34:40 +03:00
#[ macro_use ]
mod protocol_parser ;
pub use protocol_parser ::{ UntaggedResponse ::* , * } ;
2020-02-26 10:54:10 +02:00
mod mailbox ;
pub use mailbox ::* ;
2019-06-28 19:34:40 +03:00
mod operations ;
pub use operations ::* ;
mod connection ;
pub use connection ::* ;
2019-09-06 12:54:12 +03:00
mod watch ;
pub use watch ::* ;
2020-06-07 14:00:13 +03:00
mod cache ;
2020-02-28 15:47:07 +02:00
pub mod managesieve ;
2020-05-30 14:40:47 +03:00
mod untagged ;
2019-06-28 19:34:40 +03:00
2020-07-06 11:12:38 +03:00
use crate ::async_workers ::{ Async , WorkContext } ;
2020-06-30 11:40:26 +03:00
use crate ::backends ::{
RefreshEventKind ::{ self , * } ,
* ,
} ;
2019-06-28 19:34:40 +03:00
use crate ::conf ::AccountSettings ;
use crate ::email ::* ;
2020-06-22 17:21:46 +03:00
use crate ::error ::{ MeliError , Result , ResultIntoMeliError } ;
2020-07-06 11:12:38 +03:00
use futures ::lock ::Mutex as FutureMutex ;
use futures ::stream ::Stream ;
2019-12-02 01:09:23 +02:00
use std ::collections ::{ hash_map ::DefaultHasher , BTreeMap } ;
2020-06-24 17:47:08 +03:00
use std ::collections ::{ BTreeSet , HashMap , HashSet } ;
2020-07-06 11:12:38 +03:00
use std ::future ::Future ;
2019-09-08 10:27:10 +03:00
use std ::hash ::Hasher ;
2020-07-06 11:12:38 +03:00
use std ::pin ::Pin ;
2019-08-24 15:12:44 +03:00
use std ::str ::FromStr ;
2019-11-18 12:59:04 +02:00
use std ::sync ::{ Arc , Mutex , RwLock } ;
2019-12-14 18:46:12 +02:00
use std ::time ::Instant ;
2019-06-28 19:34:40 +03:00
pub type UID = usize ;
2020-07-05 15:28:55 +03:00
pub static SUPPORTED_CAPABILITIES : & [ & str ] =
2019-11-23 17:48:52 +02:00
& [ " IDLE " , " LOGIN " , " LOGINDISABLED " , " ENABLE " , " IMAP4REV1 " ] ;
2019-11-06 15:11:35 +02:00
2019-09-14 15:57:28 +03:00
#[ derive(Debug, Default) ]
pub struct EnvelopeCache {
bytes : Option < String > ,
headers : Option < String > ,
body : Option < String > ,
flags : Option < Flag > ,
}
2019-09-21 21:02:35 +03:00
#[ derive(Debug, Clone) ]
pub struct ImapServerConf {
pub server_hostname : String ,
pub server_username : String ,
pub server_password : String ,
pub server_port : u16 ,
pub use_starttls : bool ,
2020-07-06 11:12:38 +03:00
pub use_tls : bool ,
2019-09-21 21:02:35 +03:00
pub danger_accept_invalid_certs : bool ,
2020-02-28 15:47:07 +02:00
pub protocol : ImapProtocol ,
2019-09-21 21:02:35 +03:00
}
2019-10-24 20:30:17 +03:00
struct IsSubscribedFn ( Box < dyn Fn ( & str ) -> bool + Send + Sync > ) ;
impl std ::fmt ::Debug for IsSubscribedFn {
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
write! ( f , " IsSubscribedFn Box " )
}
}
impl std ::ops ::Deref for IsSubscribedFn {
type Target = Box < dyn Fn ( & str ) -> bool + Send + Sync > ;
fn deref ( & self ) -> & Box < dyn Fn ( & str ) -> bool + Send + Sync > {
& self . 0
}
}
2020-05-10 21:14:49 +03:00
type Capabilities = HashSet < Vec < u8 > > ;
2019-11-10 23:02:23 +02:00
2020-02-28 15:47:07 +02:00
#[ macro_export ]
2019-11-27 14:22:53 +02:00
macro_rules ! get_conf_val {
( $s :ident [ $var :literal ] ) = > {
$s . extra . get ( $var ) . ok_or_else ( | | {
MeliError ::new ( format! (
" Configuration error ({}): IMAP connection requires the field `{}` set " ,
$s . name . as_str ( ) ,
$var
) )
} )
} ;
( $s :ident [ $var :literal ] , $default :expr ) = > {
$s . extra
. get ( $var )
. map ( | v | {
< _ > ::from_str ( v ) . map_err ( | e | {
MeliError ::new ( format! (
" Configuration error ({}): Invalid value for field `{}`: {} \n {} " ,
$s . name . as_str ( ) ,
$var ,
v ,
e
) )
} )
} )
. unwrap_or_else ( | | Ok ( $default ) )
} ;
}
2019-11-10 23:02:23 +02:00
#[ derive(Debug) ]
pub struct UIDStore {
2020-05-10 22:05:04 +03:00
account_hash : AccountHash ,
2020-06-07 14:00:13 +03:00
cache_headers : bool ,
2020-07-06 11:12:38 +03:00
capabilities : Arc < Mutex < Capabilities > > ,
2020-05-10 21:14:49 +03:00
uidvalidity : Arc < Mutex < HashMap < MailboxHash , UID > > > ,
hash_index : Arc < Mutex < HashMap < EnvelopeHash , ( UID , MailboxHash ) > > > ,
2020-06-11 11:41:08 +03:00
uid_index : Arc < Mutex < HashMap < ( MailboxHash , UID ) , EnvelopeHash > > > ,
2020-06-23 17:25:42 +03:00
msn_index : Arc < Mutex < HashMap < MailboxHash , Vec < UID > > > > ,
2019-11-10 23:02:23 +02:00
2020-05-10 21:14:49 +03:00
byte_cache : Arc < Mutex < HashMap < UID , EnvelopeCache > > > ,
2020-05-30 14:40:47 +03:00
tag_index : Arc < RwLock < BTreeMap < u64 , String > > > ,
2020-07-06 11:12:38 +03:00
mailboxes : Arc < FutureMutex < HashMap < MailboxHash , ImapMailbox > > > ,
2020-05-30 14:40:47 +03:00
is_online : Arc < Mutex < ( Instant , Result < ( ) > ) > > ,
refresh_events : Arc < Mutex < Vec < RefreshEvent > > > ,
sender : Arc < RwLock < Option < RefreshEventConsumer > > > ,
2019-11-10 23:02:23 +02:00
}
2020-05-10 22:05:04 +03:00
impl Default for UIDStore {
fn default ( ) -> Self {
UIDStore {
account_hash : 0 ,
2020-06-07 14:00:13 +03:00
cache_headers : false ,
2020-07-06 11:12:38 +03:00
capabilities : Default ::default ( ) ,
2020-05-10 22:05:04 +03:00
uidvalidity : Default ::default ( ) ,
hash_index : Default ::default ( ) ,
uid_index : Default ::default ( ) ,
2020-06-23 17:25:42 +03:00
msn_index : Default ::default ( ) ,
2020-05-10 22:05:04 +03:00
byte_cache : Default ::default ( ) ,
2020-07-06 11:12:38 +03:00
mailboxes : Arc ::new ( FutureMutex ::new ( Default ::default ( ) ) ) ,
2020-05-30 14:40:47 +03:00
tag_index : Arc ::new ( RwLock ::new ( Default ::default ( ) ) ) ,
is_online : Arc ::new ( Mutex ::new ( (
Instant ::now ( ) ,
Err ( MeliError ::new ( " Account is uninitialised. " ) ) ,
) ) ) ,
refresh_events : Default ::default ( ) ,
sender : Arc ::new ( RwLock ::new ( None ) ) ,
2020-05-10 22:05:04 +03:00
}
}
}
2019-06-28 19:34:40 +03:00
#[ derive(Debug) ]
pub struct ImapType {
account_name : String ,
2019-10-24 20:30:17 +03:00
is_subscribed : Arc < IsSubscribedFn > ,
2020-07-06 11:12:38 +03:00
connection : Arc < FutureMutex < ImapConnection > > ,
2019-09-21 21:02:35 +03:00
server_conf : ImapServerConf ,
2019-11-10 23:02:23 +02:00
uid_store : Arc < UIDStore > ,
2019-12-02 01:09:23 +02:00
can_create_flags : Arc < Mutex < bool > > ,
2018-07-13 18:38:57 +03:00
}
2019-06-28 19:34:40 +03:00
impl MailBackend for ImapType {
2020-07-05 19:11:57 +03:00
fn is_async ( & self ) -> bool {
2020-07-06 11:12:38 +03:00
true
2020-07-05 19:11:57 +03:00
}
fn is_remote ( & self ) -> bool {
true
}
2020-07-17 00:01:35 +03:00
fn supports_search ( & self ) -> bool {
true
}
2020-07-17 23:30:02 +03:00
fn fetch_async (
2020-07-06 11:12:38 +03:00
& mut self ,
mailbox : & Mailbox ,
) -> Result < Pin < Box < dyn Stream < Item = Result < Vec < Envelope > > > + Send + 'static > > > {
let uid_store = self . uid_store . clone ( ) ;
let can_create_flags = self . can_create_flags . clone ( ) ;
let mailbox_hash = mailbox . hash ( ) ;
2020-06-11 11:46:15 +03:00
let connection = self . connection . clone ( ) ;
2020-07-06 11:12:38 +03:00
let mut max_uid : Option < usize > = None ;
let mut valid_hash_set : HashSet < EnvelopeHash > = HashSet ::default ( ) ;
let mut our_unseen : BTreeSet < EnvelopeHash > = Default ::default ( ) ;
Ok ( Box ::pin ( async_stream ::try_stream! {
2020-07-17 23:30:02 +03:00
let ( cached_hash_set , cached_payload ) = fetch_cached_envs ( mailbox_hash , & mut our_unseen , & uid_store ) ? ;
2020-07-06 11:12:38 +03:00
yield cached_payload ;
loop {
2020-07-17 23:30:02 +03:00
let res = fetch_hlpr ( & connection , mailbox_hash , & cached_hash_set , & can_create_flags , & mut our_unseen , & mut valid_hash_set , & uid_store , & mut max_uid ) . await ? ;
2020-07-06 11:12:38 +03:00
yield res ;
if max_uid = = Some ( 1 ) | | max_uid = = Some ( 0 ) {
2019-12-10 23:54:19 +02:00
return ;
}
2020-06-07 14:00:13 +03:00
2020-07-06 11:12:38 +03:00
}
} ) )
2018-07-13 18:38:57 +03:00
}
2019-06-28 19:34:40 +03:00
2020-07-06 11:12:38 +03:00
fn refresh_async (
2019-12-17 14:12:41 +02:00
& mut self ,
2020-02-26 10:54:10 +02:00
mailbox_hash : MailboxHash ,
2020-02-22 11:24:36 +02:00
sender : RefreshEventConsumer ,
2020-07-06 11:12:38 +03:00
) -> ResultFuture < ( ) > {
2020-02-22 11:24:36 +02:00
let main_conn = self . connection . clone ( ) ;
2020-05-30 14:40:47 +03:00
* self . uid_store . sender . write ( ) . unwrap ( ) = Some ( sender ) ;
2020-02-22 11:24:36 +02:00
let uid_store = self . uid_store . clone ( ) ;
2020-07-06 11:12:38 +03:00
Ok ( Box ::pin ( async move {
let inbox = uid_store
. mailboxes
. lock ( )
. await
. get ( & mailbox_hash )
. map ( std ::clone ::Clone ::clone )
. unwrap ( ) ;
let mut conn = main_conn . lock ( ) . await ;
2020-07-17 22:45:25 +03:00
watch ::examine_updates ( inbox , & mut conn , & uid_store ) . await ? ;
2020-07-06 11:12:38 +03:00
Ok ( ( ) )
} ) )
}
2020-04-04 19:19:48 +03:00
2020-07-06 11:12:38 +03:00
fn mailboxes_async ( & self ) -> ResultFuture < HashMap < MailboxHash , Mailbox > > {
let uid_store = self . uid_store . clone ( ) ;
let connection = self . connection . clone ( ) ;
Ok ( Box ::pin ( async move {
{
let mailboxes = uid_store . mailboxes . lock ( ) . await ;
if ! mailboxes . is_empty ( ) {
return Ok ( mailboxes
. iter ( )
. map ( | ( h , f ) | ( * h , Box ::new ( Clone ::clone ( f ) ) as Mailbox ) )
. collect ( ) ) ;
2020-04-04 19:19:48 +03:00
}
2020-07-06 11:12:38 +03:00
}
let new_mailboxes = ImapType ::imap_mailboxes ( & connection ) . await ? ;
let mut mailboxes = uid_store . mailboxes . lock ( ) . await ;
* mailboxes = new_mailboxes ;
/*
let mut invalid_configs = vec! [ ] ;
for m in mailboxes . values ( ) {
if m . is_subscribed ( ) ! = ( self . is_subscribed ) ( m . path ( ) ) {
invalid_configs . push ( ( m . path ( ) , m . is_subscribed ( ) ) ) ;
}
}
if ! invalid_configs . is_empty ( ) {
let mut err_string = format! ( " {} : " , self . account_name ) ;
for ( m , server_value ) in invalid_configs . iter ( ) {
err_string . extend ( format! (
" Mailbox `{}` is {}subscribed on server but {}subscribed in your configuration. These settings have to match. \n " ,
if * server_value { " " } else { " not " } ,
if * server_value { " not " } else { " " } ,
m
) . chars ( ) ) ;
}
return Err ( MeliError ::new ( err_string ) ) ;
}
mailboxes . retain ( | _ , f | ( self . is_subscribed ) ( f . path ( ) ) ) ;
* /
let keys = mailboxes . keys ( ) . cloned ( ) . collect ::< HashSet < MailboxHash > > ( ) ;
let mut uid_lock = uid_store . uidvalidity . lock ( ) . unwrap ( ) ;
for f in mailboxes . values_mut ( ) {
uid_lock . entry ( f . hash ( ) ) . or_default ( ) ;
f . children . retain ( | c | keys . contains ( c ) ) ;
}
drop ( uid_lock ) ;
Ok ( mailboxes
. iter ( )
. filter ( | ( _ , f ) | f . is_subscribed )
. map ( | ( h , f ) | ( * h , Box ::new ( Clone ::clone ( f ) ) as Mailbox ) )
. collect ( ) )
} ) )
}
2020-02-22 11:24:36 +02:00
2020-07-06 11:12:38 +03:00
fn is_online_async ( & self ) -> ResultFuture < ( ) > {
let connection = self . connection . clone ( ) ;
Ok ( Box ::pin ( async move {
let mut conn = connection . lock ( ) . await ;
conn . connect ( ) . await ? ;
Ok ( ( ) )
} ) )
}
2020-07-17 23:30:02 +03:00
fn fetch ( & mut self , _mailbox : & Mailbox ) -> Result < Async < Result < Vec < Envelope > > > > {
2020-07-06 11:12:38 +03:00
Err ( MeliError ::new ( " Unimplemented. " ) )
}
fn refresh (
& mut self ,
_mailbox_hash : MailboxHash ,
_sender : RefreshEventConsumer ,
) -> Result < Async < ( ) > > {
Err ( MeliError ::new ( " Unimplemented. " ) )
2019-12-17 14:12:41 +02:00
}
2019-09-11 17:57:55 +03:00
fn watch (
& self ,
2020-07-06 11:12:38 +03:00
_sender : RefreshEventConsumer ,
_work_context : WorkContext ,
2019-09-11 17:57:55 +03:00
) -> Result < std ::thread ::ThreadId > {
2020-07-06 11:12:38 +03:00
Err ( MeliError ::new ( " Unimplemented. " ) )
}
fn watch_async ( & self , sender : RefreshEventConsumer ) -> ResultFuture < ( ) > {
debug! ( " watch_async called " ) ;
2020-05-30 14:40:47 +03:00
let conn = ImapConnection ::new_connection ( & self . server_conf , self . uid_store . clone ( ) ) ;
2019-09-06 12:54:12 +03:00
let main_conn = self . connection . clone ( ) ;
2020-05-30 14:40:47 +03:00
* self . uid_store . sender . write ( ) . unwrap ( ) = Some ( sender ) ;
2019-11-10 23:02:23 +02:00
let uid_store = self . uid_store . clone ( ) ;
2020-07-06 11:12:38 +03:00
Ok ( Box ::pin ( async move {
let has_idle : bool = uid_store
. capabilities
. lock ( )
. unwrap ( )
. iter ( )
. any ( | cap | cap . eq_ignore_ascii_case ( b " IDLE " ) ) ;
debug! ( has_idle ) ;
let kit = ImapWatchKit {
conn ,
main_conn ,
uid_store ,
} ;
if has_idle {
idle ( kit ) . await ? ;
} else {
poll_with_examine ( kit ) . await ? ;
}
debug! ( " watch_async future returning " ) ;
Ok ( ( ) )
} ) )
2018-07-13 18:38:57 +03:00
}
2019-06-28 19:34:40 +03:00
2020-05-10 21:14:49 +03:00
fn mailboxes ( & self ) -> Result < HashMap < MailboxHash , Mailbox > > {
2020-07-06 11:12:38 +03:00
Err ( MeliError ::new ( " Unimplemented. " ) )
2018-07-13 18:38:57 +03:00
}
2019-06-28 19:34:40 +03:00
2020-06-23 17:21:50 +03:00
fn operation ( & self , hash : EnvelopeHash ) -> Result < Box < dyn BackendOp > > {
let ( uid , mailbox_hash ) = if let Some ( v ) =
self . uid_store . hash_index . lock ( ) . unwrap ( ) . get ( & hash )
{
* v
} else {
return Err ( MeliError ::new (
" Message not found in local cache, it might have been deleted before you requested it. "
) ) ;
} ;
Ok ( Box ::new ( ImapOp ::new (
2019-06-28 19:34:40 +03:00
uid ,
2020-05-30 14:40:47 +03:00
mailbox_hash ,
2019-06-28 19:34:40 +03:00
self . connection . clone ( ) ,
2019-11-10 23:02:23 +02:00
self . uid_store . clone ( ) ,
2020-06-23 17:21:50 +03:00
) ) )
2018-07-13 18:38:57 +03:00
}
2019-06-28 19:34:40 +03:00
2020-06-30 11:40:26 +03:00
fn save (
& self ,
bytes : Vec < u8 > ,
mailbox_hash : MailboxHash ,
flags : Option < Flag > ,
) -> ResultFuture < ( ) > {
2020-07-06 11:12:38 +03:00
let uid_store = self . uid_store . clone ( ) ;
let connection = self . connection . clone ( ) ;
Ok ( Box ::pin ( async move {
let path = {
let mailboxes = uid_store . mailboxes . lock ( ) . await ;
let mailbox = mailboxes . get ( & mailbox_hash ) . ok_or_else ( | | {
MeliError ::new ( format! ( " Mailbox with hash {} not found. " , mailbox_hash ) )
} ) ? ;
if ! mailbox . permissions . lock ( ) . unwrap ( ) . create_messages {
return Err ( MeliError ::new ( format! (
" You are not allowed to create messages in mailbox {} " ,
mailbox . path ( )
) ) ) ;
}
2019-11-11 00:47:23 +02:00
2020-07-06 11:12:38 +03:00
mailbox . imap_path ( ) . to_string ( )
} ;
let mut response = String ::with_capacity ( 8 * 1024 ) ;
let mut conn = connection . lock ( ) . await ;
let flags = flags . unwrap_or_else ( Flag ::empty ) ;
conn . send_command (
format! (
" APPEND \" {} \" ({}) {{{}}} " ,
& path ,
flags_to_imap_list! ( flags ) ,
bytes . len ( )
)
. as_bytes ( ) ,
2019-06-28 19:34:40 +03:00
)
2020-07-06 11:12:38 +03:00
. await ? ;
// wait for "+ Ready for literal data" reply
conn . wait_for_continuation_request ( ) . await ? ;
conn . send_literal ( & bytes ) . await ? ;
conn . read_response ( & mut response , RequiredResponses ::empty ( ) )
. await ? ;
Ok ( ( ) )
} ) )
2018-07-13 18:38:57 +03:00
}
2019-08-26 20:04:02 +03:00
2020-06-30 11:40:26 +03:00
fn as_any ( & self ) -> & dyn ::std ::any ::Any {
2019-11-06 14:53:12 +02:00
self
}
2019-12-02 01:09:23 +02:00
2020-06-30 11:40:26 +03:00
fn as_any_mut ( & mut self ) -> & mut dyn ::std ::any ::Any {
2019-12-11 00:07:47 +02:00
self
}
2019-12-02 01:09:23 +02:00
fn tags ( & self ) -> Option < Arc < RwLock < BTreeMap < u64 , String > > > > {
if * self . can_create_flags . lock ( ) . unwrap ( ) {
2020-05-30 14:40:47 +03:00
Some ( self . uid_store . tag_index . clone ( ) )
2019-12-02 01:09:23 +02:00
} else {
None
}
}
2020-01-15 12:31:49 +02:00
2020-02-26 10:54:10 +02:00
fn create_mailbox (
2020-02-22 10:57:59 +02:00
& mut self ,
mut path : String ,
2020-06-30 11:40:26 +03:00
) -> ResultFuture < ( MailboxHash , HashMap < MailboxHash , Mailbox > ) > {
2020-07-06 11:12:38 +03:00
let uid_store = self . uid_store . clone ( ) ;
let connection = self . connection . clone ( ) ;
let new_mailbox_fut = self . mailboxes_async ( ) ;
Ok ( Box ::pin ( async move {
/* Must transform path to something the IMAP server will accept
*
* Each root mailbox has a hierarchy delimeter reported by the LIST entry . All paths
* must use this delimeter to indicate children of this mailbox .
*
* A new root mailbox should have the default delimeter , which can be found out by issuing
* an empty LIST command as described in RFC3501 :
* C : A101 LIST " " " "
* S : * LIST ( \ Noselect ) " / " " "
*
* The default delimiter for us is '/' just like UNIX paths . I apologise if this
* decision is unpleasant for you .
* /
2020-02-06 01:49:18 +02:00
2020-07-06 11:12:38 +03:00
{
let mailboxes = uid_store . mailboxes . lock ( ) . await ;
for root_mailbox in mailboxes . values ( ) . filter ( | f | f . parent . is_none ( ) ) {
if path . starts_with ( & root_mailbox . name ) {
debug! ( " path starts with {:?} " , & root_mailbox ) ;
path = path . replace (
'/' ,
( root_mailbox . separator as char ) . encode_utf8 ( & mut [ 0 ; 4 ] ) ,
) ;
break ;
}
}
2020-02-06 01:49:18 +02:00
2020-07-06 11:12:38 +03:00
if mailboxes . values ( ) . any ( | f | f . path = = path ) {
return Err ( MeliError ::new ( format! (
" Mailbox named `{}` already exists. " ,
path ,
) ) ) ;
}
}
2020-02-06 01:49:18 +02:00
2020-07-06 11:12:38 +03:00
let mut response = String ::with_capacity ( 8 * 1024 ) ;
{
let mut conn_lck = connection . lock ( ) . await ;
conn_lck
. send_command ( format! ( " CREATE \" {} \" " , path , ) . as_bytes ( ) )
. await ? ;
conn_lck
. read_response ( & mut response , RequiredResponses ::empty ( ) )
. await ? ;
conn_lck
. send_command ( format! ( " SUBSCRIBE \" {} \" " , path , ) . as_bytes ( ) )
. await ? ;
conn_lck
. read_response ( & mut response , RequiredResponses ::empty ( ) )
. await ? ;
}
let ret : Result < ( ) > = ImapResponse ::from ( & response ) . into ( ) ;
ret ? ;
let new_hash = get_path_hash! ( path . as_str ( ) ) ;
uid_store . mailboxes . lock ( ) . await . clear ( ) ;
Ok ( ( new_hash , new_mailbox_fut ? . await . map_err ( | err | MeliError ::new ( format! ( " Mailbox create was succesful (returned ` {} `) but listing mailboxes afterwards returned ` {} ` " , response , err ) ) ) ? ) )
} ) )
2020-02-06 01:49:18 +02:00
}
2020-02-26 10:54:10 +02:00
fn delete_mailbox (
& mut self ,
mailbox_hash : MailboxHash ,
2020-06-30 11:40:26 +03:00
) -> ResultFuture < HashMap < MailboxHash , Mailbox > > {
2020-07-06 11:12:38 +03:00
let uid_store = self . uid_store . clone ( ) ;
let connection = self . connection . clone ( ) ;
let new_mailbox_fut = self . mailboxes_async ( ) ;
Ok ( Box ::pin ( async move {
let imap_path : String ;
let no_select : bool ;
let is_subscribed : bool ;
2020-05-30 14:40:47 +03:00
{
2020-07-06 11:12:38 +03:00
let mailboxes = uid_store . mailboxes . lock ( ) . await ;
no_select = mailboxes [ & mailbox_hash ] . no_select ;
is_subscribed = mailboxes [ & mailbox_hash ] . is_subscribed ( ) ;
imap_path = mailboxes [ & mailbox_hash ] . imap_path ( ) . to_string ( ) ;
let permissions = mailboxes [ & mailbox_hash ] . permissions ( ) ;
if ! permissions . delete_mailbox {
return Err ( MeliError ::new ( format! ( " You do not have permission to delete ` {} `. Set permissions for this mailbox are {} " , mailboxes [ & mailbox_hash ] . name ( ) , permissions ) ) ) ;
}
2020-02-22 11:22:11 +02:00
}
2020-07-06 11:12:38 +03:00
let mut response = String ::with_capacity ( 8 * 1024 ) ;
{
let mut conn_lck = connection . lock ( ) . await ;
if ! no_select
& & ( conn_lck . current_mailbox = = MailboxSelection ::Examine ( mailbox_hash )
| | conn_lck . current_mailbox = = MailboxSelection ::Select ( mailbox_hash ) )
{
/* make sure mailbox is not selected before it gets deleted, otherwise
* connection gets dropped by server * /
conn_lck . unselect ( ) . await ? ;
}
if is_subscribed {
conn_lck
. send_command ( format! ( " UNSUBSCRIBE \" {} \" " , & imap_path ) . as_bytes ( ) )
. await ? ;
conn_lck
. read_response ( & mut response , RequiredResponses ::empty ( ) )
. await ? ;
}
2020-02-22 11:22:11 +02:00
2020-07-06 11:12:38 +03:00
conn_lck
. send_command ( debug! ( format! ( " DELETE \" {} \" " , & imap_path , ) ) . as_bytes ( ) )
. await ? ;
conn_lck
. read_response ( & mut response , RequiredResponses ::empty ( ) )
. await ? ;
}
let ret : Result < ( ) > = ImapResponse ::from ( & response ) . into ( ) ;
ret ? ;
uid_store . mailboxes . lock ( ) . await . clear ( ) ;
new_mailbox_fut ? . await . map_err ( | err | format! ( " Mailbox delete was succesful (returned ` {} `) but listing mailboxes afterwards returned ` {} ` " , response , err ) . into ( ) )
} ) )
2020-02-06 01:49:18 +02:00
}
2020-06-30 11:40:26 +03:00
fn set_mailbox_subscription (
& mut self ,
mailbox_hash : MailboxHash ,
new_val : bool ,
) -> ResultFuture < ( ) > {
2020-07-06 11:12:38 +03:00
let uid_store = self . uid_store . clone ( ) ;
let connection = self . connection . clone ( ) ;
Ok ( Box ::pin ( async move {
let command : String ;
{
let mailboxes = uid_store . mailboxes . lock ( ) . await ;
if mailboxes [ & mailbox_hash ] . is_subscribed ( ) = = new_val {
return Ok ( ( ) ) ;
}
command = format! ( " SUBSCRIBE \" {} \" " , mailboxes [ & mailbox_hash ] . imap_path ( ) ) ;
}
2020-02-06 01:49:18 +02:00
2020-07-06 11:12:38 +03:00
let mut response = String ::with_capacity ( 8 * 1024 ) ;
{
let mut conn_lck = connection . lock ( ) . await ;
if new_val {
conn_lck . send_command ( command . as_bytes ( ) ) . await ? ;
} else {
conn_lck
. send_command ( format! ( " UN {} " , command ) . as_bytes ( ) )
. await ? ;
}
conn_lck
. read_response ( & mut response , RequiredResponses ::empty ( ) )
. await ? ;
2020-02-06 01:49:18 +02:00
}
2020-07-06 11:12:38 +03:00
let ret : Result < ( ) > = ImapResponse ::from ( & response ) . into ( ) ;
if ret . is_ok ( ) {
uid_store
. mailboxes
. lock ( )
. await
. entry ( mailbox_hash )
. and_modify ( | entry | {
let _ = entry . set_is_subscribed ( new_val ) ;
} ) ;
}
ret
} ) )
2020-02-06 01:49:18 +02:00
}
2020-02-26 10:54:10 +02:00
fn rename_mailbox (
& mut self ,
mailbox_hash : MailboxHash ,
mut new_path : String ,
2020-06-30 11:40:26 +03:00
) -> ResultFuture < Mailbox > {
2020-07-06 11:12:38 +03:00
let uid_store = self . uid_store . clone ( ) ;
let connection = self . connection . clone ( ) ;
let new_mailbox_fut = self . mailboxes_async ( ) ;
Ok ( Box ::pin ( async move {
let command : String ;
let mut response = String ::with_capacity ( 8 * 1024 ) ;
{
let mailboxes = uid_store . mailboxes . lock ( ) . await ;
let permissions = mailboxes [ & mailbox_hash ] . permissions ( ) ;
if ! permissions . delete_mailbox {
return Err ( MeliError ::new ( format! ( " You do not have permission to rename mailbox ` {} ` (rename is equivalent to delete + create). Set permissions for this mailbox are {} " , mailboxes [ & mailbox_hash ] . name ( ) , permissions ) ) ) ;
}
if mailboxes [ & mailbox_hash ] . separator ! = b '/' {
new_path = new_path . replace (
'/' ,
( mailboxes [ & mailbox_hash ] . separator as char ) . encode_utf8 ( & mut [ 0 ; 4 ] ) ,
) ;
}
command = format! (
2020-02-06 01:49:18 +02:00
" RENAME \" {} \" \" {} \" " ,
2020-02-26 10:54:10 +02:00
mailboxes [ & mailbox_hash ] . imap_path ( ) ,
2020-02-06 01:49:18 +02:00
new_path
2020-07-06 11:12:38 +03:00
) ;
}
{
let mut conn_lck = connection . lock ( ) . await ;
conn_lck . send_command ( debug! ( command ) . as_bytes ( ) ) . await ? ;
conn_lck
. read_response ( & mut response , RequiredResponses ::empty ( ) )
. await ? ;
}
let new_hash = get_path_hash! ( new_path . as_str ( ) ) ;
let ret : Result < ( ) > = ImapResponse ::from ( & response ) . into ( ) ;
ret ? ;
uid_store . mailboxes . lock ( ) . await . clear ( ) ;
new_mailbox_fut ? . await . map_err ( | err | format! ( " Mailbox rename was succesful (returned ` {} `) but listing mailboxes afterwards returned ` {} ` " , response , err ) ) ? ;
Ok ( BackendMailbox ::clone (
& uid_store . mailboxes . lock ( ) . await [ & new_hash ] ,
) )
} ) )
2020-02-06 01:49:18 +02:00
}
2020-02-26 10:54:10 +02:00
fn set_mailbox_permissions (
2020-02-06 01:49:18 +02:00
& mut self ,
2020-02-26 10:54:10 +02:00
mailbox_hash : MailboxHash ,
2020-07-06 11:12:38 +03:00
_val : crate ::backends ::MailboxPermissions ,
2020-06-30 11:40:26 +03:00
) -> ResultFuture < ( ) > {
2020-07-06 11:12:38 +03:00
let uid_store = self . uid_store . clone ( ) ;
//let connection = self.connection.clone();
Ok ( Box ::pin ( async move {
let mailboxes = uid_store . mailboxes . lock ( ) . await ;
let permissions = mailboxes [ & mailbox_hash ] . permissions ( ) ;
if ! permissions . change_permissions {
return Err ( MeliError ::new ( format! ( " You do not have permission to change permissions for mailbox ` {} `. Set permissions for this mailbox are {} " , mailboxes [ & mailbox_hash ] . name ( ) , permissions ) ) ) ;
}
Err ( MeliError ::new ( " Unimplemented. " ) )
} ) )
2020-01-15 12:31:49 +02:00
}
2020-04-04 20:09:51 +03:00
fn search (
& self ,
query : crate ::search ::Query ,
mailbox_hash : Option < MailboxHash > ,
2020-06-30 11:40:26 +03:00
) -> ResultFuture < SmallVec < [ EnvelopeHash ; 512 ] > > {
2020-04-04 20:09:51 +03:00
if mailbox_hash . is_none ( ) {
return Err ( MeliError ::new (
" Cannot search without specifying mailbox on IMAP " ,
) ) ;
}
let mailbox_hash = mailbox_hash . unwrap ( ) ;
fn rec ( q : & crate ::search ::Query , s : & mut String ) {
use crate ::search ::{ escape_double_quote , Query ::* } ;
match q {
Subject ( t ) = > {
s . push_str ( " SUBJECT \" " ) ;
s . extend ( escape_double_quote ( t ) . chars ( ) ) ;
s . push_str ( " \" " ) ;
}
From ( t ) = > {
s . push_str ( " FROM \" " ) ;
s . extend ( escape_double_quote ( t ) . chars ( ) ) ;
s . push_str ( " \" " ) ;
}
To ( t ) = > {
s . push_str ( " TO \" " ) ;
s . extend ( escape_double_quote ( t ) . chars ( ) ) ;
s . push_str ( " \" " ) ;
}
Cc ( t ) = > {
s . push_str ( " CC \" " ) ;
s . extend ( escape_double_quote ( t ) . chars ( ) ) ;
s . push_str ( " \" " ) ;
}
Bcc ( t ) = > {
s . push_str ( " BCC \" " ) ;
s . extend ( escape_double_quote ( t ) . chars ( ) ) ;
s . push_str ( " \" " ) ;
}
AllText ( t ) = > {
s . push_str ( " TEXT \" " ) ;
s . extend ( escape_double_quote ( t ) . chars ( ) ) ;
s . push_str ( " \" " ) ;
}
Flags ( v ) = > {
for f in v {
match f . as_str ( ) {
" draft " = > {
s . push_str ( " DRAFT " ) ;
}
" deleted " = > {
s . push_str ( " DELETED " ) ;
}
" flagged " = > {
s . push_str ( " FLAGGED " ) ;
}
" recent " = > {
s . push_str ( " RECENT " ) ;
}
" seen " | " read " = > {
s . push_str ( " SEEN " ) ;
}
" unseen " | " unread " = > {
s . push_str ( " UNSEEN " ) ;
}
" answered " = > {
s . push_str ( " ANSWERED " ) ;
}
" unanswered " = > {
s . push_str ( " UNANSWERED " ) ;
}
keyword = > {
s . push_str ( " KEYWORD " ) ;
2020-07-06 11:12:38 +03:00
s . push_str ( keyword ) ;
2020-04-04 20:09:51 +03:00
s . push_str ( " " ) ;
}
}
}
}
And ( q1 , q2 ) = > {
rec ( q1 , s ) ;
s . push_str ( " " ) ;
rec ( q2 , s ) ;
}
Or ( q1 , q2 ) = > {
s . push_str ( " OR " ) ;
rec ( q1 , s ) ;
s . push_str ( " " ) ;
rec ( q2 , s ) ;
}
Not ( q ) = > {
s . push_str ( " NOT " ) ;
rec ( q , s ) ;
}
_ = > { }
}
}
let mut query_str = String ::new ( ) ;
rec ( & query , & mut query_str ) ;
2020-07-06 11:12:38 +03:00
let connection = self . connection . clone ( ) ;
let uid_store = self . uid_store . clone ( ) ;
2020-04-04 20:09:51 +03:00
2020-07-06 11:12:38 +03:00
Ok ( Box ::pin ( async move {
let mut response = String ::with_capacity ( 8 * 1024 ) ;
let mut conn = connection . lock ( ) . await ;
conn . examine_mailbox ( mailbox_hash , & mut response , false )
. await ? ;
conn . send_command ( format! ( " UID SEARCH CHARSET UTF-8 {} " , query_str ) . as_bytes ( ) )
. await ? ;
conn . read_response ( & mut response , RequiredResponses ::SEARCH )
. await ? ;
debug! ( & response ) ;
let mut lines = response . lines ( ) ;
for l in lines . by_ref ( ) {
if l . starts_with ( " * SEARCH " ) {
use std ::iter ::FromIterator ;
let uid_index = uid_store . uid_index . lock ( ) ? ;
return Ok ( SmallVec ::from_iter (
l [ " * SEARCH " . len ( ) .. ]
. trim ( )
. split_whitespace ( )
. map ( usize ::from_str )
. filter_map ( std ::result ::Result ::ok )
. filter_map ( | uid | uid_index . get ( & ( mailbox_hash , uid ) ) )
. copied ( ) ,
) ) ;
}
2020-04-04 20:09:51 +03:00
}
2020-07-06 11:12:38 +03:00
Err ( MeliError ::new ( response ) )
} ) )
2020-04-04 20:09:51 +03:00
}
2018-08-11 18:00:21 +03:00
}
2018-07-13 18:38:57 +03:00
impl ImapType {
2019-10-24 20:30:17 +03:00
pub fn new (
s : & AccountSettings ,
is_subscribed : Box < dyn Fn ( & str ) -> bool + Send + Sync > ,
2019-11-16 00:33:22 +02:00
) -> Result < Box < dyn MailBackend > > {
let server_hostname = get_conf_val! ( s [ " server_hostname " ] ) ? ;
let server_username = get_conf_val! ( s [ " server_username " ] ) ? ;
2020-03-12 09:45:18 +02:00
let server_password = if ! s . extra . contains_key ( " server_password_command " ) {
get_conf_val! ( s [ " server_password " ] ) ? . to_string ( )
} else {
2020-05-28 16:27:02 +03:00
let invocation = get_conf_val! ( s [ " server_password_command " ] ) ? ;
let output = std ::process ::Command ::new ( " sh " )
. args ( & [ " -c " , invocation ] )
2020-03-12 09:45:18 +02:00
. stdin ( std ::process ::Stdio ::piped ( ) )
. stdout ( std ::process ::Stdio ::piped ( ) )
. stderr ( std ::process ::Stdio ::piped ( ) )
. output ( ) ? ;
if ! output . status . success ( ) {
return Err ( MeliError ::new ( format! (
" ({}) server_password_command `{}` returned {}: {} " ,
s . name ,
get_conf_val! ( s [ " server_password_command " ] ) ? ,
output . status ,
String ::from_utf8_lossy ( & output . stderr )
) ) ) ;
}
std ::str ::from_utf8 ( & output . stdout ) ? . trim_end ( ) . to_string ( )
} ;
2019-11-16 00:33:22 +02:00
let server_port = get_conf_val! ( s [ " server_port " ] , 143 ) ? ;
2020-07-06 11:12:38 +03:00
let use_tls = get_conf_val! ( s [ " use_tls " ] , true ) ? ;
let use_starttls = use_tls & & get_conf_val! ( s [ " use_starttls " ] , ! ( server_port = = 993 ) ) ? ;
2019-08-24 15:12:44 +03:00
let danger_accept_invalid_certs : bool =
2019-11-16 00:33:22 +02:00
get_conf_val! ( s [ " danger_accept_invalid_certs " ] , false ) ? ;
2019-09-21 21:02:35 +03:00
let server_conf = ImapServerConf {
server_hostname : server_hostname . to_string ( ) ,
server_username : server_username . to_string ( ) ,
2020-03-12 09:45:18 +02:00
server_password ,
2019-09-21 21:02:35 +03:00
server_port ,
2020-07-06 11:12:38 +03:00
use_tls ,
2019-09-21 21:02:35 +03:00
use_starttls ,
2019-09-20 09:07:55 +03:00
danger_accept_invalid_certs ,
2020-02-28 15:47:07 +02:00
protocol : ImapProtocol ::IMAP ,
2019-09-21 21:02:35 +03:00
} ;
2020-05-10 22:05:04 +03:00
let account_hash = {
let mut hasher = DefaultHasher ::new ( ) ;
hasher . write ( s . name . as_bytes ( ) ) ;
hasher . finish ( )
} ;
let uid_store : Arc < UIDStore > = Arc ::new ( UIDStore {
account_hash ,
2020-06-07 14:00:13 +03:00
cache_headers : get_conf_val ! ( s [ " X_header_caching " ] , false ) ? ,
2020-05-10 22:05:04 +03:00
.. UIDStore ::default ( )
} ) ;
2020-05-30 14:40:47 +03:00
let connection = ImapConnection ::new_connection ( & server_conf , uid_store . clone ( ) ) ;
2019-06-28 19:34:40 +03:00
2019-11-16 00:33:22 +02:00
Ok ( Box ::new ( ImapType {
2019-06-28 19:34:40 +03:00
account_name : s . name ( ) . to_string ( ) ,
2019-09-21 21:02:35 +03:00
server_conf ,
2019-10-24 20:30:17 +03:00
is_subscribed : Arc ::new ( IsSubscribedFn ( is_subscribed ) ) ,
2019-09-21 21:02:35 +03:00
2019-12-02 01:09:23 +02:00
can_create_flags : Arc ::new ( Mutex ::new ( false ) ) ,
2020-07-06 11:12:38 +03:00
connection : Arc ::new ( FutureMutex ::new ( connection ) ) ,
2020-05-10 22:05:04 +03:00
uid_store ,
2019-11-16 00:33:22 +02:00
} ) )
2019-06-28 19:34:40 +03:00
}
pub fn shell ( & mut self ) {
2020-07-06 11:12:38 +03:00
unimplemented! ( )
/*
2020-05-30 14:40:47 +03:00
let mut conn = ImapConnection ::new_connection ( & self . server_conf , self . uid_store . clone ( ) ) ;
2020-02-22 11:22:11 +02:00
conn . connect ( ) . unwrap ( ) ;
2019-06-28 19:34:40 +03:00
let mut res = String ::with_capacity ( 8 * 1024 ) ;
2019-12-11 00:07:47 +02:00
conn . send_command ( b " NOOP " ) . unwrap ( ) ;
2020-05-30 14:40:47 +03:00
conn . read_response ( & mut res , RequiredResponses ::empty ( ) )
. unwrap ( ) ;
2019-06-28 19:34:40 +03:00
let mut input = String ::new ( ) ;
loop {
use std ::io ;
input . clear ( ) ;
match io ::stdin ( ) . read_line ( & mut input ) {
Ok ( _ ) = > {
2019-12-11 00:07:47 +02:00
if input . trim ( ) . eq_ignore_ascii_case ( " logout " ) {
break ;
}
2019-06-28 19:34:40 +03:00
conn . send_command ( input . as_bytes ( ) ) . unwrap ( ) ;
2019-09-14 15:57:28 +03:00
conn . read_lines ( & mut res , String ::new ( ) ) . unwrap ( ) ;
if input . trim ( ) = = " IDLE " {
let mut iter = ImapBlockingConnection ::from ( conn ) ;
while let Some ( line ) = iter . next ( ) {
debug! ( " out: {} " , unsafe { std ::str ::from_utf8_unchecked ( & line ) } ) ;
}
conn = iter . into_conn ( ) ;
}
2019-06-28 19:34:40 +03:00
debug! ( " out: {} " , & res ) ;
}
Err ( error ) = > debug! ( " error: {} " , error ) ,
}
}
2020-07-06 11:12:38 +03:00
* /
2018-07-13 18:38:57 +03:00
}
2019-06-28 19:34:40 +03:00
2020-07-06 11:12:38 +03:00
pub async fn imap_mailboxes (
connection : & Arc < FutureMutex < ImapConnection > > ,
2020-05-10 21:14:49 +03:00
) -> Result < HashMap < MailboxHash , ImapMailbox > > {
let mut mailboxes : HashMap < MailboxHash , ImapMailbox > = Default ::default ( ) ;
2019-06-28 19:34:40 +03:00
let mut res = String ::with_capacity ( 8 * 1024 ) ;
2020-07-06 11:12:38 +03:00
let mut conn = connection . lock ( ) . await ;
conn . send_command ( b " LIST \" \" \" * \" " ) . await ? ;
let _ = conn
. read_response ( & mut res , RequiredResponses ::LIST_REQUIRED )
. await ? ;
2019-06-28 19:34:40 +03:00
debug! ( " out: {} " , & res ) ;
2020-01-27 15:55:01 +02:00
let mut lines = res . lines ( ) ;
/* Remove "M__ OK .." line */
lines . next_back ( ) ;
for l in lines . map ( | l | l . trim ( ) ) {
2020-02-26 10:54:10 +02:00
if let Ok ( mut mailbox ) =
2020-06-06 19:38:20 +03:00
protocol_parser ::list_mailbox_result ( l . as_bytes ( ) ) . map ( | ( _ , v ) | v )
2019-06-28 19:34:40 +03:00
{
2020-02-26 10:54:10 +02:00
if let Some ( parent ) = mailbox . parent {
if mailboxes . contains_key ( & parent ) {
mailboxes
2019-06-28 19:34:40 +03:00
. entry ( parent )
2020-02-26 10:54:10 +02:00
. and_modify ( | e | e . children . push ( mailbox . hash ) ) ;
2019-06-28 19:34:40 +03:00
} else {
/* Insert dummy parent entry, populating only the children field. Later
* when we encounter the parent entry we will swap its children with
* dummy ' s * /
2020-02-26 10:54:10 +02:00
mailboxes . insert (
2019-06-28 19:34:40 +03:00
parent ,
2020-02-26 10:54:10 +02:00
ImapMailbox {
children : vec ! [ mailbox . hash ] ,
.. ImapMailbox ::default ( )
2019-06-28 19:34:40 +03:00
} ,
) ;
}
}
2020-02-26 10:54:10 +02:00
if mailboxes . contains_key ( & mailbox . hash ) {
let entry = mailboxes . entry ( mailbox . hash ) . or_default ( ) ;
std ::mem ::swap ( & mut entry . children , & mut mailbox . children ) ;
* entry = mailbox ;
2019-06-28 19:34:40 +03:00
} else {
2020-02-26 10:54:10 +02:00
mailboxes . insert ( mailbox . hash , mailbox ) ;
2019-06-28 19:34:40 +03:00
}
} else {
debug! ( " parse error for {:?} " , l ) ;
2019-12-18 15:44:44 +02:00
}
}
2020-03-12 09:47:39 +02:00
mailboxes . retain ( | _ , v | v . hash ! = 0 ) ;
2020-07-06 11:12:38 +03:00
conn . send_command ( b " LSUB \" \" \" * \" " ) . await ? ;
conn . read_response ( & mut res , RequiredResponses ::LSUB_REQUIRED )
. await ? ;
2020-01-27 15:55:01 +02:00
let mut lines = res . lines ( ) ;
2020-07-06 11:12:38 +03:00
debug! ( " out: {} " , & res ) ;
2020-01-27 15:55:01 +02:00
/* Remove "M__ OK .." line */
lines . next_back ( ) ;
for l in lines . map ( | l | l . trim ( ) ) {
2019-12-18 15:44:44 +02:00
if let Ok ( subscription ) =
2020-06-06 19:38:20 +03:00
protocol_parser ::list_mailbox_result ( l . as_bytes ( ) ) . map ( | ( _ , v ) | v )
2019-12-18 15:44:44 +02:00
{
2020-02-26 10:54:10 +02:00
if let Some ( f ) = mailboxes . get_mut ( & subscription . hash ( ) ) {
2019-12-18 15:44:44 +02:00
if subscription . no_select {
continue ;
}
f . is_subscribed = true ;
}
} else {
debug! ( " parse error for {:?} " , l ) ;
2019-06-28 19:34:40 +03:00
}
}
2020-02-26 10:54:10 +02:00
Ok ( debug! ( mailboxes ) )
2019-06-28 19:34:40 +03:00
}
2019-11-06 15:11:35 +02:00
2019-11-27 14:22:53 +02:00
pub fn validate_config ( s : & AccountSettings ) -> Result < ( ) > {
get_conf_val! ( s [ " server_hostname " ] ) ? ;
get_conf_val! ( s [ " server_username " ] ) ? ;
2020-03-12 09:45:18 +02:00
if ! s . extra . contains_key ( " server_password_command " ) {
get_conf_val! ( s [ " server_password " ] ) ? ;
} else if s . extra . contains_key ( " server_password " ) {
return Err ( MeliError ::new ( format! (
" Configuration error ({}): both server_password and server_password_command are set, cannot choose " ,
s . name . as_str ( ) ,
) ) ) ;
}
2020-07-06 11:12:38 +03:00
let server_port = get_conf_val! ( s [ " server_port " ] , 143 ) ? ;
let use_tls = get_conf_val! ( s [ " use_tls " ] , true ) ? ;
let use_starttls = get_conf_val! ( s [ " use_starttls " ] , ! ( server_port = = 993 ) ) ? ;
if ! use_tls & & use_starttls {
return Err ( MeliError ::new ( format! (
" Configuration error ({}): incompatible use_tls and use_starttls values: use_tls = false, use_starttls = true " ,
s . name . as_str ( ) ,
) ) ) ;
}
2019-11-27 14:22:53 +02:00
get_conf_val! ( s [ " danger_accept_invalid_certs " ] , false ) ? ;
2020-06-07 14:00:13 +03:00
get_conf_val! ( s [ " X_header_caching " ] , false ) ? ;
2019-11-27 14:22:53 +02:00
Ok ( ( ) )
}
2020-07-06 11:12:38 +03:00
pub fn capabilities ( & self ) -> Vec < String > {
self . uid_store
. capabilities
. lock ( )
. unwrap ( )
. iter ( )
. map ( | c | String ::from_utf8_lossy ( c ) . into ( ) )
. collect ::< Vec < String > > ( )
}
}
2020-07-17 23:30:02 +03:00
fn fetch_cached_envs (
2020-07-06 11:12:38 +03:00
mailbox_hash : MailboxHash ,
our_unseen : & mut BTreeSet < EnvelopeHash > ,
uid_store : & UIDStore ,
) -> Result < ( HashSet < EnvelopeHash > , Vec < Envelope > ) > {
if ! uid_store . cache_headers {
return Ok ( ( HashSet ::default ( ) , vec! [ ] ) ) ;
}
let uidvalidities = uid_store . uidvalidity . lock ( ) . unwrap ( ) ;
let v = if let Some ( v ) = uidvalidities . get ( & mailbox_hash ) {
v
} else {
return Ok ( ( HashSet ::default ( ) , vec! [ ] ) ) ;
} ;
let cached_envs : ( cache ::MaxUID , Vec < ( UID , Envelope ) > ) ;
cache ::save_envelopes ( uid_store . account_hash , mailbox_hash , * v , & [ ] )
. chain_err_summary ( | | " Could not save envelopes in cache in get() " ) ? ;
2020-07-17 23:30:02 +03:00
cached_envs = cache ::fetch_envelopes ( uid_store . account_hash , mailbox_hash , * v )
2020-07-06 11:12:38 +03:00
. chain_err_summary ( | | " Could not get envelopes in cache in get() " ) ? ;
let ( _max_uid , envelopes ) = debug! ( cached_envs ) ;
let ret = envelopes . iter ( ) . map ( | ( _ , env ) | env . hash ( ) ) . collect ( ) ;
let payload = if ! envelopes . is_empty ( ) {
let mut payload = vec! [ ] ;
for ( uid , env ) in envelopes {
if ! env . is_seen ( ) {
our_unseen . insert ( env . hash ( ) ) ;
}
uid_store
. hash_index
. lock ( )
. unwrap ( )
. insert ( env . hash ( ) , ( uid , mailbox_hash ) ) ;
uid_store
. uid_index
. lock ( )
. unwrap ( )
. insert ( ( mailbox_hash , uid ) , env . hash ( ) ) ;
payload . push ( env ) ;
}
debug! ( " sending cached payload for {} " , mailbox_hash ) ;
payload
} else {
vec! [ ]
} ;
Ok ( ( ret , payload ) )
}
2020-07-17 23:30:02 +03:00
async fn fetch_hlpr (
2020-07-06 11:12:38 +03:00
connection : & Arc < FutureMutex < ImapConnection > > ,
mailbox_hash : MailboxHash ,
cached_hash_set : & HashSet < EnvelopeHash > ,
can_create_flags : & Arc < Mutex < bool > > ,
our_unseen : & mut BTreeSet < EnvelopeHash > ,
valid_hash_set : & mut HashSet < EnvelopeHash > ,
uid_store : & UIDStore ,
max_uid : & mut Option < usize > ,
) -> Result < Vec < Envelope > > {
let ( permissions , mailbox_path , mailbox_exists , no_select , unseen ) = {
let f = & uid_store . mailboxes . lock ( ) . await [ & mailbox_hash ] ;
(
f . permissions . clone ( ) ,
f . imap_path ( ) . to_string ( ) ,
f . exists . clone ( ) ,
f . no_select ,
f . unseen . clone ( ) ,
)
} ;
if no_select {
* max_uid = Some ( 0 ) ;
return Ok ( Vec ::new ( ) ) ;
}
let mut conn = connection . lock ( ) . await ;
2020-07-17 23:30:02 +03:00
debug! ( " locked for fetch {} " , mailbox_path ) ;
2020-07-06 11:12:38 +03:00
let mut response = String ::with_capacity ( 8 * 1024 ) ;
let max_uid_left = if let Some ( max_uid ) = max_uid {
* max_uid
} else {
conn . create_uid_msn_cache ( mailbox_hash , 1 ) . await ? ;
/* first SELECT the mailbox to get READ / WRITE permissions (because EXAMINE only
* returns READ - ONLY for both cases ) * /
conn . select_mailbox ( mailbox_hash , & mut response , true )
. await
. chain_err_summary ( | | format! ( " Could not select mailbox {} " , mailbox_path ) ) ? ;
let mut examine_response =
protocol_parser ::select_response ( & response ) . chain_err_summary ( | | {
format! (
" Could not parse select response for mailbox {} " ,
mailbox_path
)
} ) ? ;
* can_create_flags . lock ( ) . unwrap ( ) = examine_response . can_create_flags ;
debug! (
" mailbox: {} examine_response: {:?} " ,
mailbox_path , examine_response
) ;
{
let mut uidvalidities = uid_store . uidvalidity . lock ( ) . unwrap ( ) ;
let v = uidvalidities
. entry ( mailbox_hash )
. or_insert ( examine_response . uidvalidity ) ;
if uid_store . cache_headers {
let _ = cache ::save_envelopes (
uid_store . account_hash ,
mailbox_hash ,
examine_response . uidvalidity ,
& [ ] ,
) ;
}
* v = examine_response . uidvalidity ;
let mut permissions = permissions . lock ( ) . unwrap ( ) ;
permissions . create_messages = ! examine_response . read_only ;
permissions . remove_messages = ! examine_response . read_only ;
permissions . set_flags = ! examine_response . read_only ;
permissions . rename_messages = ! examine_response . read_only ;
permissions . delete_messages = ! examine_response . read_only ;
permissions . delete_messages = ! examine_response . read_only ;
mailbox_exists
. lock ( )
. unwrap ( )
. set_not_yet_seen ( examine_response . exists ) ;
}
if examine_response . exists = = 0 {
if uid_store . cache_headers {
for & env_hash in cached_hash_set {
conn . add_refresh_event ( RefreshEvent {
account_hash : uid_store . account_hash ,
mailbox_hash ,
kind : RefreshEventKind ::Remove ( env_hash ) ,
} ) ;
}
let _ = cache ::save_envelopes (
uid_store . account_hash ,
mailbox_hash ,
examine_response . uidvalidity ,
& [ ] ,
) ;
}
* max_uid = Some ( 0 ) ;
return Ok ( Vec ::new ( ) ) ;
}
/* reselecting the same mailbox with EXAMINE prevents expunging it */
conn . examine_mailbox ( mailbox_hash , & mut response , true )
. await ? ;
if examine_response . uidnext = = 0 {
/* UIDNEXT shouldn't be 0, since exists != 0 at this point */
conn . send_command ( format! ( " STATUS \" {} \" (UIDNEXT) " , mailbox_path ) . as_bytes ( ) )
. await ? ;
conn . read_response ( & mut response , RequiredResponses ::STATUS )
. await ? ;
let ( _ , status ) = protocol_parser ::status_response ( response . as_bytes ( ) ) ? ;
if let Some ( uidnext ) = status . uidnext {
if uidnext = = 0 {
return Err ( MeliError ::new (
" IMAP server error: zero UIDNEXt with nonzero exists. " ,
) ) ;
}
examine_response . uidnext = uidnext ;
} else {
return Err ( MeliError ::new ( " IMAP server did not reply with UIDNEXT " ) ) ;
}
}
* max_uid = Some ( examine_response . uidnext - 1 ) ;
examine_response . uidnext - 1
} ;
let chunk_size = 600 ;
let mut payload = vec! [ ] ;
conn . examine_mailbox ( mailbox_hash , & mut response , false )
. await ? ;
if max_uid_left > 0 {
let mut envelopes = vec! [ ] ;
debug! ( " {} max_uid_left= {} " , mailbox_hash , max_uid_left ) ;
if max_uid_left = = 1 {
debug! ( " UID FETCH 1 (UID FLAGS ENVELOPE BODYSTRUCTURE) " ) ;
conn . send_command ( b " UID FETCH 1 (UID FLAGS ENVELOPE BODYSTRUCTURE) " )
. await ? ;
} else {
conn . send_command (
debug! ( format! (
" UID FETCH {}:{} (UID FLAGS ENVELOPE BODYSTRUCTURE) " ,
std ::cmp ::max ( std ::cmp ::max ( max_uid_left . saturating_sub ( chunk_size ) , 1 ) , 1 ) ,
max_uid_left
) )
. as_bytes ( ) ,
)
. await ?
} ;
conn . read_response ( & mut response , RequiredResponses ::FETCH_REQUIRED )
. await
. chain_err_summary ( | | {
format! (
" Could not parse fetch response for mailbox {} " ,
mailbox_path
)
} ) ? ;
debug! (
" fetch response is {} bytes and {} lines " ,
response . len ( ) ,
response . lines ( ) . count ( )
) ;
let ( _ , v , _ ) = protocol_parser ::uid_fetch_responses ( & response ) ? ;
debug! ( " responses len is {} " , v . len ( ) ) ;
for UidFetchResponse {
uid ,
message_sequence_number ,
flags ,
envelope ,
..
} in v
{
let mut env = envelope . unwrap ( ) ;
let mut h = DefaultHasher ::new ( ) ;
h . write_usize ( uid ) ;
h . write ( mailbox_path . as_bytes ( ) ) ;
env . set_hash ( h . finish ( ) ) ;
/*
debug! (
" env hash {} {} UID = {} MSN = {} " ,
env . hash ( ) ,
env . subject ( ) ,
uid ,
message_sequence_number
) ;
* /
valid_hash_set . insert ( env . hash ( ) ) ;
let mut tag_lck = uid_store . tag_index . write ( ) . unwrap ( ) ;
if let Some ( ( flags , keywords ) ) = flags {
if ! flags . intersects ( Flag ::SEEN ) {
our_unseen . insert ( env . hash ( ) ) ;
}
env . set_flags ( flags ) ;
for f in keywords {
let hash = tag_hash! ( f ) ;
if ! tag_lck . contains_key ( & hash ) {
tag_lck . insert ( hash , f ) ;
}
env . labels_mut ( ) . push ( hash ) ;
}
}
uid_store
. msn_index
. lock ( )
. unwrap ( )
. entry ( mailbox_hash )
. or_default ( )
. insert ( message_sequence_number - 1 , uid ) ;
uid_store
. hash_index
. lock ( )
. unwrap ( )
. insert ( env . hash ( ) , ( uid , mailbox_hash ) ) ;
uid_store
. uid_index
. lock ( )
. unwrap ( )
. insert ( ( mailbox_hash , uid ) , env . hash ( ) ) ;
envelopes . push ( ( uid , env ) ) ;
}
debug! ( " sending payload for {} " , mailbox_hash ) ;
if uid_store . cache_headers {
//FIXME
cache ::save_envelopes (
uid_store . account_hash ,
mailbox_hash ,
uid_store . uidvalidity . lock ( ) . unwrap ( ) [ & mailbox_hash ] ,
& envelopes
. iter ( )
. map ( | ( uid , env ) | ( * uid , env ) )
. collect ::< SmallVec < [ ( UID , & Envelope ) ; 1024 ] > > ( ) ,
)
. chain_err_summary ( | | {
format! (
" Could not save envelopes in cache for mailbox {} " ,
mailbox_path
)
} ) ? ;
}
for & env_hash in cached_hash_set . difference ( & valid_hash_set ) {
conn . add_refresh_event ( RefreshEvent {
account_hash : uid_store . account_hash ,
mailbox_hash ,
kind : RefreshEventKind ::Remove ( env_hash ) ,
} ) ;
}
unseen
. lock ( )
. unwrap ( )
. insert_set ( our_unseen . iter ( ) . cloned ( ) . collect ( ) ) ;
mailbox_exists
. lock ( )
. unwrap ( )
. insert_existing_set ( envelopes . iter ( ) . map ( | ( _ , env ) | env . hash ( ) ) . collect ::< _ > ( ) ) ;
drop ( conn ) ;
payload . extend ( envelopes . into_iter ( ) . map ( | ( _ , env ) | env ) ) ;
}
* max_uid = if max_uid_left < = 1 {
Some ( 0 )
} else {
Some ( std ::cmp ::max (
std ::cmp ::max ( max_uid_left . saturating_sub ( chunk_size ) , 1 ) ,
1 ,
) )
} ;
Ok ( payload )
2019-06-28 19:34:40 +03:00
}