diff --git a/melib/src/backends.rs b/melib/src/backends.rs index 8ea1fbf1..609b9a8d 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -33,25 +33,20 @@ macro_rules! tag_hash { #[cfg(feature = "imap_backend")] pub mod imap; -#[cfg(feature = "imap_backend")] -pub mod imap_async; -//#[cfg(feature = "imap_backend")] -//pub mod imap2; -#[cfg(feature = "maildir_backend")] -pub mod maildir; -#[cfg(feature = "mbox_backend")] -pub mod mbox; #[cfg(feature = "notmuch_backend")] pub mod notmuch; #[cfg(feature = "notmuch_backend")] pub use self::notmuch::NotmuchDb; #[cfg(feature = "jmap_backend")] pub mod jmap; -#[cfg(feature = "jmap_backend")] -pub use self::jmap::JmapType; - +#[cfg(feature = "maildir_backend")] +pub mod maildir; +#[cfg(feature = "mbox_backend")] +pub mod mbox; #[cfg(feature = "imap_backend")] pub use self::imap::ImapType; +#[cfg(feature = "jmap_backend")] +pub use self::jmap::JmapType; use crate::async_workers::*; use crate::conf::AccountSettings; use crate::error::{MeliError, Result}; @@ -145,15 +140,8 @@ impl Backends { b.register( "imap".to_string(), Backend { - create_fn: Box::new(|| Box::new(|f, i| ImapType::new(f, i))), - validate_conf_fn: Box::new(ImapType::validate_config), - }, - ); - b.register( - "imap_async".to_string(), - Backend { - create_fn: Box::new(|| Box::new(|f, i| imap_async::ImapType::new(f, i))), - validate_conf_fn: Box::new(imap_async::ImapType::validate_config), + create_fn: Box::new(|| Box::new(|f, i| imap::ImapType::new(f, i))), + validate_conf_fn: Box::new(imap::ImapType::validate_config), }, ); } diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 2d4a93e8..29507a83 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -36,7 +36,7 @@ mod cache; pub mod managesieve; mod untagged; -use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; +use crate::async_workers::{Async, WorkContext}; use crate::backends::{ RefreshEventKind::{self, *}, *, @@ -44,9 +44,13 @@ use crate::backends::{ use crate::conf::AccountSettings; use crate::email::*; use crate::error::{MeliError, Result, ResultIntoMeliError}; +use futures::lock::Mutex as FutureMutex; +use futures::stream::Stream; use std::collections::{hash_map::DefaultHasher, BTreeMap}; use std::collections::{BTreeSet, HashMap, HashSet}; +use std::future::Future; use std::hash::Hasher; +use std::pin::Pin; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; use std::time::Instant; @@ -70,6 +74,7 @@ pub struct ImapServerConf { pub server_password: String, pub server_port: u16, pub use_starttls: bool, + pub use_tls: bool, pub danger_accept_invalid_certs: bool, pub protocol: ImapProtocol, } @@ -123,6 +128,7 @@ macro_rules! get_conf_val { pub struct UIDStore { account_hash: AccountHash, cache_headers: bool, + capabilities: Arc>, uidvalidity: Arc>>, hash_index: Arc>>, uid_index: Arc>>, @@ -131,7 +137,7 @@ pub struct UIDStore { byte_cache: Arc>>, tag_index: Arc>>, - mailboxes: Arc>>, + mailboxes: Arc>>, is_online: Arc)>>, refresh_events: Arc>>, sender: Arc>>, @@ -142,12 +148,13 @@ impl Default for UIDStore { UIDStore { account_hash: 0, cache_headers: false, + capabilities: Default::default(), uidvalidity: Default::default(), hash_index: Default::default(), uid_index: Default::default(), msn_index: Default::default(), byte_cache: Default::default(), - mailboxes: Arc::new(RwLock::new(Default::default())), + mailboxes: Arc::new(FutureMutex::new(Default::default())), tag_index: Arc::new(RwLock::new(Default::default())), is_online: Arc::new(Mutex::new(( Instant::now(), @@ -163,486 +170,179 @@ impl Default for UIDStore { pub struct ImapType { account_name: String, is_subscribed: Arc, - connection: Arc>, + connection: Arc>, server_conf: ImapServerConf, uid_store: Arc, can_create_flags: Arc>, } -#[inline(always)] -pub(self) fn try_lock( - connection: &Arc>, - dur: Option, -) -> Result> { - let now = Instant::now(); - while Instant::now().duration_since(now) <= dur.unwrap_or(std::time::Duration::from_millis(100)) - { - if let Ok(guard) = connection.try_lock() { - return Ok(guard); - } - } - Err("Connection timeout".into()) -} - impl MailBackend for ImapType { fn is_async(&self) -> bool { - false + true } - fn is_remote(&self) -> bool { true } - - fn is_online(&self) -> Result<()> { + fn get_async( + &mut self, + mailbox: &Mailbox, + ) -> Result>> + Send + 'static>>> { + let uid_store = self.uid_store.clone(); + let can_create_flags = self.can_create_flags.clone(); + let mailbox_hash = mailbox.hash(); let connection = self.connection.clone(); - let _ = std::thread::Builder::new() - .name(format!("{} connecting", self.account_name.as_str(),)) - .spawn(move || { - if let Ok(mut g) = try_lock(&connection, None) { - let _ = g.connect(); - } - }); - try_lock( - &self.uid_store.is_online, - Some(std::time::Duration::from_millis(12)), - )? - .1 - .clone() - } - - fn get(&mut self, mailbox: &Mailbox) -> Result>>> { - let mut w = AsyncBuilder::new(); - let handle = { - let tx = w.tx(); - let uid_store = self.uid_store.clone(); - let can_create_flags = self.can_create_flags.clone(); - let mailbox_hash = mailbox.hash(); - let (permissions, mailbox_path, mailbox_exists, no_select, unseen) = { - let f = &self.uid_store.mailboxes.read().unwrap()[&mailbox_hash]; - ( - f.permissions.clone(), - f.imap_path().to_string(), - f.exists.clone(), - f.no_select, - f.unseen.clone(), - ) - }; - let connection = self.connection.clone(); - let closure = move |_work_context| { - if no_select { - tx.send(AsyncStatus::Payload(Ok(Vec::new()))).unwrap(); - tx.send(AsyncStatus::Finished).unwrap(); + let mut max_uid: Option = None; + let mut valid_hash_set: HashSet = HashSet::default(); + let mut our_unseen: BTreeSet = Default::default(); + Ok(Box::pin(async_stream::try_stream! { + let (cached_hash_set, cached_payload) = get_cached_envs(mailbox_hash, &mut our_unseen, &uid_store)?; + yield cached_payload; + loop { + let res = get_hlpr(&connection, mailbox_hash,&cached_hash_set, &can_create_flags, &mut our_unseen, &mut valid_hash_set, &uid_store, &mut max_uid).await?; + yield res; + if max_uid == Some(1) || max_uid == Some(0) { return; } - let _tx = tx.clone(); - if let Err(err) = (move || { - let tx = _tx; - let mut our_unseen: BTreeSet = Default::default(); - let mut valid_hash_set: HashSet = HashSet::default(); - let cached_hash_set: HashSet = - (|| -> Result> { - if !uid_store.cache_headers { - return Ok(HashSet::default()); - } - let uidvalidities = uid_store.uidvalidity.lock().unwrap(); + } + })) + } - let v = if let Some(v) = uidvalidities.get(&mailbox_hash) { - v - } else { - return Ok(HashSet::default()); - }; - 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()" - })?; - cached_envs = - cache::get_envelopes(uid_store.account_hash, mailbox_hash, *v) - .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(); - 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); + fn refresh_async( + &mut self, + mailbox_hash: MailboxHash, + sender: RefreshEventConsumer, + ) -> ResultFuture<()> { + let main_conn = self.connection.clone(); + *self.uid_store.sender.write().unwrap() = Some(sender); + let uid_store = self.uid_store.clone(); + 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; + watch::examine_updates(&inbox, &mut conn, &uid_store).await?; + Ok(()) + })) + } - unseen.lock().unwrap().insert_set(our_unseen.clone()); - tx.send(AsyncStatus::Payload(Ok(payload))).unwrap(); - } - Ok(ret) - })() - .unwrap_or_default(); - - let mut conn = connection.lock()?; - debug!("locked for get {}", mailbox_path); - let mut response = String::with_capacity(8 * 1024); - - /* 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) - .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, - &[], - ); - } - tx.send(AsyncStatus::Payload(Ok(Vec::new()))).unwrap(); - tx.send(AsyncStatus::Finished).unwrap(); - return Ok(()); - } - /* reselecting the same mailbox with EXAMINE prevents expunging it */ - conn.examine_mailbox(mailbox_hash, &mut response, true)?; - conn.create_uid_msn_cache(mailbox_hash, 1)?; - 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(), - )?; - conn.read_response(&mut response, RequiredResponses::STATUS)?; - 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")); - } - } - let mut max_uid_left: usize = examine_response.uidnext - 1; - - let mut tag_lck = uid_store.tag_index.write().unwrap(); - - while 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)")?; - } else { - conn.send_command( - debug!(format!( - "UID FETCH {}:{} (UID FLAGS ENVELOPE BODYSTRUCTURE)", - std::cmp::max( - std::cmp::max(max_uid_left.saturating_sub(500), 1), - 1 - ), - max_uid_left - )) - .as_bytes(), - )? - }; - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) - .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()); - valid_hash_set.insert(env.hash()); - 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)); - } - max_uid_left = - std::cmp::max(std::cmp::max(max_uid_left.saturating_sub(500), 1), 1); - debug!("sending payload for {}", mailbox_hash); - if uid_store.cache_headers { - cache::save_envelopes( - uid_store.account_hash, - mailbox_hash, - examine_response.uidvalidity, - &envelopes - .iter() - .map(|(uid, env)| (*uid, env)) - .collect::>(), - ) - .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), - }); - } - let progress = envelopes.len(); - unseen - .lock() - .unwrap() - .insert_set(our_unseen.iter().cloned().collect()); - mailbox_exists.lock().unwrap().insert_existing_set( - envelopes.iter().map(|(_, env)| env.hash()).collect::<_>(), - ); - tx.send(AsyncStatus::Payload(Ok(envelopes - .into_iter() - .map(|(_, env)| env) - .collect::>()))) - .unwrap(); - tx.send(AsyncStatus::ProgressReport(progress)).unwrap(); - if max_uid_left == 1 { - break; - } - } - drop(conn); - Ok(()) - })() { - debug!("sending error payload for {}: {:?}", mailbox_hash, &err); - tx.send(AsyncStatus::Payload(Err(err))).unwrap(); + fn mailboxes_async(&self) -> ResultFuture> { + 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()); } - tx.send(AsyncStatus::Finished).unwrap(); - }; - Box::new(closure) - }; - Ok(w.build(handle)) + } + 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::>(); + 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()) + })) + } + + 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(()) + })) + } + + fn get(&mut self, _mailbox: &Mailbox) -> Result>>> { + Err(MeliError::new("Unimplemented.")) } fn refresh( &mut self, - mailbox_hash: MailboxHash, - sender: RefreshEventConsumer, + _mailbox_hash: MailboxHash, + _sender: RefreshEventConsumer, ) -> Result> { - let inbox = self - .uid_store - .mailboxes - .read() - .unwrap() - .get(&mailbox_hash) - .map(std::clone::Clone::clone) - .unwrap(); - let main_conn = self.connection.clone(); - *self.uid_store.sender.write().unwrap() = Some(sender); - let uid_store = self.uid_store.clone(); - let account_name = self.account_name.clone(); - let w = AsyncBuilder::new(); - let closure = move |work_context: WorkContext| { - let thread = std::thread::current(); - let mut conn = match try_lock(&main_conn, Some(std::time::Duration::new(2, 0))) { - Ok(conn) => conn, - Err(err) => { - uid_store - .sender - .read() - .unwrap() - .as_ref() - .unwrap() - .send(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: RefreshEventKind::Failure(err), - }); - - return; - } - }; - work_context - .set_name - .send(( - thread.id(), - format!("refreshing {} imap connection", account_name.as_str(),), - )) - .unwrap(); - - work_context - .set_status - .send((thread.id(), "refresh".to_string())) - .unwrap(); - watch::examine_updates(&inbox, &mut conn, &uid_store, &work_context) - .ok() - .take(); - }; - Ok(w.build(Box::new(closure))) + Err(MeliError::new("Unimplemented.")) } fn watch( &self, - sender: RefreshEventConsumer, - work_context: WorkContext, + _sender: RefreshEventConsumer, + _work_context: WorkContext, ) -> Result { + Err(MeliError::new("Unimplemented.")) + } + + fn watch_async(&self, sender: RefreshEventConsumer) -> ResultFuture<()> { + debug!("watch_async called"); let conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone()); let main_conn = self.connection.clone(); *self.uid_store.sender.write().unwrap() = Some(sender); let uid_store = self.uid_store.clone(); - let handle = std::thread::Builder::new() - .name(format!("{} imap connection", self.account_name.as_str(),)) - .spawn(move || { - let thread = std::thread::current(); - work_context - .set_status - .send((thread.id(), "watching".to_string())) - .unwrap(); - let has_idle: bool = main_conn - .lock() - .unwrap() - .capabilities - .iter() - .any(|cap| cap.eq_ignore_ascii_case(b"IDLE")); - let kit = ImapWatchKit { - conn, - main_conn, - uid_store, - work_context, - }; - if has_idle { - idle(kit).ok().take(); - } else { - poll_with_examine(kit).ok().take(); - } - })?; - Ok(handle.thread().id()) + 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(()) + })) } fn mailboxes(&self) -> Result> { - { - let mailboxes = self.uid_store.mailboxes.read().unwrap(); - if !mailboxes.is_empty() { - return Ok(mailboxes - .iter() - .map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Mailbox)) - .collect()); - } - } - let new_mailboxes = ImapType::imap_mailboxes(&self.connection)?; - let mut mailboxes = self.uid_store.mailboxes.write()?; - *mailboxes = new_mailboxes; - mailboxes.retain(|_, f| (self.is_subscribed)(f.path())); - let keys = mailboxes.keys().cloned().collect::>(); - let mut uid_lock = self.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()) + Err(MeliError::new("Unimplemented.")) } fn operation(&self, hash: EnvelopeHash) -> Result> { @@ -669,38 +369,44 @@ impl MailBackend for ImapType { mailbox_hash: MailboxHash, flags: Option, ) -> ResultFuture<()> { - let path = { - let mailboxes = self.uid_store.mailboxes.read().unwrap(); + 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() - ))); - } + 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() + ))); + } - mailbox.imap_path().to_string() - }; - let mut response = String::with_capacity(8 * 1024); - let mut conn = try_lock(&self.connection, Some(std::time::Duration::new(5, 0)))?; - let flags = flags.unwrap_or_else(Flag::empty); - conn.send_command( - format!( - "APPEND \"{}\" ({}) {{{}}}", - &path, - flags_to_imap_list!(flags), - bytes.len() + 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(), ) - .as_bytes(), - )?; - // wait for "+ Ready for literal data" reply - conn.wait_for_continuation_request()?; - conn.send_literal(&bytes)?; - conn.read_response(&mut response, RequiredResponses::empty())?; - Ok(Box::pin(async { Ok(()) })) + .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(()) + })) } fn as_any(&self) -> &dyn ::std::any::Any { @@ -723,102 +429,123 @@ impl MailBackend for ImapType { &mut self, mut path: String, ) -> ResultFuture<(MailboxHash, HashMap)> { - /* 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. - */ + 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. + */ - let mut mailboxes = self.uid_store.mailboxes.write().unwrap(); - 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; + { + 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; + } + } + + if mailboxes.values().any(|f| f.path == path) { + return Err(MeliError::new(format!( + "Mailbox named `{}` already exists.", + path, + ))); + } } - } - if mailboxes.values().any(|f| f.path == path) { - return Err(MeliError::new(format!( - "Mailbox named `{}` in account `{}` already exists.", - path, self.account_name, - ))); - } + let mut response = String::with_capacity(8 * 1024); + { + let mut conn_lck = connection.lock().await; - let mut response = String::with_capacity(8 * 1024); - { - let mut conn_lck = try_lock(&self.connection, None)?; - - conn_lck.send_command(format!("CREATE \"{}\"", path,).as_bytes())?; - conn_lck.read_response(&mut response, RequiredResponses::empty())?; - conn_lck.send_command(format!("SUBSCRIBE \"{}\"", path,).as_bytes())?; - conn_lck.read_response(&mut response, RequiredResponses::empty())?; - } - let ret: Result<()> = ImapResponse::from(&response).into(); - ret?; - let new_hash = get_path_hash!(path.as_str()); - mailboxes.clear(); - drop(mailboxes); - let ret = - Ok((new_hash, self.mailboxes().map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err)))?)); - Ok(Box::pin(async { ret })) + 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)))?)) + })) } fn delete_mailbox( &mut self, mailbox_hash: MailboxHash, ) -> ResultFuture> { - let mailboxes = self.uid_store.mailboxes.read().unwrap(); - 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))); - } - let mut response = String::with_capacity(8 * 1024); - { - let mut conn_lck = try_lock(&self.connection, None)?; - if !mailboxes[&mailbox_hash].no_select - && (conn_lck.current_mailbox == MailboxSelection::Examine(mailbox_hash) - || conn_lck.current_mailbox == MailboxSelection::Select(mailbox_hash)) + 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; { - /* make sure mailbox is not selected before it gets deleted, otherwise - * connection gets dropped by server */ - conn_lck.unselect()?; + 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))); + } } - if mailboxes[&mailbox_hash].is_subscribed() { - conn_lck.send_command( - format!("UNSUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path()).as_bytes(), - )?; - conn_lck.read_response(&mut response, RequiredResponses::empty())?; + 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?; + } + + conn_lck + .send_command(debug!(format!("DELETE \"{}\"", &imap_path,)).as_bytes()) + .await?; + conn_lck + .read_response(&mut response, RequiredResponses::empty()) + .await?; } - - conn_lck.send_command( - debug!(format!( - "DELETE \"{}\"", - mailboxes[&mailbox_hash].imap_path() - )) - .as_bytes(), - )?; - conn_lck.read_response(&mut response, RequiredResponses::empty())?; - } - let ret: Result<()> = ImapResponse::from(&response).into(); - ret?; - let mut mailboxes = self.uid_store.mailboxes.write().unwrap(); - mailboxes.clear(); - drop(mailboxes); - let res = self.mailboxes().map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err).into()); - - Ok(Box::pin(async { res })) + 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()) + })) } fn set_mailbox_subscription( @@ -826,33 +553,46 @@ impl MailBackend for ImapType { mailbox_hash: MailboxHash, new_val: bool, ) -> ResultFuture<()> { - let mut mailboxes = self.uid_store.mailboxes.write().unwrap(); - if mailboxes[&mailbox_hash].is_subscribed() == new_val { - return Ok(Box::pin(async { Ok(()) })); - } - - let mut response = String::with_capacity(8 * 1024); - { - let mut conn_lck = try_lock(&self.connection, None)?; - if new_val { - conn_lck.send_command( - format!("SUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path()).as_bytes(), - )?; - } else { - conn_lck.send_command( - format!("UNSUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path()).as_bytes(), - )?; + 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()); } - conn_lck.read_response(&mut response, RequiredResponses::empty())?; - } - let ret: Result<()> = ImapResponse::from(&response).into(); - if ret.is_ok() { - mailboxes.entry(mailbox_hash).and_modify(|entry| { - let _ = entry.set_is_subscribed(new_val); - }); - } - Ok(Box::pin(async { ret })) + 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?; + } + + 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 + })) } fn rename_mailbox( @@ -860,54 +600,64 @@ impl MailBackend for ImapType { mailbox_hash: MailboxHash, mut new_path: String, ) -> ResultFuture { - let mut mailboxes = self.uid_store.mailboxes.write().unwrap(); - 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))); - } - let mut response = String::with_capacity(8 * 1024); - if mailboxes[&mailbox_hash].separator != b'/' { - new_path = new_path.replace( - '/', - (mailboxes[&mailbox_hash].separator as char).encode_utf8(&mut [0; 4]), - ); - } - { - let mut conn_lck = try_lock(&self.connection, None)?; - conn_lck.send_command( - debug!(format!( + 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!( "RENAME \"{}\" \"{}\"", mailboxes[&mailbox_hash].imap_path(), new_path - )) - .as_bytes(), - )?; - conn_lck.read_response(&mut response, RequiredResponses::empty())?; - } - let new_hash = get_path_hash!(new_path.as_str()); - let ret: Result<()> = ImapResponse::from(&response).into(); - ret?; - mailboxes.clear(); - drop(mailboxes); - self.mailboxes().map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?; - - let ret = Ok(BackendMailbox::clone( - &self.uid_store.mailboxes.read().unwrap()[&new_hash], - )); - Ok(Box::pin(async { ret })) + ); + } + { + 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], + )) + })) } fn set_mailbox_permissions( &mut self, mailbox_hash: MailboxHash, - _val: MailboxPermissions, + _val: crate::backends::MailboxPermissions, ) -> ResultFuture<()> { - let mailboxes = self.uid_store.mailboxes.write().unwrap(); - 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))); - } - Ok(Box::pin(async { Err(MeliError::new("Unimplemented.")) })) + 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.")) + })) } fn search( @@ -983,7 +733,7 @@ impl MailBackend for ImapType { } keyword => { s.push_str(" KEYWORD "); - s.extend(keyword.chars()); + s.push_str(keyword); s.push_str(" "); } } @@ -1009,32 +759,38 @@ impl MailBackend for ImapType { } let mut query_str = String::new(); rec(&query, &mut query_str); + let connection = self.connection.clone(); + let uid_store = self.uid_store.clone(); - let mut response = String::with_capacity(8 * 1024); - let mut conn = try_lock(&self.connection, Some(std::time::Duration::new(2, 0)))?; - conn.examine_mailbox(mailbox_hash, &mut response, false)?; - conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query_str).as_bytes())?; - conn.read_response(&mut response, RequiredResponses::SEARCH)?; - debug!(&response); + 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 = self.uid_store.uid_index.lock()?; - let ret = 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))) - .map(|env_hash_ref| *env_hash_ref), - )); - return Ok(Box::pin(async { ret })); + 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(), + )); + } } - } - Err(MeliError::new(response)) + Err(MeliError::new(response)) + })) } } @@ -1067,7 +823,8 @@ impl ImapType { std::str::from_utf8(&output.stdout)?.trim_end().to_string() }; let server_port = get_conf_val!(s["server_port"], 143)?; - let use_starttls = get_conf_val!(s["use_starttls"], !(server_port == 993))?; + let use_tls = get_conf_val!(s["use_tls"], true)?; + let use_starttls = use_tls && get_conf_val!(s["use_starttls"], !(server_port == 993))?; let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false)?; let server_conf = ImapServerConf { @@ -1075,6 +832,7 @@ impl ImapType { server_username: server_username.to_string(), server_password, server_port, + use_tls, use_starttls, danger_accept_invalid_certs, protocol: ImapProtocol::IMAP, @@ -1097,12 +855,14 @@ impl ImapType { is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)), can_create_flags: Arc::new(Mutex::new(false)), - connection: Arc::new(Mutex::new(connection)), + connection: Arc::new(FutureMutex::new(connection)), uid_store, })) } pub fn shell(&mut self) { + unimplemented!() + /* let mut conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone()); conn.connect().unwrap(); let mut res = String::with_capacity(8 * 1024); @@ -1134,16 +894,19 @@ impl ImapType { Err(error) => debug!("error: {}", error), } } + */ } - pub fn imap_mailboxes( - connection: &Arc>, + pub async fn imap_mailboxes( + connection: &Arc>, ) -> Result> { let mut mailboxes: HashMap = Default::default(); let mut res = String::with_capacity(8 * 1024); - let mut conn = try_lock(&connection, Some(std::time::Duration::new(2, 0)))?; - conn.send_command(b"LIST \"\" \"*\"")?; - conn.read_response(&mut res, RequiredResponses::LIST_REQUIRED)?; + let mut conn = connection.lock().await; + conn.send_command(b"LIST \"\" \"*\"").await?; + let _ = conn + .read_response(&mut res, RequiredResponses::LIST_REQUIRED) + .await?; debug!("out: {}", &res); let mut lines = res.lines(); /* Remove "M__ OK .." line */ @@ -1182,10 +945,11 @@ impl ImapType { } } mailboxes.retain(|_, v| v.hash != 0); - conn.send_command(b"LSUB \"\" \"*\"")?; - conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED)?; - debug!("out: {}", &res); + conn.send_command(b"LSUB \"\" \"*\"").await?; + conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED) + .await?; let mut lines = res.lines(); + debug!("out: {}", &res); /* Remove "M__ OK .." line */ lines.next_back(); for l in lines.map(|l| l.trim()) { @@ -1205,17 +969,6 @@ impl ImapType { Ok(debug!(mailboxes)) } - pub fn capabilities(&self) -> Vec { - try_lock(&self.connection, Some(std::time::Duration::new(2, 0))) - .map(|c| { - c.capabilities - .iter() - .map(|c| String::from_utf8_lossy(c).into()) - .collect::>() - }) - .unwrap_or_default() - } - pub fn validate_config(s: &AccountSettings) -> Result<()> { get_conf_val!(s["server_hostname"])?; get_conf_val!(s["server_username"])?; @@ -1227,10 +980,337 @@ impl ImapType { s.name.as_str(), ))); } - get_conf_val!(s["server_port"], 143)?; - get_conf_val!(s["use_starttls"], false)?; + 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(), + ))); + } get_conf_val!(s["danger_accept_invalid_certs"], false)?; get_conf_val!(s["X_header_caching"], false)?; Ok(()) } + + pub fn capabilities(&self) -> Vec { + self.uid_store + .capabilities + .lock() + .unwrap() + .iter() + .map(|c| String::from_utf8_lossy(c).into()) + .collect::>() + } +} + +fn get_cached_envs( + mailbox_hash: MailboxHash, + our_unseen: &mut BTreeSet, + uid_store: &UIDStore, +) -> Result<(HashSet, Vec)> { + 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()")?; + cached_envs = cache::get_envelopes(uid_store.account_hash, mailbox_hash, *v) + .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)) +} + +async fn get_hlpr( + connection: &Arc>, + mailbox_hash: MailboxHash, + cached_hash_set: &HashSet, + can_create_flags: &Arc>, + our_unseen: &mut BTreeSet, + valid_hash_set: &mut HashSet, + uid_store: &UIDStore, + max_uid: &mut Option, +) -> Result> { + 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; + debug!("locked for get {}", mailbox_path); + 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::>(), + ) + .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) } diff --git a/melib/src/backends/imap/connection.rs b/melib/src/backends/imap/connection.rs index e135fc75..fca5e9ee 100644 --- a/melib/src/backends/imap/connection.rs +++ b/melib/src/backends/imap/connection.rs @@ -21,15 +21,18 @@ use super::protocol_parser::{ImapLineSplit, ImapResponse, RequiredResponses}; use crate::backends::MailboxHash; +use crate::connections::Connection; use crate::email::parser::BytesExt; use crate::error::*; -use std::io::Read; -use std::io::Write; extern crate native_tls; +use futures::io::{AsyncReadExt, AsyncWriteExt}; use native_tls::TlsConnector; +pub use smol::Async as AsyncWrapper; use std::collections::HashSet; +use std::future::Future; use std::iter::FromIterator; use std::net::SocketAddr; +use std::pin::Pin; use std::sync::Arc; use std::time::Instant; @@ -45,7 +48,7 @@ pub enum ImapProtocol { #[derive(Debug)] pub struct ImapStream { cmd_id: usize, - stream: native_tls::TlsStream, + stream: AsyncWrapper, protocol: ImapProtocol, } @@ -61,117 +64,136 @@ impl MailboxSelection { std::mem::replace(self, MailboxSelection::None) } } + #[derive(Debug)] pub struct ImapConnection { pub stream: Result, pub server_conf: ImapServerConf, - pub capabilities: Capabilities, pub uid_store: Arc, pub current_mailbox: MailboxSelection, } impl Drop for ImapStream { fn drop(&mut self) { - self.send_command(b"LOGOUT").ok().take(); + //self.send_command(b"LOGOUT").ok().take(); } } impl ImapStream { - pub fn new_connection(server_conf: &ImapServerConf) -> Result<(Capabilities, ImapStream)> { - use std::io::prelude::*; + pub async fn new_connection( + server_conf: &ImapServerConf, + ) -> Result<(Capabilities, ImapStream)> { use std::net::TcpStream; let path = &server_conf.server_hostname; - let mut connector = TlsConnector::builder(); - if server_conf.danger_accept_invalid_certs { - connector.danger_accept_invalid_certs(true); - } - let connector = connector.build()?; - - let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) { - a - } else { - return Err(MeliError::new(format!( - "Could not lookup address {}", - &path - ))); - }; - - let mut socket = TcpStream::connect_timeout(&addr, std::time::Duration::new(4, 0))?; - socket.set_read_timeout(Some(std::time::Duration::new(4, 0)))?; - socket.set_write_timeout(Some(std::time::Duration::new(4, 0)))?; let cmd_id = 1; - if server_conf.use_starttls { - let mut buf = vec![0; 1024]; - match server_conf.protocol { - ImapProtocol::IMAP => { - socket.write_all(format!("M{} STARTTLS\r\n", cmd_id).as_bytes())? - } - ImapProtocol::ManageSieve => { - let len = socket.read(&mut buf)?; - debug!(unsafe { std::str::from_utf8_unchecked(&buf[0..len]) }); - debug!(socket.write_all(b"STARTTLS\r\n")?); - } + let stream = if server_conf.use_tls { + let mut connector = TlsConnector::builder(); + if server_conf.danger_accept_invalid_certs { + connector.danger_accept_invalid_certs(true); } - let mut response = String::with_capacity(1024); - let mut broken = false; - let now = std::time::Instant::now(); + let connector = connector.build()?; - while now.elapsed().as_secs() < 3 { - let len = socket.read(&mut buf)?; - response.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..len]) }); + let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) { + a + } else { + return Err(MeliError::new(format!( + "Could not lookup address {}", + &path + ))); + }; + + let mut socket = AsyncWrapper::new(Connection::Tcp(TcpStream::connect_timeout( + &addr, + std::time::Duration::new(4, 0), + )?))?; + if server_conf.use_starttls { + let mut buf = vec![0; 1024]; match server_conf.protocol { ImapProtocol::IMAP => { - if response.starts_with("* OK ") && response.find("\r\n").is_some() { - if let Some(pos) = response.as_bytes().find(b"\r\n") { - response.drain(0..pos + 2); + socket + .write_all(format!("M{} STARTTLS\r\n", cmd_id).as_bytes()) + .await? + } + ImapProtocol::ManageSieve => { + socket.read(&mut buf).await?; + socket.write_all(b"STARTTLS\r\n").await?; + } + } + let mut response = String::with_capacity(1024); + let mut broken = false; + let now = std::time::Instant::now(); + + while now.elapsed().as_secs() < 3 { + let len = socket.read(&mut buf).await?; + response.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..len]) }); + match server_conf.protocol { + ImapProtocol::IMAP => { + if response.starts_with("* OK ") && response.find("\r\n").is_some() { + if let Some(pos) = response.as_bytes().find(b"\r\n") { + response.drain(0..pos + 2); + } + } + } + ImapProtocol::ManageSieve => { + if response.starts_with("OK ") && response.find("\r\n").is_some() { + response.clear(); + broken = true; + break; } } } - ImapProtocol::ManageSieve => { - if response.starts_with("OK ") && response.find("\r\n").is_some() { - response.clear(); - broken = true; - break; - } + if response.starts_with("M1 OK") { + broken = true; + break; } } - if response.starts_with("M1 OK") { - broken = true; - break; + if !broken { + return Err(MeliError::new(format!( + "Could not initiate TLS negotiation to {}.", + path + ))); } } - if !broken { - return Err(MeliError::new(format!( - "Could not initiate TLS negotiation to {}.", - path - ))); - } - } - socket - .set_nonblocking(true) - .expect("set_nonblocking call failed"); - let stream = { - let mut conn_result = connector.connect(path, socket); - if let Err(native_tls::HandshakeError::WouldBlock(midhandshake_stream)) = conn_result { - let mut midhandshake_stream = Some(midhandshake_stream); - loop { - match midhandshake_stream.take().unwrap().handshake() { - Ok(r) => { - conn_result = Ok(r); - break; - } - Err(native_tls::HandshakeError::WouldBlock(stream)) => { - midhandshake_stream = Some(stream); - } - p => { - p?; + { + // FIXME: This is blocking + let socket = socket.into_inner()?; + let mut conn_result = connector.connect(path, socket); + if let Err(native_tls::HandshakeError::WouldBlock(midhandshake_stream)) = + conn_result + { + let mut midhandshake_stream = Some(midhandshake_stream); + loop { + match midhandshake_stream.take().unwrap().handshake() { + Ok(r) => { + conn_result = Ok(r); + break; + } + Err(native_tls::HandshakeError::WouldBlock(stream)) => { + midhandshake_stream = Some(stream); + } + p => { + p?; + } } } } + AsyncWrapper::new(Connection::Tls(conn_result?))? } - conn_result? + } else { + let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) { + a + } else { + return Err(MeliError::new(format!( + "Could not lookup address {}", + &path + ))); + }; + AsyncWrapper::new(Connection::Tcp(TcpStream::connect_timeout( + &addr, + std::time::Duration::new(4, 0), + )?))? }; let mut res = String::with_capacity(8 * 1024); let mut ret = ImapStream { @@ -181,7 +203,7 @@ impl ImapStream { }; if let ImapProtocol::ManageSieve = server_conf.protocol { use data_encoding::BASE64; - ret.read_response(&mut res)?; + ret.read_response(&mut res).await?; ret.send_command( format!( "AUTHENTICATE \"PLAIN\" \"{}\"", @@ -194,13 +216,14 @@ impl ImapStream { ) ) .as_bytes(), - )?; - ret.read_response(&mut res)?; + ) + .await?; + ret.read_response(&mut res).await?; return Ok((Default::default(), ret)); } - ret.send_command(b"CAPABILITY")?; - ret.read_response(&mut res)?; + ret.send_command(b"CAPABILITY").await?; + ret.read_response(&mut res).await?; let capabilities: std::result::Result, _> = res .split_rn() .find(|l| l.starts_with("* CAPABILITY")) @@ -244,11 +267,12 @@ impl ImapStream { &server_conf.server_username, &server_conf.server_password ) .as_bytes(), - )?; + ) + .await?; let tag_start = format!("M{} ", (ret.cmd_id - 1)); loop { - ret.read_lines(&mut res, &String::new(), false)?; + ret.read_lines(&mut res, &String::new(), false).await?; let mut should_break = false; for l in res.split_rn() { if l.starts_with("* CAPABILITY") { @@ -274,29 +298,31 @@ impl ImapStream { } } - if let Some(capabilities) = capabilities { - Ok((capabilities, ret)) - } else { + if capabilities.is_none() { /* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so check * for lazy servers */ drop(capabilities); - ret.send_command(b"CAPABILITY")?; - ret.read_response(&mut res).unwrap(); + ret.send_command(b"CAPABILITY").await?; + ret.read_response(&mut res).await.unwrap(); let capabilities = protocol_parser::capabilities(res.as_bytes())?.1; let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec())); Ok((capabilities, ret)) + } else { + let capabilities = capabilities.unwrap(); + Ok((capabilities, ret)) } } - pub fn read_response(&mut self, ret: &mut String) -> Result<()> { + pub async fn read_response(&mut self, ret: &mut String) -> Result<()> { let id = match self.protocol { ImapProtocol::IMAP => format!("M{} ", self.cmd_id - 1), ImapProtocol::ManageSieve => String::new(), }; - self.read_lines(ret, &id, true) + self.read_lines(ret, &id, true).await?; + Ok(()) } - pub fn read_lines( + pub async fn read_lines( &mut self, ret: &mut String, termination_string: &str, @@ -306,7 +332,7 @@ impl ImapStream { ret.clear(); let mut last_line_idx: usize = 0; loop { - match self.stream.read(&mut buf) { + match self.stream.read(&mut buf).await { Ok(0) => break, Ok(b) => { ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) }); @@ -317,10 +343,10 @@ impl ImapStream { if let Some(prev_line) = ret[last_line_idx..pos + last_line_idx].rfind("\r\n") { - last_line_idx += prev_line + 2; - pos -= prev_line + 2; + last_line_idx += prev_line + "\r\n".len(); + pos -= prev_line + "\r\n".len(); } - if pos + "\r\n".len() == ret[last_line_idx..].len() { + if Some(pos + "\r\n".len()) == ret.get(last_line_idx..).map(|r| r.len()) { if !termination_string.is_empty() && ret[last_line_idx..].starts_with(termination_string) { @@ -333,7 +359,7 @@ impl ImapStream { break; } } - last_line_idx += pos + 2; + last_line_idx += pos + "\r\n".len(); } } Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { @@ -344,29 +370,33 @@ impl ImapStream { } } } + //debug!("returning IMAP response:\n{:?}", &ret); Ok(()) } - pub fn wait_for_continuation_request(&mut self) -> Result<()> { + pub async fn wait_for_continuation_request(&mut self) -> Result<()> { let term = "+ ".to_string(); let mut ret = String::new(); - self.read_lines(&mut ret, &term, false) + self.read_lines(&mut ret, &term, false).await?; + Ok(()) } - pub fn send_command(&mut self, command: &[u8]) -> Result<()> { + pub async fn send_command(&mut self, command: &[u8]) -> Result<()> { let command = command.trim(); match self.protocol { ImapProtocol::IMAP => { - self.stream.write_all(b"M")?; - self.stream.write_all(self.cmd_id.to_string().as_bytes())?; - self.stream.write_all(b" ")?; + self.stream.write_all(b"M").await?; + self.stream + .write_all(self.cmd_id.to_string().as_bytes()) + .await?; + self.stream.write_all(b" ").await?; self.cmd_id += 1; } ImapProtocol::ManageSieve => {} } - self.stream.write_all(command)?; - self.stream.write_all(b"\r\n")?; + self.stream.write_all(command).await?; + self.stream.write_all(b"\r\n").await?; match self.protocol { ImapProtocol::IMAP => { debug!("sent: M{} {}", self.cmd_id - 1, unsafe { @@ -378,20 +408,15 @@ impl ImapStream { Ok(()) } - pub fn send_literal(&mut self, data: &[u8]) -> Result<()> { - self.stream.write_all(data)?; - self.stream.write_all(b"\r\n")?; + pub async fn send_literal(&mut self, data: &[u8]) -> Result<()> { + self.stream.write_all(data).await?; + self.stream.write_all(b"\r\n").await?; Ok(()) } - pub fn send_raw(&mut self, raw: &[u8]) -> Result<()> { - self.stream.write_all(raw)?; - self.stream.write_all(b"\r\n")?; - Ok(()) - } - - pub fn set_nonblocking(&mut self, val: bool) -> Result<()> { - self.stream.get_mut().set_nonblocking(val)?; + pub async fn send_raw(&mut self, raw: &[u8]) -> Result<()> { + self.stream.write_all(raw).await?; + self.stream.write_all(b"\r\n").await?; Ok(()) } } @@ -404,13 +429,12 @@ impl ImapConnection { ImapConnection { stream: Err(MeliError::new("Offline".to_string())), server_conf: server_conf.clone(), - capabilities: Capabilities::default(), uid_store, current_mailbox: MailboxSelection::None, } } - pub fn connect(&mut self) -> Result<()> { + pub async fn connect(&mut self) -> Result<()> { if let (instant, ref mut status @ Ok(())) = *self.uid_store.is_online.lock().unwrap() { if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) { *status = Err(MeliError::new("Connection timed out")); @@ -421,7 +445,7 @@ impl ImapConnection { self.uid_store.is_online.lock().unwrap().0 = Instant::now(); return Ok(()); } - let new_stream = ImapStream::new_connection(&self.server_conf); + let new_stream = debug!(ImapStream::new_connection(&self.server_conf).await); if new_stream.is_err() { *self.uid_store.is_online.lock().unwrap() = ( Instant::now(), @@ -432,117 +456,94 @@ impl ImapConnection { } let (capabilities, stream) = new_stream?; self.stream = Ok(stream); - self.capabilities = capabilities; + *self.uid_store.capabilities.lock().unwrap() = capabilities; Ok(()) } - pub fn read_response( - &mut self, - ret: &mut String, + pub fn read_response<'a>( + &'a mut self, + ret: &'a mut String, required_responses: RequiredResponses, - ) -> Result<()> { - let mut response = String::new(); - ret.clear(); - self.try_send(|s| s.read_response(&mut response))?; + ) -> Pin> + Send + 'a>> { + Box::pin(async move { + let mut response = String::new(); + ret.clear(); + self.stream.as_mut()?.read_response(&mut response).await?; - match self.server_conf.protocol { - ImapProtocol::IMAP => { - let r: ImapResponse = ImapResponse::from(&response); - match r { - ImapResponse::Bye(ref response_code) => { - self.stream = Err(MeliError::new(format!( - "Offline: received BYE: {:?}", - response_code - ))); - ret.push_str(&response); - } - ImapResponse::No(ref response_code) => { - debug!("Received NO response: {:?} {:?}", response_code, response); - ret.push_str(&response); - } - ImapResponse::Bad(ref response_code) => { - debug!("Received BAD response: {:?} {:?}", response_code, response); - ret.push_str(&response); - } - _ => { - /*debug!( - "check every line for required_responses: {:#?}", - &required_responses - );*/ - for l in response.split_rn() { - /*debug!("check line: {}", &l);*/ - if required_responses.check(l) || !self.process_untagged(l)? { - ret.push_str(l); + match self.server_conf.protocol { + ImapProtocol::IMAP => { + let r: ImapResponse = ImapResponse::from(&response); + match r { + ImapResponse::Bye(ref response_code) => { + self.stream = Err(MeliError::new(format!( + "Offline: received BYE: {:?}", + response_code + ))); + ret.push_str(&response); + } + ImapResponse::No(ref response_code) => { + debug!("Received NO response: {:?} {:?}", response_code, response); + ret.push_str(&response); + } + ImapResponse::Bad(ref response_code) => { + debug!("Received BAD response: {:?} {:?}", response_code, response); + ret.push_str(&response); + } + _ => { + /*debug!( + "check every line for required_responses: {:#?}", + &required_responses + );*/ + for l in response.split_rn() { + /*debug!("check line: {}", &l);*/ + if required_responses.check(l) || !self.process_untagged(l).await? { + ret.push_str(l); + } } } - //ret.push_str(&response); } + r.into() + } + ImapProtocol::ManageSieve => { + ret.push_str(&response); + Ok(()) } - r.into() } - ImapProtocol::ManageSieve => { - ret.push_str(&response); - Ok(()) - } - } + }) } - pub fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> { - self.try_send(|s| s.read_lines(ret, &termination_string, false)) + pub async fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> { + self.stream + .as_mut()? + .read_lines(ret, &termination_string, false) + .await?; + Ok(()) } - pub fn wait_for_continuation_request(&mut self) -> Result<()> { - self.try_send(|s| s.wait_for_continuation_request()) + pub async fn wait_for_continuation_request(&mut self) -> Result<()> { + self.stream + .as_mut()? + .wait_for_continuation_request() + .await?; + Ok(()) } - pub fn send_command(&mut self, command: &[u8]) -> Result<()> { - self.try_send(|s| s.send_command(command)) + pub async fn send_command(&mut self, command: &[u8]) -> Result<()> { + self.stream.as_mut()?.send_command(command).await?; + Ok(()) } - pub fn send_literal(&mut self, data: &[u8]) -> Result<()> { - self.try_send(|s| s.send_literal(data)) + pub async fn send_literal(&mut self, data: &[u8]) -> Result<()> { + self.stream.as_mut()?.send_literal(data).await?; + Ok(()) } - pub fn send_raw(&mut self, raw: &[u8]) -> Result<()> { - self.try_send(|s| s.send_raw(raw)) + pub async fn send_raw(&mut self, raw: &[u8]) -> Result<()> { + self.stream.as_mut()?.send_raw(raw).await?; + Ok(()) } - pub fn set_nonblocking(&mut self, val: bool) -> Result<()> { - self.try_send(|s| s.set_nonblocking(val)) - } - - pub fn try_send( - &mut self, - mut action: impl FnMut(&mut ImapStream) -> Result<()>, - ) -> Result<()> { - if let (instant, ref mut status @ Ok(())) = *self.uid_store.is_online.lock().unwrap() { - if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) { - *status = Err(MeliError::new("Connection timed out")); - self.stream = Err(MeliError::new("Connection timed out")); - } - } - if let Ok(ref mut stream) = self.stream { - if action(stream).is_ok() { - self.uid_store.is_online.lock().unwrap().0 = Instant::now(); - return Ok(()); - } - } - let new_stream = ImapStream::new_connection(&self.server_conf); - if new_stream.is_err() { - *self.uid_store.is_online.lock().unwrap() = ( - Instant::now(), - Err(new_stream.as_ref().unwrap_err().clone()), - ); - } else { - *self.uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(())); - } - let (capabilities, stream) = new_stream?; - self.stream = Ok(stream); - self.capabilities = capabilities; - Err(MeliError::new("Connection timed out")) - } - - pub fn select_mailbox( + pub async fn select_mailbox( &mut self, mailbox_hash: MailboxHash, ret: &mut String, @@ -554,17 +555,19 @@ impl ImapConnection { self.send_command( format!( "SELECT \"{}\"", - self.uid_store.mailboxes.read().unwrap()[&mailbox_hash].imap_path() + self.uid_store.mailboxes.lock().await[&mailbox_hash].imap_path() ) .as_bytes(), - )?; - self.read_response(ret, RequiredResponses::SELECT_REQUIRED)?; + ) + .await?; + self.read_response(ret, RequiredResponses::SELECT_REQUIRED) + .await?; debug!("select response {}", ret); self.current_mailbox = MailboxSelection::Select(mailbox_hash); Ok(()) } - pub fn examine_mailbox( + pub async fn examine_mailbox( &mut self, mailbox_hash: MailboxHash, ret: &mut String, @@ -576,37 +579,44 @@ impl ImapConnection { self.send_command( format!( "EXAMINE \"{}\"", - self.uid_store.mailboxes.read().unwrap()[&mailbox_hash].imap_path() + self.uid_store.mailboxes.lock().await[&mailbox_hash].imap_path() ) .as_bytes(), - )?; - self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED)?; + ) + .await?; + self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED) + .await?; debug!("examine response {}", ret); self.current_mailbox = MailboxSelection::Examine(mailbox_hash); Ok(()) } - pub fn unselect(&mut self) -> Result<()> { + pub async fn unselect(&mut self) -> Result<()> { match self.current_mailbox.take() { - MailboxSelection::Examine(mailbox_hash) | MailboxSelection::Select(mailbox_hash) => { - let mut response = String::with_capacity(8 * 1024); - if self - .capabilities - .iter() - .any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT")) - { - self.send_command(b"UNSELECT")?; - self.read_response(&mut response, RequiredResponses::empty())?; - } else { - /* `RFC3691 - UNSELECT Command` states: "[..] IMAP4 provides this - * functionality (via a SELECT command with a nonexistent mailbox name or - * reselecting the same mailbox with EXAMINE command)[..] - */ - - self.select_mailbox(mailbox_hash, &mut response, true)?; - self.examine_mailbox(mailbox_hash, &mut response, true)?; - } - }, + MailboxSelection::Examine(mailbox_hash) | + MailboxSelection::Select(mailbox_hash) =>{ + let mut response = String::with_capacity(8 * 1024); + if self + .uid_store + .capabilities + .lock() + .unwrap() + .iter() + .any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT")) + { + self.send_command(b"UNSELECT").await?; + self.read_response(&mut response, RequiredResponses::empty()) + .await?; + } else { + /* `RFC3691 - UNSELECT Command` states: "[..] IMAP4 provides this + * functionality (via a SELECT command with a nonexistent mailbox name or + * reselecting the same mailbox with EXAMINE command)[..] + */ + + self.select_mailbox(mailbox_hash, &mut response, true).await?; + self.examine_mailbox(mailbox_hash, &mut response, true).await?; + } + }, MailboxSelection::None => {}, } Ok(()) @@ -623,12 +633,19 @@ impl ImapConnection { } } - pub fn create_uid_msn_cache(&mut self, mailbox_hash: MailboxHash, low: usize) -> Result<()> { + pub async fn create_uid_msn_cache( + &mut self, + mailbox_hash: MailboxHash, + low: usize, + ) -> Result<()> { debug_assert!(low > 0); let mut response = String::new(); - self.examine_mailbox(mailbox_hash, &mut response, false)?; - self.send_command(format!("UID SEARCH {}:*", low).as_bytes())?; - self.read_response(&mut response, RequiredResponses::SEARCH)?; + self.examine_mailbox(mailbox_hash, &mut response, false) + .await?; + self.send_command(format!("UID SEARCH {}:*", low).as_bytes()) + .await?; + self.read_response(&mut response, RequiredResponses::SEARCH) + .await?; debug!("uid search response {:?}", &response); let mut msn_index_lck = self.uid_store.msn_index.lock().unwrap(); let msn_index = msn_index_lck.entry(mailbox_hash).or_default(); @@ -651,27 +668,7 @@ pub struct ImapBlockingConnection { } impl From for ImapBlockingConnection { - fn from(mut conn: ImapConnection) -> Self { - conn.set_nonblocking(false) - .expect("set_nonblocking call failed"); - conn.stream - .as_mut() - .map(|s| { - s.stream - .get_mut() - .set_write_timeout(Some(std::time::Duration::new(30, 0))) - .expect("set_write_timeout call failed") - }) - .expect("set_write_timeout call failed"); - conn.stream - .as_mut() - .map(|s| { - s.stream - .get_mut() - .set_read_timeout(Some(std::time::Duration::new(30, 0))) - .expect("set_read_timeout call failed") - }) - .expect("set_read_timeout call failed"); + fn from(conn: ImapConnection) -> Self { ImapBlockingConnection { buf: [0; 1024], conn, @@ -690,58 +687,22 @@ impl ImapBlockingConnection { pub fn err(&self) -> Option<&str> { self.err.as_ref().map(String::as_str) } -} -impl Iterator for ImapBlockingConnection { - type Item = Vec; - fn next(&mut self) -> Option { + pub fn as_stream<'a>(&'a mut self) -> impl Future>> + 'a { self.result.drain(0..self.prev_res_length); self.prev_res_length = 0; + let mut break_flag = false; let mut prev_failure = None; - let ImapBlockingConnection { - ref mut prev_res_length, - ref mut result, - ref mut conn, - ref mut buf, - ref mut err, - } = self; - loop { - if conn.stream.is_err() { - debug!(&conn.stream); + async move { + if self.conn.stream.is_err() { + debug!(&self.conn.stream); return None; } - match conn.stream.as_mut().unwrap().stream.read(buf) { - Ok(0) => return None, - Ok(b) => { - result.extend_from_slice(&buf[0..b]); - debug!(unsafe { std::str::from_utf8_unchecked(result) }); - if let Some(pos) = result.find(b"\r\n") { - *prev_res_length = pos + b"\r\n".len(); - return Some(result[0..*prev_res_length].to_vec()); - } - prev_failure = None; + loop { + if let Some(y) = read(self, &mut break_flag, &mut prev_failure).await { + return Some(y); } - Err(e) - if e.kind() == std::io::ErrorKind::WouldBlock - || e.kind() == std::io::ErrorKind::Interrupted => - { - debug!(&e); - if let Some(prev_failure) = prev_failure.as_ref() { - if Instant::now().duration_since(*prev_failure) - >= std::time::Duration::new(60 * 5, 0) - { - *err = Some(e.to_string()); - return None; - } - } else { - prev_failure = Some(Instant::now()); - } - continue; - } - Err(e) => { - debug!(&conn.stream); - debug!(&e); - *err = Some(e.to_string()); + if break_flag { return None; } } @@ -749,6 +710,58 @@ impl Iterator for ImapBlockingConnection { } } +async fn read( + conn: &mut ImapBlockingConnection, + break_flag: &mut bool, + prev_failure: &mut Option, +) -> Option> { + let ImapBlockingConnection { + ref mut prev_res_length, + ref mut result, + ref mut conn, + ref mut buf, + ref mut err, + } = conn; + + match conn.stream.as_mut().unwrap().stream.read(buf).await { + Ok(0) => { + *break_flag = true; + } + Ok(b) => { + result.extend_from_slice(&buf[0..b]); + debug!(unsafe { std::str::from_utf8_unchecked(result) }); + if let Some(pos) = result.find(b"\r\n") { + *prev_res_length = pos + b"\r\n".len(); + return Some(result[0..*prev_res_length].to_vec()); + } + *prev_failure = None; + } + Err(e) + if e.kind() == std::io::ErrorKind::WouldBlock + || e.kind() == std::io::ErrorKind::Interrupted => + { + debug!(&e); + if let Some(prev_failure) = prev_failure.as_ref() { + if Instant::now().duration_since(*prev_failure) + >= std::time::Duration::new(60 * 5, 0) + { + *err = Some(e.to_string()); + *break_flag = true; + } + } else { + *prev_failure = Some(Instant::now()); + } + } + Err(e) => { + debug!(&conn.stream); + debug!(&e); + *err = Some(e.to_string()); + *break_flag = true; + } + } + None +} + fn lookup_ipv4(host: &str, port: u16) -> Result { use std::net::ToSocketAddrs; diff --git a/melib/src/backends/imap/managesieve.rs b/melib/src/backends/imap/managesieve.rs index 470d98d9..18511bc5 100644 --- a/melib/src/backends/imap/managesieve.rs +++ b/melib/src/backends/imap/managesieve.rs @@ -97,6 +97,7 @@ pub fn new_managesieve_connection(s: &AccountSettings) -> Result server_password: server_password.to_string(), server_port, use_starttls: true, + use_tls: true, danger_accept_invalid_certs, protocol: ImapProtocol::ManageSieve, }; diff --git a/melib/src/backends/imap/operations.rs b/melib/src/backends/imap/operations.rs index 0e026d86..d30f2165 100644 --- a/melib/src/backends/imap/operations.rs +++ b/melib/src/backends/imap/operations.rs @@ -20,17 +20,18 @@ */ use super::*; + use crate::backends::*; use crate::email::*; use crate::error::{MeliError, Result}; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; /// `BackendOp` implementor for Imap #[derive(Debug, Clone)] pub struct ImapOp { uid: usize, mailbox_hash: MailboxHash, - connection: Arc>, + connection: Arc>, uid_store: Arc, } @@ -38,7 +39,7 @@ impl ImapOp { pub fn new( uid: usize, mailbox_hash: MailboxHash, - connection: Arc>, + connection: Arc>, uid_store: Arc, ) -> Self { ImapOp { @@ -52,44 +53,63 @@ impl ImapOp { impl BackendOp for ImapOp { fn as_bytes(&mut self) -> ResultFuture> { - let mut bytes_cache = self.uid_store.byte_cache.lock()?; - let cache = bytes_cache.entry(self.uid).or_default(); - if cache.bytes.is_none() { - let mut response = String::with_capacity(8 * 1024); - { - let mut conn = try_lock(&self.connection, Some(std::time::Duration::new(2, 0)))?; - conn.examine_mailbox(self.mailbox_hash, &mut response)?; - conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", self.uid).as_bytes())?; - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)?; + let connection = self.connection.clone(); + let mailbox_hash = self.mailbox_hash; + let uid = self.uid; + let uid_store = self.uid_store.clone(); + Ok(Box::pin(async move { + let exists_in_cache = { + let mut bytes_cache = uid_store.byte_cache.lock()?; + let cache = bytes_cache.entry(uid).or_default(); + cache.bytes.is_some() + }; + if !exists_in_cache { + 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 FETCH {} (FLAGS RFC822)", uid).as_bytes()) + .await?; + conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) + .await?; + } + debug!( + "fetch response is {} bytes and {} lines", + response.len(), + response.lines().collect::>().len() + ); + let UidFetchResponse { + uid: _uid, + flags: _flags, + body, + .. + } = protocol_parser::uid_fetch_response(&response)?.1; + assert_eq!(_uid, uid); + assert!(body.is_some()); + let mut bytes_cache = uid_store.byte_cache.lock()?; + let cache = bytes_cache.entry(uid).or_default(); + if let Some((_flags, _)) = _flags { + //flags.lock().await.set(Some(_flags)); + cache.flags = Some(_flags); + } + cache.bytes = + Some(unsafe { std::str::from_utf8_unchecked(body.unwrap()).to_string() }); } - debug!( - "fetch response is {} bytes and {} lines", - response.len(), - response.lines().count() - ); - let UidFetchResponse { - uid, flags, body, .. - } = protocol_parser::uid_fetch_response(&response)?.1; - assert_eq!(uid, self.uid); - assert!(body.is_some()); - let mut bytes_cache = self.uid_store.byte_cache.lock()?; - let cache = bytes_cache.entry(self.uid).or_default(); - if let Some((flags, _)) = flags { - cache.flags = Some(flags); - } - cache.bytes = Some(unsafe { std::str::from_utf8_unchecked(body.unwrap()).to_string() }); - } - let ret = cache.bytes.clone().unwrap().into_bytes(); - Ok(Box::pin(async move { Ok(ret) })) + let mut bytes_cache = uid_store.byte_cache.lock()?; + let cache = bytes_cache.entry(uid).or_default(); + let ret = cache.bytes.clone().unwrap().into_bytes(); + Ok(ret) + })) } fn fetch_flags(&self) -> ResultFuture { + let mut response = String::with_capacity(8 * 1024); let connection = self.connection.clone(); let mailbox_hash = self.mailbox_hash; let uid = self.uid; let uid_store = self.uid_store.clone(); - let mut response = String::with_capacity(8 * 1024); Ok(Box::pin(async move { let exists_in_cache = { let mut bytes_cache = uid_store.byte_cache.lock()?; @@ -97,14 +117,17 @@ impl BackendOp for ImapOp { cache.flags.is_some() }; if !exists_in_cache { - let mut conn = try_lock(&connection, Some(std::time::Duration::new(2, 0)))?; - conn.examine_mailbox(mailbox_hash, &mut response, false)?; - conn.send_command(format!("UID FETCH {} FLAGS", uid).as_bytes())?; - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)?; + let mut conn = connection.lock().await; + conn.examine_mailbox(mailbox_hash, &mut response, false) + .await?; + conn.send_command(format!("UID FETCH {} FLAGS", uid).as_bytes()) + .await?; + conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) + .await?; debug!( "fetch response is {} bytes and {} lines", response.len(), - response.lines().count() + response.lines().collect::>().len() ); let v = protocol_parser::uid_fetch_flags_response(response.as_bytes()) .map(|(_, v)| v) @@ -121,7 +144,7 @@ impl BackendOp for ImapOp { .set_summary(format!("message with UID {} was not found?", uid))); } let (_uid, (_flags, _)) = v[0]; - assert_eq!(_uid, uid); + assert_eq!(uid, uid); let mut bytes_cache = uid_store.byte_cache.lock()?; let cache = bytes_cache.entry(uid).or_default(); cache.flags = Some(_flags); @@ -139,21 +162,22 @@ impl BackendOp for ImapOp { fn set_flag( &mut self, - f: Flag, + flag: Flag, value: bool, ) -> Result> + Send>>> { let flags = self.fetch_flags()?; + + let mut response = String::with_capacity(8 * 1024); let connection = self.connection.clone(); let mailbox_hash = self.mailbox_hash; let uid = self.uid; let uid_store = self.uid_store.clone(); - - let mut response = String::with_capacity(8 * 1024); Ok(Box::pin(async move { let mut flags = flags.await?; - flags.set(f, value); - let mut conn = try_lock(&connection, Some(std::time::Duration::new(2, 0)))?; - conn.select_mailbox(mailbox_hash, &mut response, false)?; + flags.set(flag, value); + let mut conn = connection.lock().await; + conn.select_mailbox(mailbox_hash, &mut response, false) + .await?; debug!(&response); conn.send_command( format!( @@ -162,8 +186,10 @@ impl BackendOp for ImapOp { flags_to_imap_list!(flags) ) .as_bytes(), - )?; - conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)?; + ) + .await?; + conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED) + .await?; debug!(&response); match protocol_parser::uid_fetch_flags_response(response.as_bytes()) .map(|(_, v)| v) @@ -172,13 +198,11 @@ impl BackendOp for ImapOp { Ok(v) => { if v.len() == 1 { debug!("responses len is {}", v.len()); - let (uid, (_flags, _)) = v[0]; - assert_eq!(uid, uid); + let (_uid, (_flags, _)) = v[0]; + assert_eq!(_uid, uid); } } - Err(e) => { - return Err(e); - } + Err(e) => Err(e)?, } let mut bytes_cache = uid_store.byte_cache.lock()?; let cache = bytes_cache.entry(uid).or_default(); @@ -197,10 +221,10 @@ impl BackendOp for ImapOp { let mailbox_hash = self.mailbox_hash; let uid = self.uid; let uid_store = self.uid_store.clone(); - Ok(Box::pin(async move { - let mut conn = try_lock(&connection, Some(std::time::Duration::new(2, 0)))?; - conn.select_mailbox(mailbox_hash, &mut response, false)?; + let mut conn = connection.lock().await; + conn.select_mailbox(mailbox_hash, &mut response, false) + .await?; conn.send_command( format!( "UID STORE {} {}FLAGS.SILENT ({})", @@ -209,8 +233,10 @@ impl BackendOp for ImapOp { &tag ) .as_bytes(), - )?; - conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)?; + ) + .await?; + conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED) + .await?; protocol_parser::uid_fetch_flags_response(response.as_bytes()) .map(|(_, v)| v) .map_err(MeliError::from)?; diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index d9feaca7..7cf69398 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -126,6 +126,26 @@ impl RequiredResponses { } } +#[test] +fn test_imap_required_responses() { + let mut ret = String::new(); + let required_responses = RequiredResponses::FETCH_REQUIRED; + let response = + &"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n" + [0..]; + for l in response.split_rn() { + /*debug!("check line: {}", &l);*/ + if required_responses.check(l) { + ret.push_str(l); + } + } + assert_eq!(&ret, "* 1040 FETCH (UID 1064 FLAGS ())\r\n"); + let v = protocol_parser::uid_fetch_flags_response(response.as_bytes()) + .unwrap() + .1; + assert_eq!(v.len(), 1); +} + #[derive(Debug)] pub struct Alert(String); pub type ImapParseResult<'a, T> = Result<(&'a str, T, Option)>; @@ -171,18 +191,18 @@ impl std::fmt::Display for ResponseCode { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { use ResponseCode::*; match self { - Alert(s)=> write!(fmt, "ALERT: {}", s), - Badcharset(None)=> write!(fmt, "Given charset is not supported by this server."), - Badcharset(Some(s))=> write!(fmt, "Given charset is not supported by this server. Supported ones are: {}", s), - Capability => write!(fmt, "Capability response"), - Parse(s) => write!(fmt, "Server error in parsing message headers: {}", s), - Permanentflags(s) => write!(fmt, "Mailbox supports these flags: {}", s), - ReadOnly=> write!(fmt, "This mailbox is selected read-only."), -ReadWrite => write!(fmt, "This mailbox is selected with read-write permissions."), - Trycreate => write!(fmt, "Failed to operate on the target mailbox because it doesn't exist. Try creating it first."), - Uidnext(uid) => write!(fmt, "Next UID value is {}", uid), - Uidvalidity(uid) => write!(fmt, "Next UIDVALIDITY value is {}", uid), - Unseen(uid) => write!(fmt, "First message without the \\Seen flag is {}", uid), + Alert(s)=> write!(fmt, "ALERT: {}", s), + Badcharset(None)=> write!(fmt, "Given charset is not supported by this server."), + Badcharset(Some(s))=> write!(fmt, "Given charset is not supported by this server. Supported ones are: {}", s), + Capability => write!(fmt, "Capability response"), + Parse(s) => write!(fmt, "Server error in parsing message headers: {}", s), + Permanentflags(s) => write!(fmt, "Mailbox supports these flags: {}", s), + ReadOnly=> write!(fmt, "This mailbox is selected read-only."), + ReadWrite => write!(fmt, "This mailbox is selected with read-write permissions."), + Trycreate => write!(fmt, "Failed to operate on the target mailbox because it doesn't exist. Try creating it first."), + Uidnext(uid) => write!(fmt, "Next UID value is {}", uid), + Uidvalidity(uid) => write!(fmt, "Next UIDVALIDITY value is {}", uid), + Unseen(uid) => write!(fmt, "First message without the \\Seen flag is {}", uid), } } } @@ -190,7 +210,7 @@ ReadWrite => write!(fmt, "This mailbox is selected with read-write permissions." impl ResponseCode { fn from(val: &str) -> ResponseCode { use ResponseCode::*; - if !val.starts_with('[') { + if !val.starts_with("[") { let msg = val.trim(); return Alert(msg.to_string()); } @@ -235,8 +255,7 @@ pub enum ImapResponse { impl> From for ImapResponse { fn from(val: T) -> ImapResponse { - let val_ref = val.as_ref(); - let val: &str = val_ref.split_rn().last().unwrap_or(val_ref); + let val: &str = val.as_ref().split_rn().last().unwrap_or(val.as_ref()); debug!(&val); let mut val = val[val.as_bytes().find(b" ").unwrap() + 1..].trim(); // M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n @@ -617,7 +636,7 @@ pub fn uid_fetch_responses(mut input: &str) -> ImapParseResult { - alert.push_str(&el_alert.0); + alert.extend(el_alert.0.chars()); } a @ None => *a = Some(el_alert), } @@ -685,7 +704,7 @@ pub fn uid_fetch_response_( pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], Vec<(usize, (Flag, Vec))>> { many0(|input| -> IResult<&[u8], (usize, (Flag, Vec))> { let (input, _) = tag("* ")(input)?; - let (input, _) = take_while(is_digit)(input)?; + let (input, _msn) = take_while(is_digit)(input)?; let (input, _) = tag(" FETCH (")(input)?; let (input, uid_flags) = permutation(( preceded( @@ -696,7 +715,7 @@ pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], Vec<(usize, (Fla alt((tag("FLAGS "), tag(" FLAGS "))), delimited(tag("("), byte_flags, tag(")")), ), - ))(input.ltrim())?; + ))(input)?; let (input, _) = tag(")\r\n")(input)?; Ok((input, (uid_flags.0, uid_flags.1))) })(input) @@ -832,6 +851,7 @@ pub enum UntaggedResponse { } pub fn untagged_responses(input: &[u8]) -> IResult<&[u8], Option> { + debug!("Parse untagged response from {:?}", to_str!(input)); let (input, _) = tag("* ")(input)?; let (input, num) = map_res(digit1, |s| usize::from_str(to_str!(s)))(input)?; let (input, _) = tag(" ")(input)?; @@ -995,13 +1015,13 @@ pub fn flags(input: &str) -> IResult<&str, (Flag, Vec)> { let mut keywords = Vec::new(); let mut input = input; - while !input.starts_with(')') && !input.is_empty() { - if input.starts_with('\\') { + while !input.starts_with(")") && !input.is_empty() { + if input.starts_with("\\") { input = &input[1..]; } let mut match_end = 0; while match_end < input.len() { - if input[match_end..].starts_with(' ') || input[match_end..].starts_with(')') { + if input[match_end..].starts_with(" ") || input[match_end..].starts_with(")") { break; } match_end += 1; @@ -1344,7 +1364,7 @@ pub fn quoted(input: &[u8]) -> IResult<&[u8], Vec> { } pub fn quoted_or_nil(input: &[u8]) -> IResult<&[u8], Option>> { - alt((map(tag("NIL"), |_| None), map(quoted, Some)))(input.ltrim()) + alt((map(tag("NIL"), |_| None), map(quoted, |v| Some(v))))(input.ltrim()) /* alt_complete!(map!(ws!(tag!("NIL")), |_| None) | map!(quoted, |v| Some(v)))); */ diff --git a/melib/src/backends/imap/untagged.rs b/melib/src/backends/imap/untagged.rs index 3b251fad..3063e1e1 100644 --- a/melib/src/backends/imap/untagged.rs +++ b/melib/src/backends/imap/untagged.rs @@ -33,7 +33,7 @@ use crate::error::*; use std::time::Instant; impl ImapConnection { - pub fn process_untagged(&mut self, line: &str) -> Result { + pub async fn process_untagged(&mut self, line: &str) -> Result { macro_rules! try_fail { ($mailbox_hash: expr, $($result:expr)+) => { $(if let Err(err) = $result { @@ -51,12 +51,13 @@ impl ImapConnection { } else { Ok(()) }?;)+ }; } + //FIXME let mailbox_hash = match self.current_mailbox { MailboxSelection::Select(h) | MailboxSelection::Examine(h) => h, MailboxSelection::None => return Ok(false), }; let mailbox = - std::clone::Clone::clone(&self.uid_store.mailboxes.read().unwrap()[&mailbox_hash]); + std::clone::Clone::clone(&self.uid_store.mailboxes.lock().await[&mailbox_hash]); let mut response = String::with_capacity(8 * 1024); let untagged_response = @@ -102,20 +103,19 @@ impl ImapConnection { UntaggedResponse::Exists(n) => { /* UID FETCH ALL UID, cross-ref, then FETCH difference headers * */ - let mut prev_exists = mailbox.exists.lock().unwrap(); debug!("exists {}", n); - if n > prev_exists.len() { + if n > mailbox.exists.lock().unwrap().len() { try_fail!( mailbox_hash, self.send_command( &[ b"FETCH", - format!("{}:{}", prev_exists.len() + 1, n).as_bytes(), + format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(), b"(UID FLAGS RFC822)", ] .join(&b' '), - ) - self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) + ).await + self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await ); match super::protocol_parser::uid_fetch_responses(&response) { Ok((_, v, _)) => { @@ -165,7 +165,7 @@ impl ImapConnection { if !env.is_seen() { mailbox.unseen.lock().unwrap().insert_new(env.hash()); } - prev_exists.insert_new(env.hash()); + mailbox.exists.lock().unwrap().insert_new(env.hash()); self.add_refresh_event(RefreshEvent { account_hash: self.uid_store.account_hash, mailbox_hash, @@ -183,8 +183,8 @@ impl ImapConnection { UntaggedResponse::Recent(_) => { try_fail!( mailbox_hash, - self.send_command(b"UID SEARCH RECENT") - self.read_response(&mut response, RequiredResponses::SEARCH) + self.send_command(b"UID SEARCH RECENT").await + self.read_response(&mut response, RequiredResponses::SEARCH).await ); match super::protocol_parser::search_results_raw(response.as_bytes()) .map(|(_, v)| v) @@ -199,8 +199,8 @@ impl ImapConnection { self.send_command( &[b"UID FETCH", v, b"(FLAGS RFC822)"] .join(&b' '), - ) - self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) + ).await + self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await ); debug!(&response); match super::protocol_parser::uid_fetch_responses(&response) { @@ -292,8 +292,8 @@ impl ImapConnection { format!("{}", msg_seq).as_bytes(), ] .join(&b' '), - ) - self.read_response(&mut response, RequiredResponses::SEARCH) + ).await + self.read_response(&mut response, RequiredResponses::SEARCH).await ); debug!(&response); match super::protocol_parser::search_results( @@ -304,7 +304,7 @@ impl ImapConnection { Ok(mut v) => { if let Some(uid) = v.pop() { let lck = self.uid_store.uid_index.lock().unwrap(); - let env_hash = lck.get(&(mailbox_hash, uid)).map(|&h| h); + let env_hash = lck.get(&(mailbox_hash, uid)).copied(); drop(lck); if let Some(env_hash) = env_hash { if !flags.0.intersects(crate::email::Flag::SEEN) { diff --git a/melib/src/backends/imap/watch.rs b/melib/src/backends/imap/watch.rs index 6ee139b1..bdb7d3b5 100644 --- a/melib/src/backends/imap/watch.rs +++ b/melib/src/backends/imap/watch.rs @@ -22,26 +22,23 @@ use super::*; use crate::backends::SpecialUsageMailbox; use crate::email::parser::BytesExt; use crate::email::parser::BytesIterExt; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; /// Arguments for IMAP watching functions pub struct ImapWatchKit { pub conn: ImapConnection, - pub main_conn: Arc>, + pub main_conn: Arc>, pub uid_store: Arc, - pub work_context: WorkContext, } macro_rules! exit_on_error { - ($conn:expr, $mailbox_hash:ident, $work_context:ident, $thread_id:ident, $($result:expr)+) => { + ($conn:expr, $mailbox_hash:ident, $($result:expr)+) => { $(if let Err(e) = $result { *$conn.uid_store.is_online.lock().unwrap() = ( Instant::now(), Err(e.clone()), ); debug!("failure: {}", e.to_string()); - $work_context.set_status.send(($thread_id, e.to_string())).unwrap(); - $work_context.finished.send($thread_id).unwrap(); let account_hash = $conn.uid_store.account_hash; $conn.add_refresh_event(RefreshEvent { account_hash, @@ -53,50 +50,29 @@ macro_rules! exit_on_error { }; } -pub fn poll_with_examine(kit: ImapWatchKit) -> Result<()> { +pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> { debug!("poll with examine"); let ImapWatchKit { mut conn, main_conn, uid_store, - work_context, } = kit; - loop { - if super::try_lock(&uid_store.is_online, Some(std::time::Duration::new(10, 0)))? - .1 - .is_ok() - { - break; - } - std::thread::sleep(std::time::Duration::from_millis(100)); - } - conn.connect()?; + conn.connect().await?; let mut response = String::with_capacity(8 * 1024); - let thread_id: std::thread::ThreadId = std::thread::current().id(); loop { - work_context - .set_status - .send((thread_id, "sleeping...".to_string())) - .unwrap(); - std::thread::sleep(std::time::Duration::from_millis(5 * 60 * 1000)); - let mailboxes = uid_store.mailboxes.read()?; + let mailboxes = uid_store.mailboxes.lock().await; for mailbox in mailboxes.values() { - work_context - .set_status - .send(( - thread_id, - format!("examining `{}` for updates...", mailbox.path()), - )) - .unwrap(); - examine_updates(mailbox, &mut conn, &uid_store, &work_context)?; + examine_updates(mailbox, &mut conn, &uid_store).await?; } - let mut main_conn = super::try_lock(&main_conn, Some(std::time::Duration::new(10, 0)))?; - main_conn.send_command(b"NOOP")?; - main_conn.read_response(&mut response, RequiredResponses::empty())?; + let mut main_conn = main_conn.lock().await; + main_conn.send_command(b"NOOP").await?; + main_conn + .read_response(&mut response, RequiredResponses::empty()) + .await?; } } -pub fn idle(kit: ImapWatchKit) -> Result<()> { +pub async fn idle(kit: ImapWatchKit) -> Result<()> { debug!("IDLE"); /* IDLE only watches the connection's selected mailbox. We will IDLE on INBOX and every ~5 * minutes wake up and poll the others */ @@ -104,23 +80,12 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { mut conn, main_conn, uid_store, - work_context, } = kit; - loop { - if super::try_lock(&uid_store.is_online, Some(std::time::Duration::new(10, 0)))? - .1 - .is_ok() - { - break; - } - std::thread::sleep(std::time::Duration::from_millis(100)); - } - conn.connect()?; - let thread_id: std::thread::ThreadId = std::thread::current().id(); + conn.connect().await?; let mailbox: ImapMailbox = match uid_store .mailboxes - .read() - .unwrap() + .lock() + .await .values() .find(|f| f.parent.is_none() && (f.special_usage() == SpecialUsageMailbox::Inbox)) .map(std::clone::Clone::clone) @@ -129,10 +94,6 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { None => { let err = MeliError::new("INBOX mailbox not found in local mailbox index. meli may have not parsed the IMAP mailboxes correctly"); debug!("failure: {}", err.to_string()); - work_context - .set_status - .send((thread_id, err.to_string())) - .unwrap(); conn.add_refresh_event(RefreshEvent { account_hash: uid_store.account_hash, mailbox_hash: 0, @@ -147,10 +108,10 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, conn.send_command(format!("SELECT \"{}\"", mailbox.imap_path()).as_bytes()) + .await conn.read_response(&mut response, RequiredResponses::SELECT_REQUIRED) + .await ); debug!("select response {}", &response); { @@ -197,71 +158,44 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { } Err(e) => { debug!("{:?}", e); - panic!("could not select mailbox"); + return Err(e).chain_err_summary(|| "could not select mailbox"); } }; } - exit_on_error!( - conn, - mailbox_hash, - work_context, - thread_id, - conn.send_command(b"IDLE") - ); - work_context - .set_status - .send((thread_id, "IDLEing".to_string())) - .unwrap(); - let mut iter = ImapBlockingConnection::from(conn); + exit_on_error!(conn, mailbox_hash, conn.send_command(b"IDLE").await); + let mut blockn = ImapBlockingConnection::from(conn); let mut beat = std::time::Instant::now(); let mut watch = std::time::Instant::now(); /* duration interval to send heartbeat */ - let _26_mins = std::time::Duration::from_secs(26 * 60); + const _26_MINS: std::time::Duration = std::time::Duration::from_secs(26 * 60); /* duration interval to check other mailboxes for changes */ - let _5_mins = std::time::Duration::from_secs(5 * 60); - while let Some(line) = iter.next() { + const _5_MINS: std::time::Duration = std::time::Duration::from_secs(5 * 60); + while let Some(line) = blockn.as_stream().await { let now = std::time::Instant::now(); - if now.duration_since(beat) >= _26_mins { - let mut main_conn_lck = - super::try_lock(&main_conn, Some(std::time::Duration::new(10, 0)))?; + if now.duration_since(beat) >= _26_MINS { + let mut main_conn_lck = main_conn.lock().await; exit_on_error!( - iter.conn, + blockn.conn, mailbox_hash, - work_context, - thread_id, - iter.conn.set_nonblocking(true) - iter.conn.send_raw(b"DONE") - iter.conn.read_response(&mut response, RequiredResponses::empty()) - iter.conn.send_command(b"IDLE") - iter.conn.set_nonblocking(false) - main_conn_lck.send_command(b"NOOP") - main_conn_lck.read_response(&mut response, RequiredResponses::empty()) + blockn.conn.send_raw(b"DONE").await + blockn.conn.read_response(&mut response, RequiredResponses::empty()).await + blockn.conn.send_command(b"IDLE").await + main_conn_lck.send_command(b"NOOP").await + main_conn_lck.read_response(&mut response, RequiredResponses::empty()).await ); beat = now; } - if now.duration_since(watch) >= _5_mins { + if now.duration_since(watch) >= _5_MINS { /* Time to poll all inboxes */ - let mut conn = try_lock(&main_conn, Some(std::time::Duration::new(10, 0)))?; - for mailbox in uid_store.mailboxes.read().unwrap().values() { - work_context - .set_status - .send(( - thread_id, - format!("examining `{}` for updates...", mailbox.path()), - )) - .unwrap(); + let mut conn = main_conn.lock().await; + let mailboxes = uid_store.mailboxes.lock().await; + for mailbox in mailboxes.values() { exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, - examine_updates(mailbox, &mut conn, &uid_store, &work_context,) + examine_updates(mailbox, &mut conn, &uid_store).await ); } - work_context - .set_status - .send((thread_id, "done examining mailboxes.".to_string())) - .unwrap(); watch = now; } *uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(())); @@ -269,21 +203,15 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { .map(|(_, v)| v) .map_err(MeliError::from) { - Ok(Some(Recent(r))) => { - let mut conn = super::try_lock(&main_conn, Some(std::time::Duration::new(10, 0)))?; - work_context - .set_status - .send((thread_id, format!("got `{} RECENT` notification", r))) - .unwrap(); + Ok(Some(Recent(_r))) => { + let mut conn = main_conn.lock().await; /* UID SEARCH RECENT */ exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, - conn.examine_mailbox(mailbox_hash, &mut response, false) - conn.send_command(b"UID SEARCH RECENT") - conn.read_response(&mut response, RequiredResponses::SEARCH) + conn.examine_mailbox(mailbox_hash, &mut response, false).await + conn.send_command(b"UID SEARCH RECENT").await + conn.read_response(&mut response, RequiredResponses::SEARCH).await ); match protocol_parser::search_results_raw(response.as_bytes()) .map(|(_, v)| v) @@ -296,31 +224,19 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, conn.send_command( &[&b"UID FETCH"[..], &v.trim().split(|b| b == &b' ').join(b','), &b"(FLAGS RFC822)"[..]] .join(&b' '), - ) - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) + ).await + conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await ); debug!(&response); match protocol_parser::uid_fetch_responses(&response) { Ok((_, v, _)) => { - let len = v.len(); - let mut ctr = 0; for UidFetchResponse { uid, flags, body, .. } in v { - work_context - .set_status - .send(( - thread_id, - format!("parsing {}/{} envelopes..", ctr, len), - )) - .unwrap(); - ctr += 1; if !uid_store .uid_index .lock() @@ -328,6 +244,10 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { .contains_key(&(mailbox_hash, uid)) { if let Ok(mut env) = Envelope::from_bytes( + /* unwrap() is safe since we ask for RFC822 in the + * above FETCH, thus uid_fetch_responses() if + * returns a successful parse, it will include the + * RFC822 response */ body.unwrap(), flags.as_ref().map(|&(f, _)| f), ) { @@ -383,10 +303,6 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { } } } - work_context - .set_status - .send((thread_id, format!("parsed {}/{} envelopes.", ctr, len))) - .unwrap(); } Err(e) => { debug!(e); @@ -409,11 +325,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { // immediately decremented by 1, and this decrement is reflected in // message sequence numbers in subsequent responses (including other // untagged EXPUNGE responses). - let mut conn = super::try_lock(&main_conn, Some(std::time::Duration::new(10, 0)))?; - work_context - .set_status - .send((thread_id, format!("got `{} EXPUNGED` notification", n))) - .unwrap(); + let mut conn = main_conn.lock().await; let deleted_uid = uid_store .msn_index .lock() @@ -436,69 +348,43 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { }); } Ok(Some(Exists(n))) => { - let mut conn = super::try_lock(&main_conn, Some(std::time::Duration::new(10, 0)))?; + let mut conn = main_conn.lock().await; /* UID FETCH ALL UID, cross-ref, then FETCH difference headers * */ - let mut prev_exists = mailbox.exists.lock().unwrap(); debug!("exists {}", n); - work_context - .set_status - .send(( - thread_id, - format!( - "got `{} EXISTS` notification (EXISTS was previously {} for {}", - n, - prev_exists.len(), - mailbox.path() - ), - )) - .unwrap(); - if n > prev_exists.len() { + if n > mailbox.exists.lock().unwrap().len() { exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, - conn.examine_mailbox(mailbox_hash, &mut response, false) + conn.examine_mailbox(mailbox_hash, &mut response, false).await conn.send_command( &[ b"FETCH", - format!("{}:{}", prev_exists.len() + 1, n).as_bytes(), + format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(), b"(UID FLAGS RFC822)", ] .join(&b' '), - ) - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) + ).await + conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await ); match protocol_parser::uid_fetch_responses(&response) { Ok((_, v, _)) => { - let len = v.len(); - let mut ctr = 0; 'fetch_responses_b: for UidFetchResponse { uid, flags, body, .. } in v { - work_context - .set_status - .send(( - thread_id, - format!("parsing {}/{} envelopes..", ctr, len), - )) - .unwrap(); if uid_store .uid_index .lock() .unwrap() .contains_key(&(mailbox_hash, uid)) { - ctr += 1; continue 'fetch_responses_b; } if let Ok(mut env) = Envelope::from_bytes( body.unwrap(), flags.as_ref().map(|&(f, _)| f), ) { - ctr += 1; uid_store .hash_index .lock() @@ -536,7 +422,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { &[(uid, &env)], )?; } - prev_exists.insert_new(env.hash()); + mailbox.exists.lock().unwrap().insert_new(env.hash()); conn.add_refresh_event(RefreshEvent { account_hash: uid_store.account_hash, @@ -545,10 +431,6 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { }); } } - work_context - .set_status - .send((thread_id, format!("parsed {}/{} envelopes.", ctr, len))) - .unwrap(); } Err(e) => { debug!(e); @@ -560,22 +442,20 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { /* a * {msg_seq} FETCH (FLAGS ({flags})) was received, so find out UID from msg_seq * and send update */ - let mut conn = super::try_lock(&main_conn, Some(std::time::Duration::new(10, 0)))?; + let mut conn = main_conn.lock().await; debug!("fetch {} {:?}", msg_seq, flags); exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, - conn.examine_mailbox(mailbox_hash, &mut response, false) + conn.examine_mailbox(mailbox_hash, &mut response, false).await conn.send_command( &[ b"UID SEARCH ", format!("{}", msg_seq).as_bytes(), ] .join(&b' '), - ) - conn.read_response(&mut response, RequiredResponses::SEARCH) + ).await + conn.read_response(&mut response, RequiredResponses::SEARCH).await ); match search_results(response.split_rn().next().unwrap_or("").as_bytes()) .map(|(_, v)| v) @@ -610,19 +490,10 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { Ok(Some(Bye { .. })) => break, Ok(None) | Err(_) => {} } - work_context - .set_status - .send((thread_id, "IDLEing".to_string())) - .unwrap(); } debug!("IDLE connection dropped"); - let err: &str = iter.err().unwrap_or("Unknown reason."); - work_context - .set_status - .send((thread_id, "IDLE connection dropped".to_string())) - .unwrap(); - work_context.finished.send(thread_id).unwrap(); - main_conn.lock().unwrap().add_refresh_event(RefreshEvent { + let err: &str = blockn.err().unwrap_or("Unknown reason."); + main_conn.lock().await.add_refresh_event(RefreshEvent { account_hash: uid_store.account_hash, mailbox_hash, kind: RefreshEventKind::Failure(MeliError::new(format!( @@ -633,22 +504,19 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { Err(MeliError::new(format!("IDLE connection dropped: {}", err))) } -pub fn examine_updates( +pub async fn examine_updates( mailbox: &ImapMailbox, conn: &mut ImapConnection, uid_store: &Arc, - work_context: &WorkContext, ) -> Result<()> { - let thread_id: std::thread::ThreadId = std::thread::current().id(); let mailbox_hash = mailbox.hash(); debug!("examining mailbox {} {}", mailbox_hash, mailbox.path()); let mut response = String::with_capacity(8 * 1024); exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, conn.examine_mailbox(mailbox_hash, &mut response, true) + .await ); *uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(())); let uidvalidity; @@ -690,7 +558,6 @@ pub fn examine_updates( }); } } - let mut prev_exists = mailbox.exists.lock().unwrap(); let n = ok.exists; if ok.recent > 0 { { @@ -698,10 +565,8 @@ pub fn examine_updates( exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, - conn.send_command(b"UID SEARCH RECENT") - conn.read_response(&mut response, RequiredResponses::SEARCH) + conn.send_command(b"UID SEARCH RECENT").await + conn.read_response(&mut response, RequiredResponses::SEARCH).await ); match protocol_parser::search_results_raw(response.as_bytes()) .map(|(_, v)| v) @@ -714,13 +579,11 @@ pub fn examine_updates( exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, conn.send_command( &[&b"UID FETCH"[..], &v.trim().split(|b| b == &b' ').join(b','), &b"(FLAGS RFC822)"[..]] .join(&b' '), - ) - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) + ).await + conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await ); debug!(&response); match protocol_parser::uid_fetch_responses(&response) { @@ -786,6 +649,7 @@ pub fn examine_updates( &[(uid, &env)], )?; } + let mut prev_exists = mailbox.exists.lock().unwrap(); prev_exists.insert_new(env.hash()); conn.add_refresh_event(RefreshEvent { @@ -810,24 +674,22 @@ pub fn examine_updates( } } } - } else if n > prev_exists.len() { + } else if n > mailbox.exists.lock().unwrap().len() { /* UID FETCH ALL UID, cross-ref, then FETCH difference headers * */ debug!("exists {}", n); exit_on_error!( conn, mailbox_hash, - work_context, - thread_id, conn.send_command( &[ b"FETCH", - format!("{}:{}", prev_exists.len() + 1, n).as_bytes(), + format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(), b"(UID FLAGS RFC822)", ] .join(&b' '), - ) - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) + ).await + conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await ); match protocol_parser::uid_fetch_responses(&response) { Ok((_, v, _)) => { @@ -883,7 +745,7 @@ pub fn examine_updates( &[(uid, &env)], )?; } - prev_exists.insert_new(env.hash()); + mailbox.exists.lock().unwrap().insert_new(env.hash()); conn.add_refresh_event(RefreshEvent { account_hash: uid_store.account_hash, @@ -901,7 +763,7 @@ pub fn examine_updates( } Err(e) => { debug!("{:?}", e); - panic!("could not select mailbox"); + return Err(e).chain_err_summary(|| "could not select mailbox"); } }; Ok(()) diff --git a/melib/src/backends/imap_async.rs b/melib/src/backends/imap_async.rs deleted file mode 100644 index df4feb5c..00000000 --- a/melib/src/backends/imap_async.rs +++ /dev/null @@ -1,1303 +0,0 @@ -/* - * meli - imap module. - * - * Copyright 2019 Manos Pitsidianakis - * - * 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 . - */ - -use crate::get_path_hash; -use smallvec::SmallVec; -#[macro_use] -mod protocol_parser; -pub use protocol_parser::{UntaggedResponse::*, *}; -mod mailbox; -pub use mailbox::*; -mod operations; -pub use operations::*; -mod connection; -pub use connection::*; -mod watch; -pub use watch::*; -mod cache; -pub mod managesieve; -mod untagged; - -use crate::async_workers::{Async, WorkContext}; -use crate::backends::{ - RefreshEventKind::{self, *}, - *, -}; -use crate::conf::AccountSettings; -use crate::email::*; -use crate::error::{MeliError, Result, ResultIntoMeliError}; -use futures::lock::Mutex as FutureMutex; -use futures::stream::Stream; -use std::collections::{hash_map::DefaultHasher, BTreeMap}; -use std::collections::{BTreeSet, HashMap, HashSet}; -use std::future::Future; -use std::hash::Hasher; -use std::pin::Pin; -use std::str::FromStr; -use std::sync::{Arc, Mutex, RwLock}; -use std::time::Instant; -pub type UID = usize; - -pub static SUPPORTED_CAPABILITIES: &[&str] = - &["IDLE", "LOGIN", "LOGINDISABLED", "ENABLE", "IMAP4REV1"]; - -#[derive(Debug, Default)] -pub struct EnvelopeCache { - bytes: Option, - headers: Option, - body: Option, - flags: Option, -} - -#[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, - pub use_tls: bool, - pub danger_accept_invalid_certs: bool, - pub protocol: ImapProtocol, -} - -struct IsSubscribedFn(Box 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 bool + Send + Sync>; - fn deref(&self) -> &Box bool + Send + Sync> { - &self.0 - } -} -type Capabilities = HashSet>; - -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)) - }; -} - -#[derive(Debug)] -pub struct UIDStore { - account_hash: AccountHash, - cache_headers: bool, - uidvalidity: Arc>>, - hash_index: Arc>>, - uid_index: Arc>>, - msn_index: Arc>>>, - - byte_cache: Arc>>, - tag_index: Arc>>, - - mailboxes: Arc>>, - is_online: Arc)>>, - refresh_events: Arc>>, - sender: Arc>>, -} - -impl Default for UIDStore { - fn default() -> Self { - UIDStore { - account_hash: 0, - cache_headers: false, - uidvalidity: Default::default(), - hash_index: Default::default(), - uid_index: Default::default(), - msn_index: Default::default(), - byte_cache: Default::default(), - mailboxes: Arc::new(FutureMutex::new(Default::default())), - 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)), - } - } -} - -#[derive(Debug)] -pub struct ImapType { - account_name: String, - is_subscribed: Arc, - connection: Arc>, - server_conf: ImapServerConf, - uid_store: Arc, - can_create_flags: Arc>, -} - -impl MailBackend for ImapType { - fn is_async(&self) -> bool { - true - } - fn is_remote(&self) -> bool { - true - } - fn get_async( - &mut self, - mailbox: &Mailbox, - ) -> Result>> + Send + 'static>>> { - let uid_store = self.uid_store.clone(); - let can_create_flags = self.can_create_flags.clone(); - let mailbox_hash = mailbox.hash(); - let connection = self.connection.clone(); - let mut max_uid: Option = None; - let mut valid_hash_set: HashSet = HashSet::default(); - let mut our_unseen: BTreeSet = Default::default(); - Ok(Box::pin(async_stream::try_stream! { - let (cached_hash_set, cached_payload) = get_cached_envs(mailbox_hash, &mut our_unseen, &uid_store)?; - yield cached_payload; - loop { - let res = get_hlpr(&connection, mailbox_hash,&cached_hash_set, &can_create_flags, &mut our_unseen, &mut valid_hash_set, &uid_store, &mut max_uid).await?; - yield res; - if max_uid == Some(1) || max_uid == Some(0) { - return; - } - - } - })) - } - - fn refresh_async( - &mut self, - mailbox_hash: MailboxHash, - sender: RefreshEventConsumer, - ) -> ResultFuture<()> { - let main_conn = self.connection.clone(); - *self.uid_store.sender.write().unwrap() = Some(sender); - let uid_store = self.uid_store.clone(); - 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; - watch::examine_updates(&inbox, &mut conn, &uid_store).await?; - Ok(()) - })) - } - - fn mailboxes_async(&self) -> ResultFuture> { - 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()); - } - } - 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::>(); - 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()) - })) - } - - 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(()) - })) - } - - fn get(&mut self, _mailbox: &Mailbox) -> Result>>> { - Err(MeliError::new("Unimplemented.")) - } - - fn refresh( - &mut self, - _mailbox_hash: MailboxHash, - _sender: RefreshEventConsumer, - ) -> Result> { - Err(MeliError::new("Unimplemented.")) - } - - fn watch( - &self, - _sender: RefreshEventConsumer, - _work_context: WorkContext, - ) -> Result { - Err(MeliError::new("Unimplemented.")) - } - - fn watch_async(&self, sender: RefreshEventConsumer) -> ResultFuture<()> { - debug!("watch_async called"); - let conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone()); - let main_conn = self.connection.clone(); - *self.uid_store.sender.write().unwrap() = Some(sender); - let uid_store = self.uid_store.clone(); - Ok(Box::pin(async move { - let has_idle: bool = main_conn - .lock() - .await - .capabilities - .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(()) - })) - } - - fn mailboxes(&self) -> Result> { - Err(MeliError::new("Unimplemented.")) - } - - fn operation(&self, hash: EnvelopeHash) -> Result> { - 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( - uid, - mailbox_hash, - self.connection.clone(), - self.uid_store.clone(), - ))) - } - - fn save( - &self, - bytes: Vec, - mailbox_hash: MailboxHash, - flags: Option, - ) -> ResultFuture<()> { - 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() - ))); - } - - 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(), - ) - .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(()) - })) - } - - fn as_any(&self) -> &dyn ::std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { - self - } - - fn tags(&self) -> Option>>> { - if *self.can_create_flags.lock().unwrap() { - Some(self.uid_store.tag_index.clone()) - } else { - None - } - } - - fn create_mailbox( - &mut self, - mut path: String, - ) -> ResultFuture<(MailboxHash, HashMap)> { - 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. - */ - - { - 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; - } - } - - if mailboxes.values().any(|f| f.path == path) { - return Err(MeliError::new(format!( - "Mailbox named `{}` already exists.", - path, - ))); - } - } - - 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)))?)) - })) - } - - fn delete_mailbox( - &mut self, - mailbox_hash: MailboxHash, - ) -> ResultFuture> { - 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; - { - 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))); - } - } - 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?; - } - - 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()) - })) - } - - fn set_mailbox_subscription( - &mut self, - mailbox_hash: MailboxHash, - new_val: bool, - ) -> ResultFuture<()> { - 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()); - } - - 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?; - } - - 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 - })) - } - - fn rename_mailbox( - &mut self, - mailbox_hash: MailboxHash, - mut new_path: String, - ) -> ResultFuture { - 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!( - "RENAME \"{}\" \"{}\"", - mailboxes[&mailbox_hash].imap_path(), - new_path - ); - } - { - 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], - )) - })) - } - - fn set_mailbox_permissions( - &mut self, - mailbox_hash: MailboxHash, - _val: crate::backends::MailboxPermissions, - ) -> ResultFuture<()> { - 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.")) - })) - } - - fn search( - &self, - query: crate::search::Query, - mailbox_hash: Option, - ) -> ResultFuture> { - 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 "); - s.push_str(keyword); - 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); - let connection = self.connection.clone(); - let uid_store = self.uid_store.clone(); - - 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(), - )); - } - } - Err(MeliError::new(response)) - })) - } -} - -impl ImapType { - pub fn new( - s: &AccountSettings, - is_subscribed: Box bool + Send + Sync>, - ) -> Result> { - let server_hostname = get_conf_val!(s["server_hostname"])?; - let server_username = get_conf_val!(s["server_username"])?; - let server_password = if !s.extra.contains_key("server_password_command") { - get_conf_val!(s["server_password"])?.to_string() - } else { - let invocation = get_conf_val!(s["server_password_command"])?; - let output = std::process::Command::new("sh") - .args(&["-c", invocation]) - .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() - }; - let server_port = get_conf_val!(s["server_port"], 143)?; - let use_tls = get_conf_val!(s["use_tls"], true)?; - let use_starttls = use_tls && get_conf_val!(s["use_starttls"], !(server_port == 993))?; - let danger_accept_invalid_certs: bool = - get_conf_val!(s["danger_accept_invalid_certs"], false)?; - let server_conf = ImapServerConf { - server_hostname: server_hostname.to_string(), - server_username: server_username.to_string(), - server_password, - server_port, - use_tls, - use_starttls, - danger_accept_invalid_certs, - protocol: ImapProtocol::IMAP, - }; - let account_hash = { - let mut hasher = DefaultHasher::new(); - hasher.write(s.name.as_bytes()); - hasher.finish() - }; - let uid_store: Arc = Arc::new(UIDStore { - account_hash, - cache_headers: get_conf_val!(s["X_header_caching"], false)?, - ..UIDStore::default() - }); - let connection = ImapConnection::new_connection(&server_conf, uid_store.clone()); - - Ok(Box::new(ImapType { - account_name: s.name().to_string(), - server_conf, - is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)), - - can_create_flags: Arc::new(Mutex::new(false)), - connection: Arc::new(FutureMutex::new(connection)), - uid_store, - })) - } - - pub fn shell(&mut self) { - unimplemented!() - /* - let mut conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone()); - conn.connect().unwrap(); - let mut res = String::with_capacity(8 * 1024); - conn.send_command(b"NOOP").unwrap(); - conn.read_response(&mut res, RequiredResponses::empty()) - .unwrap(); - - let mut input = String::new(); - loop { - use std::io; - input.clear(); - - match io::stdin().read_line(&mut input) { - Ok(_) => { - if input.trim().eq_ignore_ascii_case("logout") { - break; - } - conn.send_command(input.as_bytes()).unwrap(); - 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(); - } - debug!("out: {}", &res); - } - Err(error) => debug!("error: {}", error), - } - } - */ - } - - pub async fn imap_mailboxes( - connection: &Arc>, - ) -> Result> { - let mut mailboxes: HashMap = Default::default(); - let mut res = String::with_capacity(8 * 1024); - let mut conn = connection.lock().await; - conn.send_command(b"LIST \"\" \"*\"").await?; - let _ = conn - .read_response(&mut res, RequiredResponses::LIST_REQUIRED) - .await?; - debug!("out: {}", &res); - let mut lines = res.lines(); - /* Remove "M__ OK .." line */ - lines.next_back(); - for l in lines.map(|l| l.trim()) { - if let Ok(mut mailbox) = - protocol_parser::list_mailbox_result(l.as_bytes()).map(|(_, v)| v) - { - if let Some(parent) = mailbox.parent { - if mailboxes.contains_key(&parent) { - mailboxes - .entry(parent) - .and_modify(|e| e.children.push(mailbox.hash)); - } 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 */ - mailboxes.insert( - parent, - ImapMailbox { - children: vec![mailbox.hash], - ..ImapMailbox::default() - }, - ); - } - } - 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; - } else { - mailboxes.insert(mailbox.hash, mailbox); - } - } else { - debug!("parse error for {:?}", l); - } - } - mailboxes.retain(|_, v| v.hash != 0); - conn.send_command(b"LSUB \"\" \"*\"").await?; - conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED) - .await?; - let mut lines = res.lines(); - debug!("out: {}", &res); - /* Remove "M__ OK .." line */ - lines.next_back(); - for l in lines.map(|l| l.trim()) { - if let Ok(subscription) = - protocol_parser::list_mailbox_result(l.as_bytes()).map(|(_, v)| v) - { - if let Some(f) = mailboxes.get_mut(&subscription.hash()) { - if subscription.no_select { - continue; - } - f.is_subscribed = true; - } - } else { - debug!("parse error for {:?}", l); - } - } - Ok(debug!(mailboxes)) - } - - pub fn validate_config(s: &AccountSettings) -> Result<()> { - get_conf_val!(s["server_hostname"])?; - get_conf_val!(s["server_username"])?; - 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(), - ))); - } - 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(), - ))); - } - get_conf_val!(s["danger_accept_invalid_certs"], false)?; - get_conf_val!(s["X_header_caching"], false)?; - Ok(()) - } -} - -fn get_cached_envs( - mailbox_hash: MailboxHash, - our_unseen: &mut BTreeSet, - uid_store: &UIDStore, -) -> Result<(HashSet, Vec)> { - 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()")?; - cached_envs = cache::get_envelopes(uid_store.account_hash, mailbox_hash, *v) - .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)) -} - -async fn get_hlpr( - connection: &Arc>, - mailbox_hash: MailboxHash, - cached_hash_set: &HashSet, - can_create_flags: &Arc>, - our_unseen: &mut BTreeSet, - valid_hash_set: &mut HashSet, - uid_store: &UIDStore, - max_uid: &mut Option, -) -> Result> { - 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; - debug!("locked for get {}", mailbox_path); - 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::>(), - ) - .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) -} diff --git a/melib/src/backends/imap_async/cache.rs b/melib/src/backends/imap_async/cache.rs deleted file mode 100644 index 6efff683..00000000 --- a/melib/src/backends/imap_async/cache.rs +++ /dev/null @@ -1,174 +0,0 @@ -/* - * meli - imap melib - * - * Copyright 2020 Manos Pitsidianakis - * - * 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 . - */ - -use super::UID; -use crate::{ - backends::{AccountHash, MailboxHash}, - email::Envelope, - error::*, -}; - -pub type MaxUID = UID; - -#[cfg(feature = "sqlite3")] -pub use sqlite3_m::*; - -#[cfg(feature = "sqlite3")] -mod sqlite3_m { - use super::*; - use crate::sqlite3; - const DB_NAME: &str = "header_cache.db"; - const INIT_SCRIPT: &str = "PRAGMA foreign_keys = true; - PRAGMA encoding = 'UTF-8'; - - CREATE TABLE IF NOT EXISTS envelopes ( - mailbox_hash INTEGER, - uid INTEGER, - validity INTEGER, - envelope BLOB NOT NULL UNIQUE, - PRIMARY KEY (mailbox_hash, uid, validity), - FOREIGN KEY (mailbox_hash, validity) REFERENCES uidvalidity(mailbox_hash, uid) ON DELETE CASCADE - ); - CREATE TABLE IF NOT EXISTS uidvalidity ( - uid INTEGER UNIQUE, - mailbox_hash INTEGER UNIQUE, - PRIMARY KEY (mailbox_hash, uid) - ); - CREATE INDEX IF NOT EXISTS envelope_idx ON envelopes(mailbox_hash, uid, validity); - CREATE INDEX IF NOT EXISTS uidvalidity_idx ON uidvalidity(mailbox_hash);"; - - pub fn get_envelopes( - account_hash: AccountHash, - mailbox_hash: MailboxHash, - uidvalidity: usize, - ) -> Result<(MaxUID, Vec<(UID, Envelope)>)> { - let conn = sqlite3::open_or_create_db( - &format!("{}_{}", account_hash, DB_NAME), - Some(INIT_SCRIPT), - )?; - let mut stmt = conn - .prepare("SELECT MAX(uid) FROM envelopes WHERE mailbox_hash = ? AND validity = ?") - .unwrap(); - let max_uid: usize = stmt - .query_map( - sqlite3::params![mailbox_hash as i64, uidvalidity as i64], - |row| row.get(0).map(|u: i64| u as usize), - ) - .chain_err_summary(|| { - format!( - "Error while performing query {:?}", - "SELECT MAX(uid) FROM envelopes WHERE mailbox_hash = ? AND validity = ?" - ) - })? - .next() - .unwrap() - .unwrap_or(0); - let mut stmt = conn - .prepare("SELECT uid, envelope FROM envelopes WHERE mailbox_hash = ? AND validity = ?") - .unwrap(); - let results: Vec<(UID, Vec)> = stmt - .query_map( - sqlite3::params![mailbox_hash as i64, uidvalidity as i64], - |row| Ok((row.get::<_, i64>(0)? as usize, row.get(1)?)), - ) - .chain_err_summary(|| { - format!( - "Error while performing query {:?}", - "SELECT uid, envelope FROM envelopes WHERE mailbox_hash = ? AND validity = ?", - ) - })? - .collect::>()?; - debug!( - "imap cache max_uid: {} results len: {}", - max_uid, - results.len() - ); - Ok(( - max_uid, - results - .into_iter() - .map(|(uid, env)| { - Ok(( - uid, - bincode::deserialize(&env).map_err(|e| MeliError::new(e.to_string()))?, - )) - }) - .collect::>>()?, - )) - } - - pub fn save_envelopes( - account_hash: AccountHash, - mailbox_hash: MailboxHash, - uidvalidity: usize, - envs: &[(UID, &Envelope)], - ) -> Result<()> { - let conn = - sqlite3::open_or_create_db(&format!("{}_{}", account_hash, DB_NAME), Some(INIT_SCRIPT)) - .chain_err_summary(|| { - format!( - "Could not create header_cache.db for account {}", - account_hash - ) - })?; - conn.execute( - "INSERT OR REPLACE INTO uidvalidity (uid, mailbox_hash) VALUES (?1, ?2)", - sqlite3::params![uidvalidity as i64, mailbox_hash as i64], - ) - .chain_err_summary(|| { - format!( - "Could not insert uidvalidity {} in header_cache of account {}", - uidvalidity, account_hash - ) - })?; - for (uid, env) in envs { - conn.execute( - "INSERT OR REPLACE INTO envelopes (uid, mailbox_hash, validity, envelope) VALUES (?1, ?2, ?3, ?4)", - sqlite3::params![*uid as i64, mailbox_hash as i64, uidvalidity as i64, bincode::serialize(env).map_err(|e| MeliError::new(e.to_string()))?], - ).chain_err_summary(|| format!("Could not insert envelope with hash {} in header_cache of account {}", env.hash(), account_hash))?; - } - Ok(()) - } -} - -#[cfg(not(feature = "sqlite3"))] -pub use filesystem_m::*; - -#[cfg(not(feature = "sqlite3"))] -mod filesystem_m { - use super::*; - pub fn get_envelopes( - _account_hash: AccountHash, - _mailbox_hash: MailboxHash, - _uidvalidity: usize, - ) -> Result<(MaxUID, Vec<(UID, Envelope)>)> { - Ok((0, vec![])) - } - - pub fn save_envelopes( - _account_hash: AccountHash, - _mailbox_hash: MailboxHash, - _uidvalidity: usize, - _envs: &[(UID, &Envelope)], - ) -> Result<()> { - Ok(()) - } -} diff --git a/melib/src/backends/imap_async/connection.rs b/melib/src/backends/imap_async/connection.rs deleted file mode 100644 index a1658635..00000000 --- a/melib/src/backends/imap_async/connection.rs +++ /dev/null @@ -1,777 +0,0 @@ -/* - * meli - imap module. - * - * Copyright 2017 - 2019 Manos Pitsidianakis - * - * 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 . - */ - -use super::protocol_parser::{ImapLineSplit, ImapResponse, RequiredResponses}; -use crate::backends::MailboxHash; -use crate::connections::Connection; -use crate::email::parser::BytesExt; -use crate::error::*; -extern crate native_tls; -use futures::io::{AsyncReadExt, AsyncWriteExt}; -use native_tls::TlsConnector; -pub use smol::Async as AsyncWrapper; -use std::collections::HashSet; -use std::future::Future; -use std::iter::FromIterator; -use std::net::SocketAddr; -use std::pin::Pin; -use std::sync::Arc; -use std::time::Instant; - -use super::protocol_parser; -use super::{Capabilities, ImapServerConf, UIDStore}; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ImapProtocol { - IMAP, - ManageSieve, -} - -#[derive(Debug)] -pub struct ImapStream { - cmd_id: usize, - stream: AsyncWrapper, - protocol: ImapProtocol, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum MailboxSelection { - None, - Select(MailboxHash), - Examine(MailboxHash), -} - -impl MailboxSelection { - pub fn take(&mut self) -> Self { - std::mem::replace(self, MailboxSelection::None) - } -} - -#[derive(Debug)] -pub struct ImapConnection { - pub stream: Result, - pub server_conf: ImapServerConf, - pub capabilities: Capabilities, - pub uid_store: Arc, - pub current_mailbox: MailboxSelection, -} - -impl Drop for ImapStream { - fn drop(&mut self) { - //self.send_command(b"LOGOUT").ok().take(); - } -} - -impl ImapStream { - pub async fn new_connection( - server_conf: &ImapServerConf, - ) -> Result<(Capabilities, ImapStream)> { - use std::net::TcpStream; - let path = &server_conf.server_hostname; - debug!("ImapStream::new_connection"); - - let cmd_id = 1; - let stream = if debug!(server_conf.use_tls) { - let mut connector = TlsConnector::builder(); - if server_conf.danger_accept_invalid_certs { - connector.danger_accept_invalid_certs(true); - } - let connector = connector.build()?; - - let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) { - a - } else { - return Err(MeliError::new(format!( - "Could not lookup address {}", - &path - ))); - }; - debug!("addr {:?}", addr); - - let mut socket = AsyncWrapper::new(Connection::Tcp(debug!( - TcpStream::connect_timeout(&addr, std::time::Duration::new(4, 0),) - )?))?; - if debug!(server_conf.use_starttls) { - let mut buf = vec![0; 1024]; - match server_conf.protocol { - ImapProtocol::IMAP => { - socket - .write_all(format!("M{} STARTTLS\r\n", cmd_id).as_bytes()) - .await? - } - ImapProtocol::ManageSieve => { - let len = socket.read(&mut buf).await?; - debug!(unsafe { std::str::from_utf8_unchecked(&buf[0..len]) }); - debug!(socket.write_all(b"STARTTLS\r\n").await?); - } - } - let mut response = String::with_capacity(1024); - let mut broken = false; - let now = std::time::Instant::now(); - - while now.elapsed().as_secs() < 3 { - let len = socket.read(&mut buf).await?; - response.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..len]) }); - match server_conf.protocol { - ImapProtocol::IMAP => { - if response.starts_with("* OK ") && response.find("\r\n").is_some() { - if let Some(pos) = response.as_bytes().find(b"\r\n") { - response.drain(0..pos + 2); - } - } - } - ImapProtocol::ManageSieve => { - if response.starts_with("OK ") && response.find("\r\n").is_some() { - response.clear(); - broken = true; - break; - } - } - } - if response.starts_with("M1 OK") { - broken = true; - break; - } - } - if !broken { - return Err(MeliError::new(format!( - "Could not initiate TLS negotiation to {}.", - path - ))); - } - } - - { - // FIXME: This is blocking - let socket = socket.into_inner()?; - let mut conn_result = connector.connect(path, socket); - if let Err(native_tls::HandshakeError::WouldBlock(midhandshake_stream)) = - conn_result - { - let mut midhandshake_stream = Some(midhandshake_stream); - loop { - match midhandshake_stream.take().unwrap().handshake() { - Ok(r) => { - conn_result = Ok(r); - break; - } - Err(native_tls::HandshakeError::WouldBlock(stream)) => { - midhandshake_stream = Some(stream); - } - p => { - p?; - } - } - } - } - AsyncWrapper::new(Connection::Tls(conn_result?))? - } - } else { - let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) { - a - } else { - return Err(MeliError::new(format!( - "Could not lookup address {}", - &path - ))); - }; - AsyncWrapper::new(Connection::Tcp(TcpStream::connect_timeout( - &addr, - std::time::Duration::new(4, 0), - )?))? - }; - let mut res = String::with_capacity(8 * 1024); - let mut ret = ImapStream { - cmd_id, - stream, - protocol: server_conf.protocol, - }; - if let ImapProtocol::ManageSieve = server_conf.protocol { - use data_encoding::BASE64; - ret.read_response(&mut res).await?; - ret.send_command( - format!( - "AUTHENTICATE \"PLAIN\" \"{}\"", - BASE64.encode( - format!( - "\0{}\0{}", - &server_conf.server_username, &server_conf.server_password - ) - .as_bytes() - ) - ) - .as_bytes(), - ) - .await?; - ret.read_response(&mut res).await?; - return Ok((Default::default(), ret)); - } - - ret.send_command(b"CAPABILITY").await?; - ret.read_response(&mut res).await?; - let capabilities: std::result::Result, _> = res - .split_rn() - .find(|l| l.starts_with("* CAPABILITY")) - .ok_or_else(|| MeliError::new("")) - .and_then(|res| { - protocol_parser::capabilities(res.as_bytes()) - .map_err(|_| MeliError::new("")) - .map(|(_, v)| v) - }); - - if capabilities.is_err() { - return Err(MeliError::new(format!( - "Could not connect to {}: expected CAPABILITY response but got:{}", - &server_conf.server_hostname, res - ))); - } - - let capabilities = capabilities.unwrap(); - if !capabilities - .iter() - .any(|cap| cap.eq_ignore_ascii_case(b"IMAP4rev1")) - { - return Err(MeliError::new(format!( - "Could not connect to {}: server is not IMAP4rev1 compliant", - &server_conf.server_hostname - ))); - } else if capabilities - .iter() - .any(|cap| cap.eq_ignore_ascii_case(b"LOGINDISABLED")) - { - return Err(MeliError::new(format!( - "Could not connect to {}: server does not accept logins [LOGINDISABLED]", - &server_conf.server_hostname - ))); - } - - let mut capabilities = None; - ret.send_command( - format!( - "LOGIN \"{}\" \"{}\"", - &server_conf.server_username, &server_conf.server_password - ) - .as_bytes(), - ) - .await?; - let tag_start = format!("M{} ", (ret.cmd_id - 1)); - - loop { - ret.read_lines(&mut res, &String::new(), false).await?; - let mut should_break = false; - for l in res.split_rn() { - if l.starts_with("* CAPABILITY") { - capabilities = protocol_parser::capabilities(l.as_bytes()) - .map(|(_, capabilities)| { - HashSet::from_iter(capabilities.into_iter().map(|s: &[u8]| s.to_vec())) - }) - .ok(); - } - - if l.starts_with(tag_start.as_str()) { - if !l[tag_start.len()..].trim().starts_with("OK ") { - return Err(MeliError::new(format!( - "Could not connect. Server replied with '{}'", - l[tag_start.len()..].trim() - ))); - } - should_break = true; - } - } - if should_break { - break; - } - } - - if capabilities.is_none() { - /* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so check - * for lazy servers */ - drop(capabilities); - ret.send_command(b"CAPABILITY").await?; - ret.read_response(&mut res).await.unwrap(); - let capabilities = protocol_parser::capabilities(res.as_bytes())?.1; - let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec())); - Ok((capabilities, ret)) - } else { - let capabilities = capabilities.unwrap(); - Ok((capabilities, ret)) - } - } - - pub async fn read_response(&mut self, ret: &mut String) -> Result<()> { - let id = match self.protocol { - ImapProtocol::IMAP => format!("M{} ", self.cmd_id - 1), - ImapProtocol::ManageSieve => String::new(), - }; - self.read_lines(ret, &id, true).await?; - Ok(()) - } - - pub async fn read_lines( - &mut self, - ret: &mut String, - termination_string: &str, - keep_termination_string: bool, - ) -> Result<()> { - let mut buf: [u8; 1024] = [0; 1024]; - ret.clear(); - let mut last_line_idx: usize = 0; - loop { - match self.stream.read(&mut buf).await { - Ok(0) => break, - Ok(b) => { - ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) }); - if let Some(mut pos) = ret[last_line_idx..].rfind("\r\n") { - if ret[last_line_idx..].starts_with("* BYE") { - return Err(MeliError::new("Disconnected")); - } - if let Some(prev_line) = - ret[last_line_idx..pos + last_line_idx].rfind("\r\n") - { - last_line_idx += prev_line + "\r\n".len(); - pos -= prev_line + "\r\n".len(); - } - if Some(pos + "\r\n".len()) == ret.get(last_line_idx..).map(|r| r.len()) { - if !termination_string.is_empty() - && ret[last_line_idx..].starts_with(termination_string) - { - debug!(&ret[last_line_idx..]); - if !keep_termination_string { - ret.replace_range(last_line_idx.., ""); - } - break; - } else if termination_string.is_empty() { - break; - } - } - last_line_idx += pos + "\r\n".len(); - } - } - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - continue; - } - Err(e) => { - return Err(MeliError::from(e)); - } - } - } - //debug!("returning IMAP response:\n{:?}", &ret); - Ok(()) - } - - pub async fn wait_for_continuation_request(&mut self) -> Result<()> { - let term = "+ ".to_string(); - let mut ret = String::new(); - self.read_lines(&mut ret, &term, false).await?; - Ok(()) - } - - pub async fn send_command(&mut self, command: &[u8]) -> Result<()> { - let command = command.trim(); - match self.protocol { - ImapProtocol::IMAP => { - self.stream.write_all(b"M").await?; - self.stream - .write_all(self.cmd_id.to_string().as_bytes()) - .await?; - self.stream.write_all(b" ").await?; - self.cmd_id += 1; - } - ImapProtocol::ManageSieve => {} - } - - self.stream.write_all(command).await?; - self.stream.write_all(b"\r\n").await?; - match self.protocol { - ImapProtocol::IMAP => { - debug!("sent: M{} {}", self.cmd_id - 1, unsafe { - std::str::from_utf8_unchecked(command) - }); - } - ImapProtocol::ManageSieve => {} - } - Ok(()) - } - - pub async fn send_literal(&mut self, data: &[u8]) -> Result<()> { - self.stream.write_all(data).await?; - self.stream.write_all(b"\r\n").await?; - Ok(()) - } - - pub async fn send_raw(&mut self, raw: &[u8]) -> Result<()> { - self.stream.write_all(raw).await?; - self.stream.write_all(b"\r\n").await?; - Ok(()) - } -} - -impl ImapConnection { - pub fn new_connection( - server_conf: &ImapServerConf, - uid_store: Arc, - ) -> ImapConnection { - ImapConnection { - stream: Err(MeliError::new("Offline".to_string())), - server_conf: server_conf.clone(), - capabilities: Capabilities::default(), - uid_store, - current_mailbox: MailboxSelection::None, - } - } - - pub async fn connect(&mut self) -> Result<()> { - if let (instant, ref mut status @ Ok(())) = *self.uid_store.is_online.lock().unwrap() { - if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) { - *status = Err(MeliError::new("Connection timed out")); - self.stream = Err(MeliError::new("Connection timed out")); - } - } - if self.stream.is_ok() { - self.uid_store.is_online.lock().unwrap().0 = Instant::now(); - return Ok(()); - } - let new_stream = debug!(ImapStream::new_connection(&self.server_conf).await); - if new_stream.is_err() { - *self.uid_store.is_online.lock().unwrap() = ( - Instant::now(), - Err(new_stream.as_ref().unwrap_err().clone()), - ); - } else { - *self.uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(())); - } - let (capabilities, stream) = new_stream?; - self.stream = Ok(stream); - self.capabilities = capabilities; - Ok(()) - } - - pub fn read_response<'a>( - &'a mut self, - ret: &'a mut String, - required_responses: RequiredResponses, - ) -> Pin> + Send + 'a>> { - Box::pin(async move { - let mut response = String::new(); - ret.clear(); - self.stream.as_mut()?.read_response(&mut response).await?; - - match self.server_conf.protocol { - ImapProtocol::IMAP => { - let r: ImapResponse = ImapResponse::from(&response); - match r { - ImapResponse::Bye(ref response_code) => { - self.stream = Err(MeliError::new(format!( - "Offline: received BYE: {:?}", - response_code - ))); - ret.push_str(&response); - } - ImapResponse::No(ref response_code) => { - debug!("Received NO response: {:?} {:?}", response_code, response); - ret.push_str(&response); - } - ImapResponse::Bad(ref response_code) => { - debug!("Received BAD response: {:?} {:?}", response_code, response); - ret.push_str(&response); - } - _ => { - /*debug!( - "check every line for required_responses: {:#?}", - &required_responses - );*/ - for l in response.split_rn() { - /*debug!("check line: {}", &l);*/ - if required_responses.check(l) || !self.process_untagged(l).await? { - ret.push_str(l); - } - } - } - } - r.into() - } - ImapProtocol::ManageSieve => { - ret.push_str(&response); - Ok(()) - } - } - }) - } - - pub async fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> { - self.stream - .as_mut()? - .read_lines(ret, &termination_string, false) - .await?; - Ok(()) - } - - pub async fn wait_for_continuation_request(&mut self) -> Result<()> { - self.stream - .as_mut()? - .wait_for_continuation_request() - .await?; - Ok(()) - } - - pub async fn send_command(&mut self, command: &[u8]) -> Result<()> { - self.stream.as_mut()?.send_command(command).await?; - Ok(()) - } - - pub async fn send_literal(&mut self, data: &[u8]) -> Result<()> { - self.stream.as_mut()?.send_literal(data).await?; - Ok(()) - } - - pub async fn send_raw(&mut self, raw: &[u8]) -> Result<()> { - self.stream.as_mut()?.send_raw(raw).await?; - Ok(()) - } - - pub async fn select_mailbox( - &mut self, - mailbox_hash: MailboxHash, - ret: &mut String, - force: bool, - ) -> Result<()> { - if !force && self.current_mailbox == MailboxSelection::Select(mailbox_hash) { - return Ok(()); - } - self.send_command( - format!( - "SELECT \"{}\"", - self.uid_store.mailboxes.lock().await[&mailbox_hash].imap_path() - ) - .as_bytes(), - ) - .await?; - self.read_response(ret, RequiredResponses::SELECT_REQUIRED) - .await?; - debug!("select response {}", ret); - self.current_mailbox = MailboxSelection::Select(mailbox_hash); - Ok(()) - } - - pub async fn examine_mailbox( - &mut self, - mailbox_hash: MailboxHash, - ret: &mut String, - force: bool, - ) -> Result<()> { - if !force && self.current_mailbox == MailboxSelection::Examine(mailbox_hash) { - return Ok(()); - } - self.send_command( - format!( - "EXAMINE \"{}\"", - self.uid_store.mailboxes.lock().await[&mailbox_hash].imap_path() - ) - .as_bytes(), - ) - .await?; - self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED) - .await?; - debug!("examine response {}", ret); - self.current_mailbox = MailboxSelection::Examine(mailbox_hash); - Ok(()) - } - - pub async fn unselect(&mut self) -> Result<()> { - match self.current_mailbox.take() { - MailboxSelection::Examine(mailbox_hash) | - MailboxSelection::Select(mailbox_hash) =>{ - let mut response = String::with_capacity(8 * 1024); - if self - .capabilities - .iter() - .any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT")) - { - self.send_command(b"UNSELECT").await?; - self.read_response(&mut response, RequiredResponses::empty()) - .await?; - } else { - /* `RFC3691 - UNSELECT Command` states: "[..] IMAP4 provides this - * functionality (via a SELECT command with a nonexistent mailbox name or - * reselecting the same mailbox with EXAMINE command)[..] - */ - - self.select_mailbox(mailbox_hash, &mut response, true).await?; - self.examine_mailbox(mailbox_hash, &mut response, true).await?; - } - }, - MailboxSelection::None => {}, - } - Ok(()) - } - - pub fn add_refresh_event(&mut self, ev: crate::backends::RefreshEvent) { - if let Some(ref sender) = self.uid_store.sender.read().unwrap().as_ref() { - sender.send(ev); - for ev in self.uid_store.refresh_events.lock().unwrap().drain(..) { - sender.send(ev); - } - } else { - self.uid_store.refresh_events.lock().unwrap().push(ev); - } - } - - pub async fn create_uid_msn_cache( - &mut self, - mailbox_hash: MailboxHash, - low: usize, - ) -> Result<()> { - debug_assert!(low > 0); - let mut response = String::new(); - self.examine_mailbox(mailbox_hash, &mut response, false) - .await?; - self.send_command(format!("UID SEARCH {}:*", low).as_bytes()) - .await?; - self.read_response(&mut response, RequiredResponses::SEARCH) - .await?; - debug!("uid search response {:?}", &response); - let mut msn_index_lck = self.uid_store.msn_index.lock().unwrap(); - let msn_index = msn_index_lck.entry(mailbox_hash).or_default(); - let _ = msn_index.drain(low - 1..); - msn_index.extend( - debug!(protocol_parser::search_results(response.as_bytes()))? - .1 - .into_iter(), - ); - Ok(()) - } -} - -pub struct ImapBlockingConnection { - buf: [u8; 1024], - result: Vec, - prev_res_length: usize, - pub conn: ImapConnection, - err: Option, -} - -impl From for ImapBlockingConnection { - fn from(conn: ImapConnection) -> Self { - ImapBlockingConnection { - buf: [0; 1024], - conn, - prev_res_length: 0, - result: Vec::with_capacity(8 * 1024), - err: None, - } - } -} - -impl ImapBlockingConnection { - pub fn into_conn(self) -> ImapConnection { - self.conn - } - - pub fn err(&self) -> Option<&str> { - self.err.as_ref().map(String::as_str) - } - - pub fn as_stream<'a>(&'a mut self) -> impl Future>> + 'a { - self.result.drain(0..self.prev_res_length); - self.prev_res_length = 0; - let mut break_flag = false; - let mut prev_failure = None; - async move { - if self.conn.stream.is_err() { - debug!(&self.conn.stream); - return None; - } - loop { - if let Some(y) = read(self, &mut break_flag, &mut prev_failure).await { - return Some(y); - } - if break_flag { - return None; - } - } - } - } -} - -async fn read( - conn: &mut ImapBlockingConnection, - break_flag: &mut bool, - prev_failure: &mut Option, -) -> Option> { - let ImapBlockingConnection { - ref mut prev_res_length, - ref mut result, - ref mut conn, - ref mut buf, - ref mut err, - } = conn; - - match conn.stream.as_mut().unwrap().stream.read(buf).await { - Ok(0) => { - *break_flag = true; - } - Ok(b) => { - result.extend_from_slice(&buf[0..b]); - debug!(unsafe { std::str::from_utf8_unchecked(result) }); - if let Some(pos) = result.find(b"\r\n") { - *prev_res_length = pos + b"\r\n".len(); - return Some(result[0..*prev_res_length].to_vec()); - } - *prev_failure = None; - } - Err(e) - if e.kind() == std::io::ErrorKind::WouldBlock - || e.kind() == std::io::ErrorKind::Interrupted => - { - debug!(&e); - if let Some(prev_failure) = prev_failure.as_ref() { - if Instant::now().duration_since(*prev_failure) - >= std::time::Duration::new(60 * 5, 0) - { - *err = Some(e.to_string()); - *break_flag = true; - } - } else { - *prev_failure = Some(Instant::now()); - } - } - Err(e) => { - debug!(&conn.stream); - debug!(&e); - *err = Some(e.to_string()); - *break_flag = true; - } - } - None -} - -fn lookup_ipv4(host: &str, port: u16) -> Result { - use std::net::ToSocketAddrs; - - let addrs = (host, port).to_socket_addrs()?; - for addr in addrs { - if let SocketAddr::V4(_) = addr { - return Ok(addr); - } - } - - Err(MeliError::new("Cannot lookup address")) -} diff --git a/melib/src/backends/imap_async/mailbox.rs b/melib/src/backends/imap_async/mailbox.rs deleted file mode 100644 index 31508445..00000000 --- a/melib/src/backends/imap_async/mailbox.rs +++ /dev/null @@ -1,172 +0,0 @@ -/* - * meli - imap module. - * - * Copyright 2019 Manos Pitsidianakis - * - * 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 . - */ -use crate::backends::{ - BackendMailbox, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox, -}; -use crate::email::EnvelopeHash; -use crate::error::*; -use std::collections::BTreeSet; -use std::sync::{Arc, Mutex, RwLock}; - -#[derive(Debug, Default, Clone)] -pub struct LazyCountSet { - not_yet_seen: usize, - set: BTreeSet, -} - -impl LazyCountSet { - pub fn set_not_yet_seen(&mut self, new_val: usize) { - self.not_yet_seen = new_val; - } - - pub fn insert_existing(&mut self, new_val: EnvelopeHash) -> bool { - if self.not_yet_seen == 0 { - false - } else { - self.not_yet_seen -= 1; - self.set.insert(new_val); - true - } - } - - pub fn insert_existing_set(&mut self, set: BTreeSet) -> bool { - debug!("insert_existing_set {:?}", &set); - if self.not_yet_seen < set.len() { - false - } else { - self.not_yet_seen -= set.len(); - self.set.extend(set.into_iter()); - true - } - } - - #[inline(always)] - pub fn len(&self) -> usize { - self.set.len() + self.not_yet_seen - } - - #[inline(always)] - pub fn clear(&mut self) { - self.set.clear(); - self.not_yet_seen = 0; - } - - pub fn insert_new(&mut self, new_val: EnvelopeHash) { - self.set.insert(new_val); - } - - pub fn insert_set(&mut self, set: BTreeSet) { - debug!("insert__set {:?}", &set); - self.set.extend(set.into_iter()); - } - - pub fn remove(&mut self, new_val: EnvelopeHash) -> bool { - self.set.remove(&new_val) - } -} - -#[test] -fn test_lazy_count_set() { - let mut new = LazyCountSet::default(); - new.set_not_yet_seen(10); - for i in 0..10 { - assert!(new.insert_existing(i)); - } - assert!(!new.insert_existing(10)); -} - -#[derive(Debug, Default, Clone)] -pub struct ImapMailbox { - pub(super) hash: MailboxHash, - pub(super) imap_path: String, - pub(super) path: String, - pub(super) name: String, - pub(super) parent: Option, - pub(super) children: Vec, - pub separator: u8, - pub usage: Arc>, - pub no_select: bool, - pub is_subscribed: bool, - - pub permissions: Arc>, - pub exists: Arc>, - pub unseen: Arc>, -} - -impl ImapMailbox { - pub fn imap_path(&self) -> &str { - &self.imap_path - } -} - -impl BackendMailbox for ImapMailbox { - fn hash(&self) -> MailboxHash { - self.hash - } - - fn name(&self) -> &str { - &self.name - } - - fn path(&self) -> &str { - &self.path - } - - fn change_name(&mut self, s: &str) { - self.name = s.to_string(); - } - - fn children(&self) -> &[MailboxHash] { - &self.children - } - - fn clone(&self) -> Mailbox { - Box::new(std::clone::Clone::clone(self)) - } - - fn special_usage(&self) -> SpecialUsageMailbox { - *self.usage.read().unwrap() - } - - fn parent(&self) -> Option { - self.parent - } - - fn permissions(&self) -> MailboxPermissions { - *self.permissions.lock().unwrap() - } - fn is_subscribed(&self) -> bool { - self.is_subscribed - } - fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> { - self.is_subscribed = new_val; - Ok(()) - } - - fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> { - *self.usage.write()? = new_val; - Ok(()) - } - - fn count(&self) -> Result<(usize, usize)> { - Ok((self.unseen.lock()?.len(), self.exists.lock()?.len())) - } -} diff --git a/melib/src/backends/imap_async/managesieve.rs b/melib/src/backends/imap_async/managesieve.rs deleted file mode 100644 index 18511bc5..00000000 --- a/melib/src/backends/imap_async/managesieve.rs +++ /dev/null @@ -1,139 +0,0 @@ -/* - * meli - managesieve - * - * Copyright 2020 Manos Pitsidianakis - * - * 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 . - */ - -use super::{ImapConnection, ImapProtocol, ImapServerConf, UIDStore}; -use crate::conf::AccountSettings; -use crate::error::{MeliError, Result}; -use crate::get_conf_val; -use nom::{ - branch::alt, bytes::complete::tag, combinator::map, error::ErrorKind, - multi::separated_nonempty_list, sequence::separated_pair, IResult, -}; -use std::str::FromStr; -use std::sync::{Arc, Mutex}; -use std::time::Instant; - -pub fn managesieve_capabilities(input: &[u8]) -> Result> { - let (_, ret) = separated_nonempty_list( - tag(b"\r\n"), - alt(( - separated_pair(quoted_raw, tag(b" "), quoted_raw), - map(quoted_raw, |q| (q, &b""[..])), - )), - )(input)?; - Ok(ret) -} - -#[test] -fn test_managesieve_capabilities() { - assert_eq!(managesieve_capabilities(b"\"IMPLEMENTATION\" \"Dovecot Pigeonhole\"\r\n\"SIEVE\" \"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext\"\r\n\"NOTIFY\" \"mailto\"\r\n\"SASL\" \"PLAIN\"\r\n\"STARTTLS\"\r\n\"VERSION\" \"1.0\"\r\n").unwrap(), vec![ - (&b"IMPLEMENTATION"[..],&b"Dovecot Pigeonhole"[..]), - (&b"SIEVE"[..],&b"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"[..]), - (&b"NOTIFY"[..],&b"mailto"[..]), - (&b"SASL"[..],&b"PLAIN"[..]), - (&b"STARTTLS"[..], &b""[..]), - (&b"VERSION"[..],&b"1.0"[..])] - - ); -} - -// Return a byte sequence surrounded by "s and decoded if necessary -pub fn quoted_raw(input: &[u8]) -> IResult<&[u8], &[u8]> { - if input.is_empty() || input[0] != b'"' { - return Err(nom::Err::Error((input, ErrorKind::Tag))); - } - - let mut i = 1; - while i < input.len() { - if input[i] == b'\"' && input[i - 1] != b'\\' { - return Ok((&input[i + 1..], &input[1..i])); - } - i += 1; - } - - Err(nom::Err::Error((input, ErrorKind::Tag))) -} - -pub trait ManageSieve { - fn havespace(&mut self) -> Result<()>; - fn putscript(&mut self) -> Result<()>; - - fn listscripts(&mut self) -> Result<()>; - fn setactive(&mut self) -> Result<()>; - - fn getscript(&mut self) -> Result<()>; - - fn deletescript(&mut self) -> Result<()>; - fn renamescript(&mut self) -> Result<()>; -} - -pub fn new_managesieve_connection(s: &AccountSettings) -> Result { - let server_hostname = get_conf_val!(s["server_hostname"])?; - let server_username = get_conf_val!(s["server_username"])?; - let server_password = get_conf_val!(s["server_password"])?; - let server_port = get_conf_val!(s["server_port"], 4190)?; - let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false)?; - let server_conf = ImapServerConf { - server_hostname: server_hostname.to_string(), - server_username: server_username.to_string(), - server_password: server_password.to_string(), - server_port, - use_starttls: true, - use_tls: true, - danger_accept_invalid_certs, - protocol: ImapProtocol::ManageSieve, - }; - let uid_store = Arc::new(UIDStore { - is_online: Arc::new(Mutex::new(( - Instant::now(), - Err(MeliError::new("Account is uninitialised.")), - ))), - ..Default::default() - }); - Ok(ImapConnection::new_connection(&server_conf, uid_store)) -} - -impl ManageSieve for ImapConnection { - fn havespace(&mut self) -> Result<()> { - Ok(()) - } - fn putscript(&mut self) -> Result<()> { - Ok(()) - } - - fn listscripts(&mut self) -> Result<()> { - Ok(()) - } - fn setactive(&mut self) -> Result<()> { - Ok(()) - } - - fn getscript(&mut self) -> Result<()> { - Ok(()) - } - - fn deletescript(&mut self) -> Result<()> { - Ok(()) - } - fn renamescript(&mut self) -> Result<()> { - Ok(()) - } -} diff --git a/melib/src/backends/imap_async/operations.rs b/melib/src/backends/imap_async/operations.rs deleted file mode 100644 index d30f2165..00000000 --- a/melib/src/backends/imap_async/operations.rs +++ /dev/null @@ -1,252 +0,0 @@ -/* - * meli - imap module. - * - * Copyright 2017 - 2019 Manos Pitsidianakis - * - * 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 . - */ - -use super::*; - -use crate::backends::*; -use crate::email::*; -use crate::error::{MeliError, Result}; -use std::sync::Arc; - -/// `BackendOp` implementor for Imap -#[derive(Debug, Clone)] -pub struct ImapOp { - uid: usize, - mailbox_hash: MailboxHash, - connection: Arc>, - uid_store: Arc, -} - -impl ImapOp { - pub fn new( - uid: usize, - mailbox_hash: MailboxHash, - connection: Arc>, - uid_store: Arc, - ) -> Self { - ImapOp { - uid, - connection, - mailbox_hash, - uid_store, - } - } -} - -impl BackendOp for ImapOp { - fn as_bytes(&mut self) -> ResultFuture> { - let connection = self.connection.clone(); - let mailbox_hash = self.mailbox_hash; - let uid = self.uid; - let uid_store = self.uid_store.clone(); - Ok(Box::pin(async move { - let exists_in_cache = { - let mut bytes_cache = uid_store.byte_cache.lock()?; - let cache = bytes_cache.entry(uid).or_default(); - cache.bytes.is_some() - }; - if !exists_in_cache { - 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 FETCH {} (FLAGS RFC822)", uid).as_bytes()) - .await?; - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) - .await?; - } - debug!( - "fetch response is {} bytes and {} lines", - response.len(), - response.lines().collect::>().len() - ); - let UidFetchResponse { - uid: _uid, - flags: _flags, - body, - .. - } = protocol_parser::uid_fetch_response(&response)?.1; - assert_eq!(_uid, uid); - assert!(body.is_some()); - let mut bytes_cache = uid_store.byte_cache.lock()?; - let cache = bytes_cache.entry(uid).or_default(); - if let Some((_flags, _)) = _flags { - //flags.lock().await.set(Some(_flags)); - cache.flags = Some(_flags); - } - cache.bytes = - Some(unsafe { std::str::from_utf8_unchecked(body.unwrap()).to_string() }); - } - let mut bytes_cache = uid_store.byte_cache.lock()?; - let cache = bytes_cache.entry(uid).or_default(); - let ret = cache.bytes.clone().unwrap().into_bytes(); - Ok(ret) - })) - } - - fn fetch_flags(&self) -> ResultFuture { - let mut response = String::with_capacity(8 * 1024); - let connection = self.connection.clone(); - let mailbox_hash = self.mailbox_hash; - let uid = self.uid; - let uid_store = self.uid_store.clone(); - - Ok(Box::pin(async move { - let exists_in_cache = { - let mut bytes_cache = uid_store.byte_cache.lock()?; - let cache = bytes_cache.entry(uid).or_default(); - cache.flags.is_some() - }; - if !exists_in_cache { - let mut conn = connection.lock().await; - conn.examine_mailbox(mailbox_hash, &mut response, false) - .await?; - conn.send_command(format!("UID FETCH {} FLAGS", uid).as_bytes()) - .await?; - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) - .await?; - debug!( - "fetch response is {} bytes and {} lines", - response.len(), - response.lines().collect::>().len() - ); - let v = protocol_parser::uid_fetch_flags_response(response.as_bytes()) - .map(|(_, v)| v) - .map_err(MeliError::from)?; - if v.len() != 1 { - debug!("responses len is {}", v.len()); - debug!(&response); - /* TODO: Trigger cache invalidation here. */ - debug!(format!("message with UID {} was not found", uid)); - return Err(MeliError::new(format!( - "Invalid/unexpected response: {:?}", - response - )) - .set_summary(format!("message with UID {} was not found?", uid))); - } - let (_uid, (_flags, _)) = v[0]; - assert_eq!(uid, uid); - let mut bytes_cache = uid_store.byte_cache.lock()?; - let cache = bytes_cache.entry(uid).or_default(); - cache.flags = Some(_flags); - } - { - let val = { - let mut bytes_cache = uid_store.byte_cache.lock()?; - let cache = bytes_cache.entry(uid).or_default(); - cache.flags - }; - Ok(val.unwrap()) - } - })) - } - - fn set_flag( - &mut self, - flag: Flag, - value: bool, - ) -> Result> + Send>>> { - let flags = self.fetch_flags()?; - - let mut response = String::with_capacity(8 * 1024); - let connection = self.connection.clone(); - let mailbox_hash = self.mailbox_hash; - let uid = self.uid; - let uid_store = self.uid_store.clone(); - Ok(Box::pin(async move { - let mut flags = flags.await?; - flags.set(flag, value); - let mut conn = connection.lock().await; - conn.select_mailbox(mailbox_hash, &mut response, false) - .await?; - debug!(&response); - conn.send_command( - format!( - "UID STORE {} FLAGS.SILENT ({})", - uid, - flags_to_imap_list!(flags) - ) - .as_bytes(), - ) - .await?; - conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED) - .await?; - debug!(&response); - match protocol_parser::uid_fetch_flags_response(response.as_bytes()) - .map(|(_, v)| v) - .map_err(MeliError::from) - { - Ok(v) => { - if v.len() == 1 { - debug!("responses len is {}", v.len()); - let (_uid, (_flags, _)) = v[0]; - assert_eq!(_uid, uid); - } - } - Err(e) => Err(e)?, - } - let mut bytes_cache = uid_store.byte_cache.lock()?; - let cache = bytes_cache.entry(uid).or_default(); - cache.flags = Some(flags); - Ok(()) - })) - } - - fn set_tag( - &mut self, - tag: String, - value: bool, - ) -> Result> + Send>>> { - let mut response = String::with_capacity(8 * 1024); - let connection = self.connection.clone(); - let mailbox_hash = self.mailbox_hash; - let uid = self.uid; - let uid_store = self.uid_store.clone(); - Ok(Box::pin(async move { - let mut conn = connection.lock().await; - conn.select_mailbox(mailbox_hash, &mut response, false) - .await?; - conn.send_command( - format!( - "UID STORE {} {}FLAGS.SILENT ({})", - uid, - if value { "+" } else { "-" }, - &tag - ) - .as_bytes(), - ) - .await?; - conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED) - .await?; - protocol_parser::uid_fetch_flags_response(response.as_bytes()) - .map(|(_, v)| v) - .map_err(MeliError::from)?; - let hash = tag_hash!(tag); - if value { - uid_store.tag_index.write().unwrap().insert(hash, tag); - } else { - uid_store.tag_index.write().unwrap().remove(&hash); - } - Ok(()) - })) - } -} diff --git a/melib/src/backends/imap_async/protocol_parser.rs b/melib/src/backends/imap_async/protocol_parser.rs deleted file mode 100644 index 7cf69398..00000000 --- a/melib/src/backends/imap_async/protocol_parser.rs +++ /dev/null @@ -1,1544 +0,0 @@ -/* - * meli - melib crate. - * - * Copyright 2017-2020 Manos Pitsidianakis - * - * 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 . - */ - -use super::*; -use crate::email::parser::{BytesExt, IResult}; -use crate::error::ResultIntoMeliError; -use crate::get_path_hash; -use nom::{ - branch::{alt, permutation}, - bytes::complete::{is_a, is_not, tag, take, take_until, take_while}, - character::complete::digit1, - character::is_digit, - combinator::{map, map_res, opt}, - multi::{length_data, many0, many1, separated_list, separated_nonempty_list}, - sequence::{delimited, preceded}, -}; -use std::str::FromStr; - -bitflags! { - #[derive(Default, Serialize, Deserialize)] - pub struct RequiredResponses: u64 { - const CAPABILITY = 0b0000_0000_0000_0001; - const BYE = 0b0000_0000_0000_0010; - const FLAGS = 0b0000_0000_0000_0100; - const EXISTS = 0b0000_0000_0000_1000; - const RECENT = 0b0000_0000_0001_0000; - const UNSEEN = 0b0000_0000_0010_0000; - const PERMANENTFLAGS = 0b0000_0000_0100_0000; - const UIDNEXT = 0b0000_0000_1000_0000; - const UIDVALIDITY = 0b0000_0001_0000_0000; - const LIST = 0b0000_0010_0000_0000; - const LSUB = 0b0000_0100_0000_0000; - const STATUS = 0b0000_1000_0000_0000; - const EXPUNGE = 0b0001_0000_0000_0000; - const SEARCH = 0b0010_0000_0000_0000; - const FETCH = 0b0100_0000_0000_0000; - const CAPABILITY_REQUIRED = Self::CAPABILITY.bits; - const LOGOUT_REQUIRED = Self::BYE.bits; - const SELECT_REQUIRED = Self::FLAGS.bits | Self::EXISTS.bits | Self::RECENT.bits | Self::UNSEEN.bits | Self::PERMANENTFLAGS.bits | Self::UIDNEXT.bits | Self::UIDVALIDITY.bits; - const EXAMINE_REQUIRED = Self::FLAGS.bits | Self::EXISTS.bits | Self::RECENT.bits | Self::UNSEEN.bits | Self::PERMANENTFLAGS.bits | Self::UIDNEXT.bits | Self::UIDVALIDITY.bits; - const LIST_REQUIRED = Self::LIST.bits; - const LSUB_REQUIRED = Self::LSUB.bits; - const FETCH_REQUIRED = Self::FETCH.bits; - const STORE_REQUIRED = Self::FETCH.bits; - } -} - -impl RequiredResponses { - pub fn check(&self, line: &str) -> bool { - if !line.starts_with("* ") { - return false; - } - let line = &line["* ".len()..]; - let mut ret = false; - if self.intersects(RequiredResponses::CAPABILITY) { - ret |= line.starts_with("CAPABILITY"); - } - if self.intersects(RequiredResponses::BYE) { - ret |= line.starts_with("BYE"); - } - if self.intersects(RequiredResponses::FLAGS) { - ret |= line.starts_with("FLAGS"); - } - if self.intersects(RequiredResponses::EXISTS) { - ret |= line.ends_with("EXISTS\r\n"); - } - if self.intersects(RequiredResponses::RECENT) { - ret |= line.ends_with("RECENT\r\n"); - } - if self.intersects(RequiredResponses::UNSEEN) { - ret |= line.starts_with("UNSEEN"); - } - if self.intersects(RequiredResponses::PERMANENTFLAGS) { - ret |= line.starts_with("PERMANENTFLAGS"); - } - if self.intersects(RequiredResponses::UIDNEXT) { - ret |= line.starts_with("UIDNEXT"); - } - if self.intersects(RequiredResponses::UIDVALIDITY) { - ret |= line.starts_with("UIDVALIDITY"); - } - if self.intersects(RequiredResponses::LIST) { - ret |= line.starts_with("LIST"); - } - if self.intersects(RequiredResponses::LSUB) { - ret |= line.starts_with("LSUB"); - } - if self.intersects(RequiredResponses::STATUS) { - ret |= line.starts_with("STATUS"); - } - if self.intersects(RequiredResponses::EXPUNGE) { - ret |= line.ends_with("EXPUNGE\r\n"); - } - if self.intersects(RequiredResponses::SEARCH) { - ret |= line.starts_with("SEARCH"); - } - if self.intersects(RequiredResponses::FETCH) { - let mut ptr = 0; - for i in 0..line.len() { - if !line.as_bytes()[i].is_ascii_digit() { - ptr = i; - break; - } - } - ret |= line[ptr..].trim_start().starts_with("FETCH"); - } - ret - } -} - -#[test] -fn test_imap_required_responses() { - let mut ret = String::new(); - let required_responses = RequiredResponses::FETCH_REQUIRED; - let response = - &"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n" - [0..]; - for l in response.split_rn() { - /*debug!("check line: {}", &l);*/ - if required_responses.check(l) { - ret.push_str(l); - } - } - assert_eq!(&ret, "* 1040 FETCH (UID 1064 FLAGS ())\r\n"); - let v = protocol_parser::uid_fetch_flags_response(response.as_bytes()) - .unwrap() - .1; - assert_eq!(v.len(), 1); -} - -#[derive(Debug)] -pub struct Alert(String); -pub type ImapParseResult<'a, T> = Result<(&'a str, T, Option)>; -pub struct ImapLineIterator<'a> { - slice: &'a str, -} - -#[derive(Debug, PartialEq)] -pub enum ResponseCode { - ///The human-readable text contains a special alert that MUST be presented to the user in a fashion that calls the user's attention to the message. - Alert(String), - - ///Optionally followed by a parenthesized list of charsets. A SEARCH failed because the given charset is not supported by this implementation. If the optional list of charsets is given, this lists the charsets that are supported by this implementation. - Badcharset(Option), - - /// Followed by a list of capabilities. This can appear in the initial OK or PREAUTH response to transmit an initial capabilities list. This makes it unnecessary for a client to send a separate CAPABILITY command if it recognizes this response. - Capability, - - /// The human-readable text represents an error in parsing the [RFC-2822] header or [MIME-IMB] headers of a message in the mailbox. - Parse(String), - - /// Followed by a parenthesized list of flags, indicates which of the known flags the client can change permanently. Any flags that are in the FLAGS untagged response, but not the PERMANENTFLAGS list, can not be set permanently. If the client attempts to STORE a flag that is not in the PERMANENTFLAGS list, the server will either ignore the change or store the state change for the remainder of the current session only. The PERMANENTFLAGS list can also include the special flag \*, which indicates that it is possible to create new keywords by attempting to store those flags in the mailbox. - Permanentflags(String), - - /// The mailbox is selected read-only, or its access while selected has changed from read-write to read-only. - ReadOnly, - - /// The mailbox is selected read-write, or its access while selected has changed from read-only to read-write. - ReadWrite, - - /// An APPEND or COPY attempt is failing because the target mailbox does not exist (as opposed to some other reason). This is a hint to the client that the operation can succeed if the mailbox is first created by the CREATE command. - Trycreate, - - /// Followed by a decimal number, indicates the next unique identifier value. Refer to section 2.3.1.1 for more information. - Uidnext(UID), - /// Followed by a decimal number, indicates the unique identifier validity value. Refer to section 2.3.1.1 for more information. - Uidvalidity(UID), - /// Followed by a decimal number, indicates the number of the first message without the \Seen flag set. - Unseen(usize), -} - -impl std::fmt::Display for ResponseCode { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - use ResponseCode::*; - match self { - Alert(s)=> write!(fmt, "ALERT: {}", s), - Badcharset(None)=> write!(fmt, "Given charset is not supported by this server."), - Badcharset(Some(s))=> write!(fmt, "Given charset is not supported by this server. Supported ones are: {}", s), - Capability => write!(fmt, "Capability response"), - Parse(s) => write!(fmt, "Server error in parsing message headers: {}", s), - Permanentflags(s) => write!(fmt, "Mailbox supports these flags: {}", s), - ReadOnly=> write!(fmt, "This mailbox is selected read-only."), - ReadWrite => write!(fmt, "This mailbox is selected with read-write permissions."), - Trycreate => write!(fmt, "Failed to operate on the target mailbox because it doesn't exist. Try creating it first."), - Uidnext(uid) => write!(fmt, "Next UID value is {}", uid), - Uidvalidity(uid) => write!(fmt, "Next UIDVALIDITY value is {}", uid), - Unseen(uid) => write!(fmt, "First message without the \\Seen flag is {}", uid), - } - } -} - -impl ResponseCode { - fn from(val: &str) -> ResponseCode { - use ResponseCode::*; - if !val.starts_with("[") { - let msg = val.trim(); - return Alert(msg.to_string()); - } - - let val = &val[1..]; - if val.starts_with("BADCHARSET") { - let charsets = val - .as_bytes() - .find(b"(") - .map(|pos| val[pos + 1..].trim().to_string()); - Badcharset(charsets) - } else if val.starts_with("READONLY") { - ReadOnly - } else if val.starts_with("READWRITE") { - ReadWrite - } else if val.starts_with("TRYCREATE") { - Trycreate - } else if val.starts_with("UIDNEXT") { - //FIXME - Uidnext(0) - } else if val.starts_with("UIDVALIDITY") { - //FIXME - Uidvalidity(0) - } else if val.starts_with("UNSEEN") { - //FIXME - Unseen(0) - } else { - let msg = &val[val.as_bytes().find(b"] ").unwrap() + 1..].trim(); - Alert(msg.to_string()) - } - } -} - -#[derive(Debug, PartialEq)] -pub enum ImapResponse { - Ok(ResponseCode), - No(ResponseCode), - Bad(ResponseCode), - Preauth(ResponseCode), - Bye(ResponseCode), -} - -impl> From for ImapResponse { - fn from(val: T) -> ImapResponse { - let val: &str = val.as_ref().split_rn().last().unwrap_or(val.as_ref()); - debug!(&val); - let mut val = val[val.as_bytes().find(b" ").unwrap() + 1..].trim(); - // M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n - if val.ends_with(" secs).") { - val = &val[..val.as_bytes().rfind(b"(").unwrap()]; - } - - if val.starts_with("OK") { - Self::Ok(ResponseCode::from(&val["OK ".len()..])) - } else if val.starts_with("NO") { - Self::No(ResponseCode::from(&val["NO ".len()..])) - } else if val.starts_with("BAD") { - Self::Bad(ResponseCode::from(&val["BAD ".len()..])) - } else if val.starts_with("PREAUTH") { - Self::Preauth(ResponseCode::from(&val["PREAUTH ".len()..])) - } else if val.starts_with("BYE") { - Self::Bye(ResponseCode::from(&val["BYE ".len()..])) - } else { - panic!("Unknown IMAP response: `{}`", val); - } - } -} - -impl Into> for ImapResponse { - fn into(self) -> Result<()> { - match self { - Self::Ok(_) | Self::Preauth(_) | Self::Bye(_) => Ok(()), - Self::No(ResponseCode::Alert(msg)) | Self::Bad(ResponseCode::Alert(msg)) => { - Err(MeliError::new(msg)) - } - Self::No(err) => Err(MeliError::new(format!("{:?}", err))) - .chain_err_summary(|| "IMAP NO Response.".to_string()), - Self::Bad(err) => Err(MeliError::new(format!("{:?}", err))) - .chain_err_summary(|| "IMAP BAD Response.".to_string()), - } - } -} - -#[test] -fn test_imap_response() { - assert_eq!(ImapResponse::from("M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n"), ImapResponse::No(ResponseCode::Alert("Invalid mailbox name: Name must not have '/' characters".to_string()))); -} - -impl<'a> Iterator for ImapLineIterator<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option<&'a str> { - if self.slice.is_empty() { - None - } else if let Some(pos) = self.slice.find("\r\n") { - let ret = &self.slice[..pos + 2]; - self.slice = &self.slice[pos + 2..]; - Some(ret) - } else { - let ret = self.slice; - self.slice = &self.slice[ret.len()..]; - Some(ret) - } - } -} - -pub trait ImapLineSplit { - fn split_rn(&self) -> ImapLineIterator; -} - -impl ImapLineSplit for str { - fn split_rn(&self) -> ImapLineIterator { - ImapLineIterator { slice: self } - } -} - -macro_rules! to_str ( - ($v:expr) => (unsafe{ std::str::from_utf8_unchecked($v) }) -); - -/*macro_rules! dbg_dmp ( - ($i: expr, $submac:ident!( $($args:tt)* )) => ( - { - let l = line!(); - match $submac!($i, $($args)*) { - nom::IResult::Error(a) => { - debug!("Error({:?}) at l.{} by ' {} '\n{}", a, l, stringify!($submac!($($args)*)), unsafe{ std::str::from_utf8_unchecked($i) }); - nom::IResult::Error(a) - }, - nom::IResult::Incomplete(a) => { - debug!("Incomplete({:?}) at {} by ' {} '\n{}", a, l, stringify!($submac!($($args)*)), unsafe{ std::str::from_utf8_unchecked($i) }); - nom::IResult::Incomplete(a) - }, - a => a - } - } - ); - - ($i:expr, $f:ident) => ( - dbg_dmp!($i, call!($f)); - ); -); -*/ - -/* -* LIST (\HasNoChildren) "." INBOX.Sent -* LIST (\HasChildren) "." INBOX - */ - -pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> { - let (input, _) = alt((tag("* LIST ("), tag("* LSUB (")))(input.ltrim())?; - let (input, properties) = take_until(&b")"[0..])(input)?; - let (input, _) = tag(b") ")(input)?; - let (input, separator) = delimited(tag(b"\""), take(1_u32), tag(b"\""))(input)?; - let (input, _) = take(1_u32)(input)?; - let (input, path) = mailbox_token(input)?; - Ok(( - input, - ({ - let separator: u8 = separator[0]; - let mut f = ImapMailbox::default(); - f.no_select = false; - f.is_subscribed = false; - for p in properties.split(|&b| b == b' ') { - if p.eq_ignore_ascii_case(b"\\NoSelect") { - f.no_select = true; - } else if p.eq_ignore_ascii_case(b"\\Sent") { - let _ = f.set_special_usage(SpecialUsageMailbox::Sent); - } else if p.eq_ignore_ascii_case(b"\\Junk") { - let _ = f.set_special_usage(SpecialUsageMailbox::Trash); - } else if p.eq_ignore_ascii_case(b"\\Drafts") { - let _ = f.set_special_usage(SpecialUsageMailbox::Drafts); - } - } - f.is_subscribed = path == "INBOX"; - f.imap_path = path.into(); - f.hash = get_path_hash!(&f.imap_path); - f.path = if separator == b'/' { - f.imap_path.clone() - } else { - f.imap_path.replace(separator as char, "/") - }; - f.name = if let Some(pos) = f.imap_path.as_bytes().iter().rposition(|&c| c == separator) - { - f.parent = Some(get_path_hash!(&f.imap_path[..pos])); - f.imap_path[pos + 1..].to_string() - } else { - f.imap_path.clone() - }; - f.separator = separator; - - debug!(f) - }), - )) -} - -pub fn my_flags(input: &[u8]) -> IResult<&[u8], Flag> { - let (input, flags) = separated_list(tag(" "), preceded(tag("\\"), is_not(")")))(input)?; - let mut ret = Flag::default(); - for f in flags { - match f { - b"Answered" => { - ret.set(Flag::REPLIED, true); - } - b"Flagged" => { - ret.set(Flag::FLAGGED, true); - } - b"Deleted" => { - ret.set(Flag::TRASHED, true); - } - b"Seen" => { - ret.set(Flag::SEEN, true); - } - b"Draft" => { - ret.set(Flag::DRAFT, true); - } - f => { - debug!("unknown Flag token value: {}", unsafe { - std::str::from_utf8_unchecked(f) - }); - } - } - } - Ok((input, ret)) -} - -#[derive(Debug)] -pub struct UidFetchResponse<'a> { - pub uid: UID, - pub message_sequence_number: usize, - pub flags: Option<(Flag, Vec)>, - pub body: Option<&'a [u8]>, - pub envelope: Option, -} - -pub fn uid_fetch_response(input: &str) -> ImapParseResult> { - macro_rules! should_start_with { - ($input:expr, $tag:literal) => { - if !$input.starts_with($tag) { - return Err(MeliError::new(format!( - "Expected `{}` but got `{:.50}`", - $tag, &$input - ))); - } - }; - } - should_start_with!(input, "* "); - - let mut i = "* ".len(); - macro_rules! bounds { - () => { - if i == input.len() { - return Err(MeliError::new(format!( - "Expected more input. Got: `{:.50}`", - input - ))); - } - }; - (break) => { - if i == input.len() { - break; - } - }; - } - - macro_rules! eat_whitespace { - () => { - while (input.as_bytes()[i] as char).is_whitespace() { - i += 1; - bounds!(); - } - }; - (break) => { - while (input.as_bytes()[i] as char).is_whitespace() { - i += 1; - bounds!(break); - } - }; - } - - let mut ret = UidFetchResponse { - uid: 0, - message_sequence_number: 0, - flags: None, - body: None, - envelope: None, - }; - - while input.as_bytes()[i].is_ascii_digit() { - let b: u8 = input.as_bytes()[i] - 0x30; - ret.message_sequence_number *= 10; - ret.message_sequence_number += b as usize; - i += 1; - bounds!(); - } - - eat_whitespace!(); - should_start_with!(input[i..], "FETCH ("); - i += "FETCH (".len(); - let mut has_attachments = false; - while i < input.len() { - eat_whitespace!(break); - - if input[i..].starts_with("UID ") { - i += "UID ".len(); - if let Ok((rest, uid)) = take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>( - is_digit, - )(input[i..].as_bytes()) - { - i += input.len() - i - rest.len(); - ret.uid = usize::from_str(unsafe { std::str::from_utf8_unchecked(uid) }).unwrap(); - } else { - return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", - input - )))); - } - } else if input[i..].starts_with("FLAGS (") { - i += "FLAGS (".len(); - if let Ok((rest, flags)) = flags(&input[i..]) { - ret.flags = Some(flags); - i += (input.len() - i - rest.len()) + 1; - } else { - return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", - input - )))); - } - } else if input[i..].starts_with("RFC822 {") { - i += "RFC822 ".len(); - if let Ok((rest, body)) = - length_data::<_, _, (&[u8], nom::error::ErrorKind), _>(delimited( - tag("{"), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - tag("}\r\n"), - ))(input[i..].as_bytes()) - { - ret.body = Some(body); - i += input.len() - i - rest.len(); - } else { - return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", - input - )))); - } - } else if input[i..].starts_with("ENVELOPE (") { - i += "ENVELOPE ".len(); - if let Ok((rest, envelope)) = envelope(input[i..].as_bytes()) { - ret.envelope = Some(envelope); - i += input.len() - i - rest.len(); - } else { - return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", - &input[i..] - )))); - } - } else if input[i..].starts_with("BODYSTRUCTURE ") { - i += "BODYSTRUCTURE ".len(); - let mut struct_ptr = i; - let mut parenth_level = 0; - let mut inside_quote = false; - while struct_ptr != input.len() { - if !inside_quote { - if input.as_bytes()[struct_ptr] == b'(' { - parenth_level += 1; - } else if input.as_bytes()[struct_ptr] == b')' { - if parenth_level == 0 { - return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", - &input[struct_ptr..] - )))); - } - parenth_level -= 1; - if parenth_level == 0 { - struct_ptr += 1; - break; - } - } else if input.as_bytes()[struct_ptr] == b'"' { - inside_quote = true; - } - } else if input.as_bytes()[struct_ptr] == b'\"' - && (struct_ptr == 0 || (input.as_bytes()[struct_ptr - 1] != b'\\')) - { - inside_quote = false; - } - struct_ptr += 1; - } - - has_attachments = bodystructure_has_attachments(&input.as_bytes()[i..struct_ptr]); - i = struct_ptr; - } else if input[i..].starts_with(")\r\n") { - i += ")\r\n".len(); - break; - } else { - debug!( - "Got unexpected token while parsing UID FETCH response:\n`{}`\n", - input - ); - return debug!(Err(MeliError::new(format!( - "Got unexpected token while parsing UID FETCH response: `{:.40}`", - &input[i..] - )))); - } - } - - if let Some(env) = ret.envelope.as_mut() { - env.set_has_attachments(has_attachments); - } - - Ok((&input[i..], ret, None)) -} - -pub fn uid_fetch_responses(mut input: &str) -> ImapParseResult>> { - let mut ret = Vec::new(); - let mut alert: Option = None; - - loop { - let next_response = uid_fetch_response(input); - match next_response { - Ok((rest, el, el_alert)) => { - if let Some(el_alert) = el_alert { - match &mut alert { - Some(Alert(ref mut alert)) => { - alert.extend(el_alert.0.chars()); - } - a @ None => *a = Some(el_alert), - } - } - input = rest; - ret.push(el); - if !input.starts_with("* ") { - break; - } - } - Err(err) => { - return Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH responses: `{:.40}`, {}", - input, err - ))); - } - } - } - - if !input.is_empty() && ret.is_empty() { - return Err(MeliError::new(format!( - "310Unexpected input while parsing UID FETCH responses: `{:.40}`", - input - ))); - } - Ok((input, ret, None)) -} - -/* - * - * "* 1 FETCH (FLAGS (\Seen) UID 1 RFC822.HEADER {5224} " -*/ -pub fn uid_fetch_response_( - input: &[u8], -) -> IResult<&[u8], Vec<(usize, Option<(Flag, Vec)>, &[u8])>> { - many0( - |input| -> IResult<&[u8], (usize, Option<(Flag, Vec)>, &[u8])> { - let (input, _) = tag("* ")(input)?; - let (input, _) = take_while(is_digit)(input)?; - let (input, result) = permutation(( - preceded( - alt((tag("UID "), tag(" UID "))), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - ), - opt(preceded( - alt((tag("FLAGS "), tag(" FLAGS "))), - delimited(tag("("), byte_flags, tag(")")), - )), - length_data(delimited( - tag("{"), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - tag("}\r\n"), - )), - ))(input.ltrim())?; - let (input, _) = tag(")\r\n")(input)?; - Ok((input, (result.0, result.1, result.2))) - }, - )(input) -} - -pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], Vec<(usize, (Flag, Vec))>> { - many0(|input| -> IResult<&[u8], (usize, (Flag, Vec))> { - let (input, _) = tag("* ")(input)?; - let (input, _msn) = take_while(is_digit)(input)?; - let (input, _) = tag(" FETCH (")(input)?; - let (input, uid_flags) = permutation(( - preceded( - alt((tag("UID "), tag(" UID "))), - map_res(digit1, |s| usize::from_str(to_str!(s))), - ), - preceded( - alt((tag("FLAGS "), tag(" FLAGS "))), - delimited(tag("("), byte_flags, tag(")")), - ), - ))(input)?; - let (input, _) = tag(")\r\n")(input)?; - Ok((input, (uid_flags.0, uid_flags.1))) - })(input) -} - -macro_rules! flags_to_imap_list { - ($flags:ident) => {{ - let mut ret = String::new(); - if !($flags & Flag::REPLIED).is_empty() { - ret.push_str("\\Answered"); - } - if !($flags & Flag::FLAGGED).is_empty() { - if !ret.is_empty() { - ret.push(' '); - } - ret.push_str("\\Flagged"); - } - if !($flags & Flag::TRASHED).is_empty() { - if !ret.is_empty() { - ret.push(' '); - } - ret.push_str("\\Deleted"); - } - if !($flags & Flag::SEEN).is_empty() { - if !ret.is_empty() { - ret.push(' '); - } - ret.push_str("\\Seen"); - } - if !($flags & Flag::DRAFT).is_empty() { - if !ret.is_empty() { - ret.push(' '); - } - ret.push_str("\\Draft"); - } - ret - }}; -} - -/* Input Example: - * ============== - * - * "M0 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SPECIAL-USE] Logged in\r\n" -* "* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENT TOKEN AUTH=OAUTHBEARER AUTH=XOAUTH\r\n" -* "* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN\r\n" - */ - -pub fn capabilities(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> { - let (input, _) = take_until("CAPABILITY ")(input)?; - let (input, _) = tag("CAPABILITY ")(input)?; - let (input, ret) = separated_nonempty_list(tag(" "), is_not(" ]\r\n"))(input)?; - let (input, _) = take_until("\r\n")(input)?; - let (input, _) = tag("\r\n")(input)?; - Ok((input, ret)) - /* - pub capabilities<>, - do_parse!( - take_until!("CAPABILITY ") - >> tag!("CAPABILITY ") - >> ret: separated_nonempty_list_complete!(tag!(" "), is_not!(" ]\r\n")) - >> take_until!("\r\n") - >> tag!("\r\n") - >> ({ ret }) - ) - */ -} - -/// This enum represents the server's untagged responses detailed in `7. Server Responses` of RFC 3501 INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1 -pub enum UntaggedResponse { - /// ```text - /// 7.4.1. EXPUNGE Response - /// - /// The EXPUNGE response reports that the specified message sequence - /// number has been permanently removed from the mailbox. The message - /// sequence number for each successive message in the mailbox is - /// immediately decremented by 1, and this decrement is reflected in - /// message sequence numbers in subsequent responses (including other - /// untagged EXPUNGE responses). - /// - /// The EXPUNGE response also decrements the number of messages in the - /// mailbox; it is not necessary to send an EXISTS response with the - /// new value. - /// - /// As a result of the immediate decrement rule, message sequence - /// numbers that appear in a set of successive EXPUNGE responses - /// depend upon whether the messages are removed starting from lower - /// numbers to higher numbers, or from higher numbers to lower - /// numbers. For example, if the last 5 messages in a 9-message - /// mailbox are expunged, a "lower to higher" server will send five - /// untagged EXPUNGE responses for message sequence number 5, whereas - /// a "higher to lower server" will send successive untagged EXPUNGE - /// responses for message sequence numbers 9, 8, 7, 6, and 5. - /// - /// An EXPUNGE response MUST NOT be sent when no command is in - /// progress, nor while responding to a FETCH, STORE, or SEARCH - /// command. This rule is necessary to prevent a loss of - /// synchronization of message sequence numbers between client and - /// server. A command is not "in progress" until the complete command - /// has been received; in particular, a command is not "in progress" - /// during the negotiation of command continuation. - /// - /// Note: UID FETCH, UID STORE, and UID SEARCH are different - /// commands from FETCH, STORE, and SEARCH. An EXPUNGE - /// response MAY be sent during a UID command. - /// - /// The update from the EXPUNGE response MUST be recorded by the - /// client. - /// ``` - Expunge(usize), - /// ```text - /// 7.3.1. EXISTS Response - /// - /// The EXISTS response reports the number of messages in the mailbox. - /// This response occurs as a result of a SELECT or EXAMINE command, - /// and if the size of the mailbox changes (e.g., new messages). - /// - /// The update from the EXISTS response MUST be recorded by the - /// client. - /// ``` - Exists(usize), - /// ```text - /// 7.3.2. RECENT Response - /// The RECENT response reports the number of messages with the - /// \Recent flag set. This response occurs as a result of a SELECT or - /// EXAMINE command, and if the size of the mailbox changes (e.g., new - /// messages). - /// ``` - Recent(usize), - Fetch(usize, (Flag, Vec)), - Bye { - reason: String, - }, -} - -pub fn untagged_responses(input: &[u8]) -> IResult<&[u8], Option> { - debug!("Parse untagged response from {:?}", to_str!(input)); - let (input, _) = tag("* ")(input)?; - let (input, num) = map_res(digit1, |s| usize::from_str(to_str!(s)))(input)?; - let (input, _) = tag(" ")(input)?; - let (input, _tag) = take_until("\r\n")(input)?; - let (input, _) = tag("\r\n")(input)?; - Ok((input, { - use UntaggedResponse::*; - match _tag { - b"EXPUNGE" => Some(Expunge(num)), - b"EXISTS" => Some(Exists(num)), - b"RECENT" => Some(Recent(num)), - _ if _tag.starts_with(b"FETCH ") => { - let f = flags(unsafe { - std::str::from_utf8_unchecked(&_tag[b"FETCH (FLAGS (".len()..]) - }) - .map(|(_, flags)| Fetch(num, flags)); - if let Err(ref err) = f { - debug!( - "untagged_response malformed fetch: {} {}", - unsafe { std::str::from_utf8_unchecked(_tag) }, - err - ) - } - f.ok() - } - _ => { - debug!("unknown untagged_response: {}", to_str!(_tag)); - None - } - } - })) -} - -pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec> { - alt(( - |input: &'a [u8]| -> IResult<&'a [u8], Vec> { - let (input, _) = tag("* SEARCH ")(input)?; - let (input, list) = separated_nonempty_list( - tag(b" "), - map_res(is_not(" \r\n"), |s: &[u8]| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - )(input)?; - let (input, _) = tag("\r\n")(input)?; - Ok((input, list)) - }, - |input: &'a [u8]| -> IResult<&'a [u8], Vec> { - let (input, _) = tag("* SEARCH\r\n")(input)?; - Ok((input, vec![])) - }, - ))(input) -} - -pub fn search_results_raw<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { - alt(( - |input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> { - let (input, _) = tag("* SEARCH ")(input)?; - let (input, list) = take_until("\r\n")(input)?; - let (input, _) = tag("\r\n")(input)?; - Ok((input, list)) - }, - |input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> { - let (input, _) = tag("* SEARCH\r\n")(input)?; - Ok((input, &b""[0..])) - }, - ))(input) -} - -#[test] -fn test_imap_search() { - assert_eq!(search_results(b"* SEARCH\r\n").map(|(_, v)| v), Ok(vec![])); - assert_eq!( - search_results(b"* SEARCH 1\r\n").map(|(_, v)| v), - Ok(vec![1]) - ); - assert_eq!( - search_results(b"* SEARCH 1 2 3 4\r\n").map(|(_, v)| v), - Ok(vec![1, 2, 3, 4]) - ); - assert_eq!( - search_results_raw(b"* SEARCH 1 2 3 4\r\n").map(|(_, v)| v), - Ok(&b"1 2 3 4"[..]) - ); -} - -#[derive(Debug, Default, Clone)] -pub struct SelectResponse { - pub exists: usize, - pub recent: usize, - pub flags: (Flag, Vec), - pub unseen: usize, - pub uidvalidity: usize, - pub uidnext: usize, - pub permanentflags: (Flag, Vec), - /// if SELECT returns \* we can set arbritary flags permanently. - pub can_create_flags: bool, - pub read_only: bool, -} - -/* - * Example: C: A142 SELECT INBOX - * S: * 172 EXISTS - * S: * 1 RECENT - * S: * OK [UNSEEN 12] Message 12 is first unseen - * S: * OK [UIDVALIDITY 3857529045] UIDs valid - * S: * OK [UIDNEXT 4392] Predicted next UID - * S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) - * S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited - * S: A142 OK [READ-WRITE] SELECT completed - */ - -/* - * - * * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) - * * OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted. - * * 45 EXISTS - * * 0 RECENT - * * OK [UNSEEN 16] First unseen. - * * OK [UIDVALIDITY 1554422056] UIDs valid - * * OK [UIDNEXT 50] Predicted next UID - */ -pub fn select_response(input: &str) -> Result { - if input.contains("* OK") { - let mut ret = SelectResponse::default(); - for l in input.split("\r\n") { - if l.starts_with("* ") && l.ends_with(" EXISTS") { - ret.exists = usize::from_str(&l["* ".len()..l.len() - " EXISTS".len()])?; - } else if l.starts_with("* ") && l.ends_with(" RECENT") { - ret.recent = usize::from_str(&l["* ".len()..l.len() - " RECENT".len()])?; - } else if l.starts_with("* FLAGS (") { - ret.flags = flags(&l["* FLAGS (".len()..l.len() - ")".len()]).map(|(_, v)| v)?; - } else if l.starts_with("* OK [UNSEEN ") { - ret.unseen = usize::from_str(&l["* OK [UNSEEN ".len()..l.find(']').unwrap()])?; - } else if l.starts_with("* OK [UIDVALIDITY ") { - ret.uidvalidity = - usize::from_str(&l["* OK [UIDVALIDITY ".len()..l.find(']').unwrap()])?; - } else if l.starts_with("* OK [UIDNEXT ") { - ret.uidnext = usize::from_str(&l["* OK [UIDNEXT ".len()..l.find(']').unwrap()])?; - } else if l.starts_with("* OK [PERMANENTFLAGS (") { - ret.permanentflags = - flags(&l["* OK [PERMANENTFLAGS (".len()..l.find(')').unwrap()]) - .map(|(_, v)| v)?; - ret.can_create_flags = l.contains("\\*"); - } else if l.contains("OK [READ-WRITE]") { - ret.read_only = false; - } else if l.contains("OK [READ-ONLY]") { - ret.read_only = true; - } else if !l.is_empty() { - debug!("select response: {}", l); - } - } - Ok(ret) - } else { - debug!("BAD/NO response in select: {}", input); - Err(MeliError::new(input.to_string())) - } -} - -pub fn flags(input: &str) -> IResult<&str, (Flag, Vec)> { - let mut ret = Flag::default(); - let mut keywords = Vec::new(); - - let mut input = input; - while !input.starts_with(")") && !input.is_empty() { - if input.starts_with("\\") { - input = &input[1..]; - } - let mut match_end = 0; - while match_end < input.len() { - if input[match_end..].starts_with(" ") || input[match_end..].starts_with(")") { - break; - } - match_end += 1; - } - - match &input[..match_end] { - "Answered" => { - ret.set(Flag::REPLIED, true); - } - "Flagged" => { - ret.set(Flag::FLAGGED, true); - } - "Deleted" => { - ret.set(Flag::TRASHED, true); - } - "Seen" => { - ret.set(Flag::SEEN, true); - } - "Draft" => { - ret.set(Flag::DRAFT, true); - } - f => { - keywords.push(f.to_string()); - } - } - input = &input[match_end..]; - input = input.trim_start(); - } - Ok((input, (ret, keywords))) -} - -pub fn byte_flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec)> { - let i = unsafe { std::str::from_utf8_unchecked(input) }; - match flags(i) { - Ok((rest, ret)) => Ok((rest.as_bytes(), ret)), - Err(nom::Err::Error(err)) => Err(nom::Err::Error(err.as_bytes())), - Err(nom::Err::Failure(err)) => Err(nom::Err::Error(err.as_bytes())), - Err(nom::Err::Incomplete(_)) => { - Err(nom::Err::Error((input, "byte_flags(): incomplete").into())) - } - } -} - -/* -* The fields of the envelope structure are in the following -* order: date, subject, from, sender, reply-to, to, cc, bcc, -* in-reply-to, and message-id. The date, subject, in-reply-to, -* and message-id fields are strings. The from, sender, reply-to, -* to, cc, and bcc fields are parenthesized lists of address -* structures. -* An address structure is a parenthesized list that describes an -* electronic mail address. The fields of an address structure -* are in the following order: personal name, [SMTP] -* at-domain-list (source route), mailbox name, and host name. -*/ - -/* -* * 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" -* RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" -* "IMAP4rev1 WG mtg summary and minutes" -* (("Terry Gray" NIL "gray" "cac.washington.edu")) -* (("Terry Gray" NIL "gray" "cac.washington.edu")) -* (("Terry Gray" NIL "gray" "cac.washington.edu")) -* ((NIL NIL "imap" "cac.washington.edu")) -* ((NIL NIL "minutes" "CNRI.Reston.VA.US") -* ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL -* "") -*/ - -pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> { - let (input, _) = tag("(")(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, date) = quoted_or_nil(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, subject) = quoted_or_nil(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, from) = envelope_addresses(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, _sender) = envelope_addresses(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, _reply_to) = envelope_addresses(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, to) = envelope_addresses(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, cc) = envelope_addresses(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, bcc) = envelope_addresses(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, in_reply_to) = quoted_or_nil(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, message_id) = quoted_or_nil(input)?; - let (input, _) = opt(is_a("\r\n\t "))(input)?; - let (input, _) = tag(")")(input)?; - Ok(( - input, - ({ - let mut env = Envelope::new(0); - if let Some(date) = date { - env.set_date(&date); - if let Ok(d) = crate::email::parser::generic::date(env.date_as_str().as_bytes()) { - env.set_datetime(d); - } - } - - if let Some(subject) = subject { - env.set_subject(subject.to_vec()); - } - - if let Some(from) = from { - env.set_from(from); - } - if let Some(to) = to { - env.set_to(to); - } - - if let Some(cc) = cc { - env.set_cc(cc); - } - - if let Some(bcc) = bcc { - env.set_bcc(bcc); - } - if let Some(in_reply_to) = in_reply_to { - env.set_in_reply_to(&in_reply_to); - env.push_references(&in_reply_to); - } - - if let Some(message_id) = message_id { - env.set_message_id(&message_id); - } - env - }), - )) - /* - do_parse!( - tag!("(") - >> opt!(is_a!("\r\n\t ")) - >> date: quoted_or_nil - >> opt!(is_a!("\r\n\t ")) - >> subject: quoted_or_nil - >> opt!(is_a!("\r\n\t ")) - >> from: envelope_addresses - >> opt!(is_a!("\r\n\t ")) - >> sender: envelope_addresses - >> opt!(is_a!("\r\n\t ")) - >> reply_to: envelope_addresses - >> opt!(is_a!("\r\n\t ")) - >> to: envelope_addresses - >> opt!(is_a!("\r\n\t ")) - >> cc: envelope_addresses - >> opt!(is_a!("\r\n\t ")) - >> bcc: envelope_addresses - >> opt!(is_a!("\r\n\t ")) - >> in_reply_to: quoted_or_nil - >> opt!(is_a!("\r\n\t ")) - >> message_id: quoted_or_nil - >> opt!(is_a!("\r\n\t ")) - >> tag!(")") - >> ({ - let mut env = Envelope::new(0); - if let Some(date) = date { - env.set_date(&date); - if let Ok(d) = crate::email::parser::generic::date(env.date_as_str().as_bytes()) { - env.set_datetime(d); - } - } - - if let Some(subject) = subject { - env.set_subject(subject.to_vec()); - } - - if let Some(from) = from { - env.set_from(from); - } - if let Some(to) = to { - env.set_to(to); - } - - if let Some(cc) = cc { - env.set_cc(cc); - } - - if let Some(bcc) = bcc { - env.set_bcc(bcc); - } - if let Some(in_reply_to) = in_reply_to { - env.set_in_reply_to(&in_reply_to); - env.push_references(&in_reply_to); - } - - if let Some(message_id) = message_id { - env.set_message_id(&message_id); - } - env - }) - */ -} - -/* Helper to build StrBuilder for Address structs */ -macro_rules! str_builder { - ($offset:expr, $length:expr) => { - StrBuilder { - offset: $offset, - length: $length, - } - }; -} - -// Parse a list of addresses in the format of the ENVELOPE structure -pub fn envelope_addresses<'a>(input: &'a [u8]) -> IResult<&'a [u8], Option>> { - alt(( - map(tag("NIL"), |_| None), - |input: &'a [u8]| -> IResult<&'a [u8], Option>> { - let (input, _) = tag("(")(input)?; - let (input, envelopes) = - many1(delimited(tag("("), envelope_address, tag(")")))(input.ltrim())?; - let (input, _) = tag(")")(input)?; - Ok((input, Some(envelopes))) - }, - map(tag("\"\""), |_| None), - ))(input) - /* - alt_complete!(map!(tag!("NIL"), |_| None) | - do_parse!( - tag!("(") - >> envelopes: many1!(delimited!(ws!(tag!("(")), envelope_address, tag!(")"))) - >> tag!(")") - >> ({ - Some(envelopes) - }) - ) - )); - */ -} - -// Parse an address in the format of the ENVELOPE structure eg -// ("Terry Gray" NIL "gray" "cac.washington.edu") -pub fn envelope_address(input: &[u8]) -> IResult<&[u8], Address> { - let (input, name) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?; - let (input, _) = is_a("\r\n\t ")(input)?; - let (input, _) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?; - let (input, _) = is_a("\r\n\t ")(input)?; - let (input, mailbox_name) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?; - let (input, host_name) = opt(preceded( - is_a("\r\n\t "), - alt((quoted, map(tag("NIL"), |_| Vec::new()))), - ))(input)?; - Ok(( - input, - Address::Mailbox(MailboxAddress { - raw: if let Some(host_name) = host_name.as_ref() { - format!( - "{}{}<{}@{}>", - to_str!(&name), - if name.is_empty() { "" } else { " " }, - to_str!(&mailbox_name), - to_str!(&host_name) - ) - .into_bytes() - } else { - format!( - "{}{}{}", - to_str!(&name), - if name.is_empty() { "" } else { " " }, - to_str!(&mailbox_name), - ) - .into_bytes() - }, - display_name: str_builder!(0, name.len()), - address_spec: if let Some(host_name) = host_name.as_ref() { - str_builder!( - if name.is_empty() { 1 } else { name.len() + 2 }, - mailbox_name.len() + host_name.len() + 1 - ) - } else { - str_builder!( - if name.is_empty() { 0 } else { name.len() + 1 }, - mailbox_name.len() - ) - }, - }), - )) - /* - do_parse!( - name: alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new())) - >> is_a!("\r\n\t ") - >> alt_complete!(quoted| map!(tag!("NIL"), |_| Vec::new())) - >> is_a!("\r\n\t ") - >> mailbox_name: dbg_dmp!(alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new()))) - >> is_a!("\r\n\t ") - >> host_name: alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new())) - >> ({ - Address::Mailbox(MailboxAddress { - raw: format!("{}{}<{}@{}>", to_str!(&name), if name.is_empty() { "" } else { " " }, to_str!(&mailbox_name), to_str!(&host_name)).into_bytes(), - display_name: str_builder!(0, name.len()), - address_spec: str_builder!(if name.is_empty() { 1 } else { name.len() + 2 }, mailbox_name.len() + host_name.len() + 1), - }) - }) - )); - */ -} - -// Read a literal ie a byte sequence prefixed with a tag containing its length delimited in {}s -pub fn literal(input: &[u8]) -> IResult<&[u8], &[u8]> { - length_data(delimited( - tag("{"), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - tag("}\r\n"), - ))(input) -} - -// Return a byte sequence surrounded by "s and decoded if necessary -pub fn quoted(input: &[u8]) -> IResult<&[u8], Vec> { - if let Ok((r, o)) = literal(input) { - return match crate::email::parser::encodings::phrase(o, false) { - Ok((_, out)) => Ok((r, out)), - e => e, - }; - } - if input.is_empty() || input[0] != b'"' { - return Err(nom::Err::Error((input, "quoted(): EOF").into())); - } - - let mut i = 1; - while i < input.len() { - if input[i] == b'\"' && input[i - 1] != b'\\' { - return match crate::email::parser::encodings::phrase(&input[1..i], false) { - Ok((_, out)) => Ok((&input[i + 1..], out)), - e => e, - }; - } - i += 1; - } - - Err(nom::Err::Error( - (input, "quoted(): not a quoted phrase").into(), - )) -} - -pub fn quoted_or_nil(input: &[u8]) -> IResult<&[u8], Option>> { - alt((map(tag("NIL"), |_| None), map(quoted, |v| Some(v))))(input.ltrim()) - /* - alt_complete!(map!(ws!(tag!("NIL")), |_| None) | map!(quoted, |v| Some(v)))); - */ -} - -pub fn uid_fetch_envelopes_response( - input: &[u8], -) -> IResult<&[u8], Vec<(usize, Option<(Flag, Vec)>, Envelope)>> { - many0( - |input: &[u8]| -> IResult<&[u8], (usize, Option<(Flag, Vec)>, Envelope)> { - let (input, _) = tag("* ")(input)?; - let (input, _) = take_while(is_digit)(input)?; - let (input, _) = tag(" FETCH (")(input)?; - let (input, uid_flags) = permutation(( - preceded( - alt((tag("UID "), tag(" UID "))), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - ), - opt(preceded( - alt((tag("FLAGS "), tag(" FLAGS "))), - delimited(tag("("), byte_flags, tag(")")), - )), - ))(input.ltrim())?; - let (input, _) = tag(" ENVELOPE ")(input)?; - let (input, env) = envelope(input.ltrim())?; - let (input, _) = tag("BODYSTRUCTURE ")(input)?; - let (input, bodystructure) = take_until(")\r\n")(input)?; - let (input, _) = tag(")\r\n")(input)?; - Ok((input, { - let mut env = env; - let has_attachments = bodystructure_has_attachments(bodystructure); - env.set_has_attachments(has_attachments); - (uid_flags.0, uid_flags.1, env) - })) - }, - )(input) - /* - many0!( - do_parse!( - tag!("* ") - >> take_while!(call!(is_digit)) - >> tag!(" FETCH (") - >> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit1, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), opt!(preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")"))))) - >> tag!(" ENVELOPE ") - >> env: ws!(envelope) - >> tag!("BODYSTRUCTURE ") - >> bodystructure: take_until!(")\r\n") - >> tag!(")\r\n") - >> ({ - let mut env = env; - let has_attachments = bodystructure_has_attachments(bodystructure); - env.set_has_attachments(has_attachments); - (uid_flags.0, uid_flags.1, env) - }) - ) - ) - ); - */ -} - -pub fn bodystructure_has_attachments(input: &[u8]) -> bool { - input.rfind(b" \"mixed\" ").is_some() || input.rfind(b" \"MIXED\" ").is_some() -} - -#[derive(Debug, Default, Clone)] -pub struct StatusResponse { - pub messages: Option, - pub recent: Option, - pub uidnext: Option, - pub uidvalidity: Option, - pub unseen: Option, -} - -// status = "STATUS" SP mailbox SP "(" status-att *(SP status-att) ")" -// status-att = "MESSAGES" / "RECENT" / "UIDNEXT" / "UIDVALIDITY" / "UNSEEN" -pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> { - let (input, _) = tag("* STATUS ")(input)?; - let (input, _) = take_until(" (")(input)?; - let (input, _) = tag(" (")(input)?; - let (input, result) = permutation(( - opt(preceded( - alt((tag("MESSAGES "), tag(" MESSAGES "))), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - )), - opt(preceded( - alt((tag("RECENT "), tag(" RECENT "))), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - )), - opt(preceded( - alt((tag("UIDNEXT "), tag(" UIDNEXT "))), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - )), - opt(preceded( - alt((tag("UIDVALIDITY "), tag(" UIDVALIDITY "))), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - )), - opt(preceded( - alt((tag("UNSEEN "), tag(" UNSEEN "))), - map_res(digit1, |s| { - usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) - }), - )), - ))(input)?; - let (input, _) = tag(")\r\n")(input)?; - Ok(( - input, - StatusResponse { - messages: result.0, - recent: result.1, - uidnext: result.2, - uidvalidity: result.3, - unseen: result.4, - }, - )) -} - -// mailbox = "INBOX" / astring -// ; INBOX is case-insensitive. All case variants of -// ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX -// ; not as an astring. An astring which consists of -// ; the case-insensitive sequence "I" "N" "B" "O" "X" -// ; is considered to be INBOX and not an astring. -// ; Refer to section 5.1 for further -// ; semantic details of mailbox names. -pub fn mailbox_token<'i>(input: &'i [u8]) -> IResult<&'i [u8], &'i str> { - let (input, astring) = astring_token(input)?; - if astring.eq_ignore_ascii_case(b"INBOX") { - return Ok((input, "INBOX")); - } - Ok((input, to_str!(astring))) -} - -// astring = 1*ASTRING-CHAR / string -fn astring_token(input: &[u8]) -> IResult<&[u8], &[u8]> { - alt((string_token, astring_char_tokens))(input) -} - -// string = quoted / literal -fn string_token(input: &[u8]) -> IResult<&[u8], &[u8]> { - if let Ok((r, o)) = literal(input) { - return Ok((r, o)); - } - if input.is_empty() || input[0] != b'"' { - return Err(nom::Err::Error((input, "string_token(): EOF").into())); - } - - let mut i = 1; - while i < input.len() { - if input[i] == b'\"' && input[i - 1] != b'\\' { - return Ok((&input[i + 1..], &input[1..i])); - } - i += 1; - } - - Err(nom::Err::Error( - (input, "string_token(): not a quoted phrase").into(), - )) -} - -// ASTRING-CHAR = ATOM-CHAR / resp-specials -// atom = 1*ATOM-CHAR -// ATOM-CHAR = -// atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / quoted-specials / resp-specials -fn astring_char_tokens(input: &[u8]) -> IResult<&[u8], &[u8]> { - // FIXME - is_not(" ")(input) -} diff --git a/melib/src/backends/imap_async/untagged.rs b/melib/src/backends/imap_async/untagged.rs deleted file mode 100644 index 907b085a..00000000 --- a/melib/src/backends/imap_async/untagged.rs +++ /dev/null @@ -1,332 +0,0 @@ -/* - * meli - imap - * - * Copyright 2020 Manos Pitsidianakis - * - * 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 . - */ - -use super::{ImapConnection, MailboxSelection}; -use crate::backends::imap_async::protocol_parser::{ - ImapLineSplit, RequiredResponses, UidFetchResponse, UntaggedResponse, -}; -use crate::backends::BackendMailbox; -use crate::backends::{ - RefreshEvent, - RefreshEventKind::{self, *}, -}; -use crate::email::Envelope; -use crate::error::*; -use std::time::Instant; - -impl ImapConnection { - pub async fn process_untagged(&mut self, line: &str) -> Result { - macro_rules! try_fail { - ($mailbox_hash: expr, $($result:expr)+) => { - $(if let Err(err) = $result { - *self.uid_store.is_online.lock().unwrap() = ( - Instant::now(), - Err(err.clone()), - ); - debug!("failure: {}", err.to_string()); - self.add_refresh_event(RefreshEvent { - account_hash: self.uid_store.account_hash, - mailbox_hash: $mailbox_hash, - kind: RefreshEventKind::Failure(err.clone()), - }); - Err(err) - } else { Ok(()) }?;)+ - }; - } - //FIXME - let mailbox_hash = match self.current_mailbox { - MailboxSelection::Select(h) | MailboxSelection::Examine(h) => h, - MailboxSelection::None => return Ok(false), - }; - let mailbox = - std::clone::Clone::clone(&self.uid_store.mailboxes.lock().await[&mailbox_hash]); - - let mut response = String::with_capacity(8 * 1024); - let untagged_response = - match super::protocol_parser::untagged_responses(line.as_bytes()).map(|(_, v)| v) { - Ok(None) | Err(_) => { - return Ok(false); - } - Ok(Some(r)) => r, - }; - match untagged_response { - UntaggedResponse::Bye { reason } => { - *self.uid_store.is_online.lock().unwrap() = - (std::time::Instant::now(), Err(reason.into())); - } - UntaggedResponse::Expunge(n) => { - let deleted_uid = self - .uid_store - .msn_index - .lock() - .unwrap() - .entry(mailbox_hash) - .or_default() - .remove(n); - debug!("expunge {}, UID = {}", n, deleted_uid); - let deleted_hash: crate::email::EnvelopeHash = self - .uid_store - .uid_index - .lock() - .unwrap() - .remove(&(mailbox_hash, deleted_uid)) - .unwrap(); - self.uid_store - .hash_index - .lock() - .unwrap() - .remove(&deleted_hash); - self.add_refresh_event(RefreshEvent { - account_hash: self.uid_store.account_hash, - mailbox_hash, - kind: Remove(deleted_hash), - }); - } - UntaggedResponse::Exists(n) => { - /* UID FETCH ALL UID, cross-ref, then FETCH difference headers - * */ - debug!("exists {}", n); - if n > mailbox.exists.lock().unwrap().len() { - try_fail!( - mailbox_hash, - self.send_command( - &[ - b"FETCH", - format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(), - b"(UID FLAGS RFC822)", - ] - .join(&b' '), - ).await - self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await - ); - match super::protocol_parser::uid_fetch_responses(&response) { - Ok((_, v, _)) => { - 'fetch_responses: for UidFetchResponse { - uid, flags, body, .. - } in v - { - if self - .uid_store - .uid_index - .lock() - .unwrap() - .contains_key(&(mailbox_hash, uid)) - { - continue 'fetch_responses; - } - if let Ok(mut env) = Envelope::from_bytes( - body.unwrap(), - flags.as_ref().map(|&(f, _)| f), - ) { - self.uid_store - .hash_index - .lock() - .unwrap() - .insert(env.hash(), (uid, mailbox_hash)); - self.uid_store - .uid_index - .lock() - .unwrap() - .insert((mailbox_hash, uid), env.hash()); - if let Some((_, keywords)) = flags { - let mut tag_lck = self.uid_store.tag_index.write().unwrap(); - 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); - } - } - debug!( - "Create event {} {} {}", - env.hash(), - env.subject(), - mailbox.path(), - ); - if !env.is_seen() { - mailbox.unseen.lock().unwrap().insert_new(env.hash()); - } - mailbox.exists.lock().unwrap().insert_new(env.hash()); - self.add_refresh_event(RefreshEvent { - account_hash: self.uid_store.account_hash, - mailbox_hash, - kind: Create(Box::new(env)), - }); - } - } - } - Err(e) => { - debug!(e); - } - } - } - } - UntaggedResponse::Recent(_) => { - try_fail!( - mailbox_hash, - self.send_command(b"UID SEARCH RECENT").await - self.read_response(&mut response, RequiredResponses::SEARCH).await - ); - match super::protocol_parser::search_results_raw(response.as_bytes()) - .map(|(_, v)| v) - .map_err(MeliError::from) - { - Ok(&[]) => { - debug!("UID SEARCH RECENT returned no results"); - } - Ok(v) => { - try_fail!( - mailbox_hash, - self.send_command( - &[b"UID FETCH", v, b"(FLAGS RFC822)"] - .join(&b' '), - ).await - self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await - ); - debug!(&response); - match super::protocol_parser::uid_fetch_responses(&response) { - Ok((_, v, _)) => { - for UidFetchResponse { - uid, flags, body, .. - } in v - { - if !self - .uid_store - .uid_index - .lock() - .unwrap() - .contains_key(&(mailbox_hash, uid)) - { - if let Ok(mut env) = Envelope::from_bytes( - body.unwrap(), - flags.as_ref().map(|&(f, _)| f), - ) { - self.uid_store - .hash_index - .lock() - .unwrap() - .insert(env.hash(), (uid, mailbox_hash)); - self.uid_store - .uid_index - .lock() - .unwrap() - .insert((mailbox_hash, uid), env.hash()); - debug!( - "Create event {} {} {}", - env.hash(), - env.subject(), - mailbox.path(), - ); - if let Some((_, keywords)) = flags { - let mut tag_lck = - self.uid_store.tag_index.write().unwrap(); - 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); - } - } - if !env.is_seen() { - mailbox - .unseen - .lock() - .unwrap() - .insert_new(env.hash()); - } - - mailbox.exists.lock().unwrap().insert_new(env.hash()); - self.add_refresh_event(RefreshEvent { - account_hash: self.uid_store.account_hash, - mailbox_hash, - kind: Create(Box::new(env)), - }); - } - } - } - } - Err(e) => { - debug!(e); - } - } - } - Err(e) => { - debug!( - "UID SEARCH RECENT err: {}\nresp: {}", - e.to_string(), - &response - ); - } - } - } - UntaggedResponse::Fetch(msg_seq, flags) => { - /* a * {msg_seq} FETCH (FLAGS ({flags})) was received, so find out UID from msg_seq - * and send update - */ - debug!("fetch {} {:?}", msg_seq, flags); - try_fail!( - mailbox_hash, - self.send_command( - &[ - b"UID SEARCH ", - format!("{}", msg_seq).as_bytes(), - ] - .join(&b' '), - ).await - self.read_response(&mut response, RequiredResponses::SEARCH).await - ); - debug!(&response); - match super::protocol_parser::search_results( - response.split_rn().next().unwrap_or("").as_bytes(), - ) - .map(|(_, v)| v) - { - Ok(mut v) => { - if let Some(uid) = v.pop() { - let lck = self.uid_store.uid_index.lock().unwrap(); - let env_hash = lck.get(&(mailbox_hash, uid)).copied(); - drop(lck); - if let Some(env_hash) = env_hash { - if !flags.0.intersects(crate::email::Flag::SEEN) { - mailbox.unseen.lock().unwrap().insert_new(env_hash); - } else { - mailbox.unseen.lock().unwrap().remove(env_hash); - } - self.add_refresh_event(RefreshEvent { - account_hash: self.uid_store.account_hash, - mailbox_hash, - kind: NewFlags(env_hash, flags), - }); - }; - } - } - Err(e) => { - debug!(&response); - debug!(e); - } - } - } - } - Ok(true) - } -} diff --git a/melib/src/backends/imap_async/watch.rs b/melib/src/backends/imap_async/watch.rs deleted file mode 100644 index bdb7d3b5..00000000 --- a/melib/src/backends/imap_async/watch.rs +++ /dev/null @@ -1,770 +0,0 @@ -/* - * meli - imap module. - * - * Copyright 2019 Manos Pitsidianakis - * - * 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 . - */ -use super::*; -use crate::backends::SpecialUsageMailbox; -use crate::email::parser::BytesExt; -use crate::email::parser::BytesIterExt; -use std::sync::Arc; - -/// Arguments for IMAP watching functions -pub struct ImapWatchKit { - pub conn: ImapConnection, - pub main_conn: Arc>, - pub uid_store: Arc, -} - -macro_rules! exit_on_error { - ($conn:expr, $mailbox_hash:ident, $($result:expr)+) => { - $(if let Err(e) = $result { - *$conn.uid_store.is_online.lock().unwrap() = ( - Instant::now(), - Err(e.clone()), - ); - debug!("failure: {}", e.to_string()); - let account_hash = $conn.uid_store.account_hash; - $conn.add_refresh_event(RefreshEvent { - account_hash, - mailbox_hash: $mailbox_hash, - kind: RefreshEventKind::Failure(e.clone()), - }); - Err(e) - } else { Ok(()) }?;)+ - }; -} - -pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> { - debug!("poll with examine"); - let ImapWatchKit { - mut conn, - main_conn, - uid_store, - } = kit; - conn.connect().await?; - let mut response = String::with_capacity(8 * 1024); - loop { - let mailboxes = uid_store.mailboxes.lock().await; - for mailbox in mailboxes.values() { - examine_updates(mailbox, &mut conn, &uid_store).await?; - } - let mut main_conn = main_conn.lock().await; - main_conn.send_command(b"NOOP").await?; - main_conn - .read_response(&mut response, RequiredResponses::empty()) - .await?; - } -} - -pub async fn idle(kit: ImapWatchKit) -> Result<()> { - debug!("IDLE"); - /* IDLE only watches the connection's selected mailbox. We will IDLE on INBOX and every ~5 - * minutes wake up and poll the others */ - let ImapWatchKit { - mut conn, - main_conn, - uid_store, - } = kit; - conn.connect().await?; - let mailbox: ImapMailbox = match uid_store - .mailboxes - .lock() - .await - .values() - .find(|f| f.parent.is_none() && (f.special_usage() == SpecialUsageMailbox::Inbox)) - .map(std::clone::Clone::clone) - { - Some(mailbox) => mailbox, - None => { - let err = MeliError::new("INBOX mailbox not found in local mailbox index. meli may have not parsed the IMAP mailboxes correctly"); - debug!("failure: {}", err.to_string()); - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash: 0, - kind: RefreshEventKind::Failure(err.clone()), - }); - return Err(err); - } - }; - let mailbox_hash = mailbox.hash(); - let uidvalidity; - let mut response = String::with_capacity(8 * 1024); - exit_on_error!( - conn, - mailbox_hash, - conn.send_command(format!("SELECT \"{}\"", mailbox.imap_path()).as_bytes()) - .await - conn.read_response(&mut response, RequiredResponses::SELECT_REQUIRED) - .await - ); - debug!("select response {}", &response); - { - let mut prev_exists = mailbox.exists.lock().unwrap(); - match protocol_parser::select_response(&response) { - Ok(ok) => { - { - uidvalidity = ok.uidvalidity; - let mut uidvalidities = uid_store.uidvalidity.lock().unwrap(); - - if let Some(v) = uidvalidities.get_mut(&mailbox_hash) { - if *v != ok.uidvalidity { - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: RefreshEventKind::Rescan, - }); - prev_exists.clear(); - /* - uid_store.uid_index.lock().unwrap().clear(); - uid_store.hash_index.lock().unwrap().clear(); - uid_store.byte_cache.lock().unwrap().clear(); - */ - *v = ok.uidvalidity; - } - } else { - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: RefreshEventKind::Rescan, - }); - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: RefreshEventKind::Failure(MeliError::new(format!( - "Unknown mailbox: {} {}", - mailbox.path(), - mailbox_hash - ))), - }); - } - } - debug!(&ok); - } - Err(e) => { - debug!("{:?}", e); - return Err(e).chain_err_summary(|| "could not select mailbox"); - } - }; - } - exit_on_error!(conn, mailbox_hash, conn.send_command(b"IDLE").await); - let mut blockn = ImapBlockingConnection::from(conn); - let mut beat = std::time::Instant::now(); - let mut watch = std::time::Instant::now(); - /* duration interval to send heartbeat */ - const _26_MINS: std::time::Duration = std::time::Duration::from_secs(26 * 60); - /* duration interval to check other mailboxes for changes */ - const _5_MINS: std::time::Duration = std::time::Duration::from_secs(5 * 60); - while let Some(line) = blockn.as_stream().await { - let now = std::time::Instant::now(); - if now.duration_since(beat) >= _26_MINS { - let mut main_conn_lck = main_conn.lock().await; - exit_on_error!( - blockn.conn, - mailbox_hash, - blockn.conn.send_raw(b"DONE").await - blockn.conn.read_response(&mut response, RequiredResponses::empty()).await - blockn.conn.send_command(b"IDLE").await - main_conn_lck.send_command(b"NOOP").await - main_conn_lck.read_response(&mut response, RequiredResponses::empty()).await - ); - beat = now; - } - if now.duration_since(watch) >= _5_MINS { - /* Time to poll all inboxes */ - let mut conn = main_conn.lock().await; - let mailboxes = uid_store.mailboxes.lock().await; - for mailbox in mailboxes.values() { - exit_on_error!( - conn, - mailbox_hash, - examine_updates(mailbox, &mut conn, &uid_store).await - ); - } - watch = now; - } - *uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(())); - match protocol_parser::untagged_responses(line.as_slice()) - .map(|(_, v)| v) - .map_err(MeliError::from) - { - Ok(Some(Recent(_r))) => { - let mut conn = main_conn.lock().await; - /* UID SEARCH RECENT */ - exit_on_error!( - conn, - mailbox_hash, - conn.examine_mailbox(mailbox_hash, &mut response, false).await - conn.send_command(b"UID SEARCH RECENT").await - conn.read_response(&mut response, RequiredResponses::SEARCH).await - ); - match protocol_parser::search_results_raw(response.as_bytes()) - .map(|(_, v)| v) - .map_err(MeliError::from) - { - Ok(&[]) => { - debug!("UID SEARCH RECENT returned no results"); - } - Ok(v) => { - exit_on_error!( - conn, - mailbox_hash, - conn.send_command( - &[&b"UID FETCH"[..], &v.trim().split(|b| b == &b' ').join(b','), &b"(FLAGS RFC822)"[..]] - .join(&b' '), - ).await - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await - ); - debug!(&response); - match protocol_parser::uid_fetch_responses(&response) { - Ok((_, v, _)) => { - for UidFetchResponse { - uid, flags, body, .. - } in v - { - if !uid_store - .uid_index - .lock() - .unwrap() - .contains_key(&(mailbox_hash, uid)) - { - if let Ok(mut env) = Envelope::from_bytes( - /* unwrap() is safe since we ask for RFC822 in the - * above FETCH, thus uid_fetch_responses() if - * returns a successful parse, it will include the - * RFC822 response */ - body.unwrap(), - flags.as_ref().map(|&(f, _)| f), - ) { - uid_store - .hash_index - .lock() - .unwrap() - .insert(env.hash(), (uid, mailbox_hash)); - uid_store - .uid_index - .lock() - .unwrap() - .insert((mailbox_hash, uid), env.hash()); - debug!( - "Create event {} {} {}", - env.hash(), - env.subject(), - mailbox.path(), - ); - if let Some((_, keywords)) = flags { - let mut tag_lck = - uid_store.tag_index.write().unwrap(); - 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); - } - } - if !env.is_seen() { - mailbox - .unseen - .lock() - .unwrap() - .insert_new(env.hash()); - } - if uid_store.cache_headers { - cache::save_envelopes( - uid_store.account_hash, - mailbox_hash, - uidvalidity, - &[(uid, &env)], - )?; - } - mailbox.exists.lock().unwrap().insert_new(env.hash()); - - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: Create(Box::new(env)), - }); - } - } - } - } - Err(e) => { - debug!(e); - } - } - } - Err(e) => { - debug!( - "UID SEARCH RECENT err: {}\nresp: {}", - e.to_string(), - &response - ); - } - } - } - Ok(Some(Expunge(n))) => { - // The EXPUNGE response reports that the specified message sequence - // number has been permanently removed from the mailbox. The message - // sequence number for each successive message in the mailbox is - // immediately decremented by 1, and this decrement is reflected in - // message sequence numbers in subsequent responses (including other - // untagged EXPUNGE responses). - let mut conn = main_conn.lock().await; - let deleted_uid = uid_store - .msn_index - .lock() - .unwrap() - .entry(mailbox_hash) - .or_default() - .remove(n); - debug!("expunge {}, UID = {}", n, deleted_uid); - let deleted_hash: EnvelopeHash = uid_store - .uid_index - .lock() - .unwrap() - .remove(&(mailbox_hash, deleted_uid)) - .unwrap(); - uid_store.hash_index.lock().unwrap().remove(&deleted_hash); - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: Remove(deleted_hash), - }); - } - Ok(Some(Exists(n))) => { - let mut conn = main_conn.lock().await; - /* UID FETCH ALL UID, cross-ref, then FETCH difference headers - * */ - debug!("exists {}", n); - if n > mailbox.exists.lock().unwrap().len() { - exit_on_error!( - conn, - mailbox_hash, - conn.examine_mailbox(mailbox_hash, &mut response, false).await - conn.send_command( - &[ - b"FETCH", - format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(), - b"(UID FLAGS RFC822)", - ] - .join(&b' '), - ).await - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await - ); - match protocol_parser::uid_fetch_responses(&response) { - Ok((_, v, _)) => { - 'fetch_responses_b: for UidFetchResponse { - uid, flags, body, .. - } in v - { - if uid_store - .uid_index - .lock() - .unwrap() - .contains_key(&(mailbox_hash, uid)) - { - continue 'fetch_responses_b; - } - if let Ok(mut env) = Envelope::from_bytes( - body.unwrap(), - flags.as_ref().map(|&(f, _)| f), - ) { - uid_store - .hash_index - .lock() - .unwrap() - .insert(env.hash(), (uid, mailbox_hash)); - uid_store - .uid_index - .lock() - .unwrap() - .insert((mailbox_hash, uid), env.hash()); - if let Some((_, keywords)) = flags { - let mut tag_lck = uid_store.tag_index.write().unwrap(); - 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); - } - } - debug!( - "Create event {} {} {}", - env.hash(), - env.subject(), - mailbox.path(), - ); - if !env.is_seen() { - mailbox.unseen.lock().unwrap().insert_new(env.hash()); - } - if uid_store.cache_headers { - cache::save_envelopes( - uid_store.account_hash, - mailbox_hash, - uidvalidity, - &[(uid, &env)], - )?; - } - mailbox.exists.lock().unwrap().insert_new(env.hash()); - - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: Create(Box::new(env)), - }); - } - } - } - Err(e) => { - debug!(e); - } - } - } - } - Ok(Some(Fetch(msg_seq, flags))) => { - /* a * {msg_seq} FETCH (FLAGS ({flags})) was received, so find out UID from msg_seq - * and send update - */ - let mut conn = main_conn.lock().await; - debug!("fetch {} {:?}", msg_seq, flags); - exit_on_error!( - conn, - mailbox_hash, - conn.examine_mailbox(mailbox_hash, &mut response, false).await - conn.send_command( - &[ - b"UID SEARCH ", - format!("{}", msg_seq).as_bytes(), - ] - .join(&b' '), - ).await - conn.read_response(&mut response, RequiredResponses::SEARCH).await - ); - match search_results(response.split_rn().next().unwrap_or("").as_bytes()) - .map(|(_, v)| v) - { - Ok(mut v) => { - if let Some(uid) = v.pop() { - if let Some(env_hash) = uid_store - .uid_index - .lock() - .unwrap() - .get(&(mailbox_hash, uid)) - { - if !flags.0.intersects(crate::email::Flag::SEEN) { - mailbox.unseen.lock().unwrap().insert_new(*env_hash); - } else { - mailbox.unseen.lock().unwrap().remove(*env_hash); - } - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: NewFlags(*env_hash, flags), - }); - } - } - } - Err(e) => { - debug!(&response); - debug!(e); - } - } - } - Ok(Some(Bye { .. })) => break, - Ok(None) | Err(_) => {} - } - } - debug!("IDLE connection dropped"); - let err: &str = blockn.err().unwrap_or("Unknown reason."); - main_conn.lock().await.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: RefreshEventKind::Failure(MeliError::new(format!( - "IDLE connection dropped: {}", - &err - ))), - }); - Err(MeliError::new(format!("IDLE connection dropped: {}", err))) -} - -pub async fn examine_updates( - mailbox: &ImapMailbox, - conn: &mut ImapConnection, - uid_store: &Arc, -) -> Result<()> { - let mailbox_hash = mailbox.hash(); - debug!("examining mailbox {} {}", mailbox_hash, mailbox.path()); - let mut response = String::with_capacity(8 * 1024); - exit_on_error!( - conn, - mailbox_hash, - conn.examine_mailbox(mailbox_hash, &mut response, true) - .await - ); - *uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(())); - let uidvalidity; - match protocol_parser::select_response(&response) { - Ok(ok) => { - uidvalidity = ok.uidvalidity; - debug!(&ok); - { - let mut uidvalidities = uid_store.uidvalidity.lock().unwrap(); - - if let Some(v) = uidvalidities.get_mut(&mailbox_hash) { - if *v != ok.uidvalidity { - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: RefreshEventKind::Rescan, - }); - /* - uid_store.uid_index.lock().unwrap().clear(); - uid_store.hash_index.lock().unwrap().clear(); - uid_store.byte_cache.lock().unwrap().clear(); - */ - *v = ok.uidvalidity; - } - } else { - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: RefreshEventKind::Rescan, - }); - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: RefreshEventKind::Failure(MeliError::new(format!( - "Unknown mailbox: {} {}", - mailbox.path(), - mailbox_hash - ))), - }); - } - } - let n = ok.exists; - if ok.recent > 0 { - { - /* UID SEARCH RECENT */ - exit_on_error!( - conn, - mailbox_hash, - conn.send_command(b"UID SEARCH RECENT").await - conn.read_response(&mut response, RequiredResponses::SEARCH).await - ); - match protocol_parser::search_results_raw(response.as_bytes()) - .map(|(_, v)| v) - .map_err(MeliError::from) - { - Ok(&[]) => { - debug!("UID SEARCH RECENT returned no results"); - } - Ok(v) => { - exit_on_error!( - conn, - mailbox_hash, - conn.send_command( - &[&b"UID FETCH"[..], &v.trim().split(|b| b == &b' ').join(b','), &b"(FLAGS RFC822)"[..]] - .join(&b' '), - ).await - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await - ); - debug!(&response); - match protocol_parser::uid_fetch_responses(&response) { - Ok((_, v, _)) => { - 'fetch_responses_c: for UidFetchResponse { - uid, - flags, - body, - .. - } in v - { - if uid_store - .uid_index - .lock() - .unwrap() - .contains_key(&(mailbox_hash, uid)) - { - continue 'fetch_responses_c; - } - if let Ok(mut env) = Envelope::from_bytes( - body.unwrap(), - flags.as_ref().map(|&(f, _)| f), - ) { - uid_store - .hash_index - .lock() - .unwrap() - .insert(env.hash(), (uid, mailbox_hash)); - uid_store - .uid_index - .lock() - .unwrap() - .insert((mailbox_hash, uid), env.hash()); - debug!( - "Create event {} {} {}", - env.hash(), - env.subject(), - mailbox.path(), - ); - if let Some((_, keywords)) = flags { - let mut tag_lck = - uid_store.tag_index.write().unwrap(); - 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); - } - } - if !env.is_seen() { - mailbox - .unseen - .lock() - .unwrap() - .insert_new(env.hash()); - } - if uid_store.cache_headers { - cache::save_envelopes( - uid_store.account_hash, - mailbox_hash, - uidvalidity, - &[(uid, &env)], - )?; - } - let mut prev_exists = mailbox.exists.lock().unwrap(); - prev_exists.insert_new(env.hash()); - - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: Create(Box::new(env)), - }); - } - } - } - Err(e) => { - debug!(e); - } - } - } - Err(e) => { - debug!( - "UID SEARCH RECENT err: {}\nresp: {}", - e.to_string(), - &response - ); - } - } - } - } else if n > mailbox.exists.lock().unwrap().len() { - /* UID FETCH ALL UID, cross-ref, then FETCH difference headers - * */ - debug!("exists {}", n); - exit_on_error!( - conn, - mailbox_hash, - conn.send_command( - &[ - b"FETCH", - format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(), - b"(UID FLAGS RFC822)", - ] - .join(&b' '), - ).await - conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await - ); - match protocol_parser::uid_fetch_responses(&response) { - Ok((_, v, _)) => { - 'fetch_responses_a: for UidFetchResponse { - uid, flags, body, .. - } in v - { - if uid_store - .uid_index - .lock() - .unwrap() - .contains_key(&(mailbox_hash, uid)) - { - continue 'fetch_responses_a; - } - if let Ok(mut env) = - Envelope::from_bytes(body.unwrap(), flags.as_ref().map(|&(f, _)| f)) - { - uid_store - .hash_index - .lock() - .unwrap() - .insert(env.hash(), (uid, mailbox_hash)); - uid_store - .uid_index - .lock() - .unwrap() - .insert((mailbox_hash, uid), env.hash()); - if let Some((_, keywords)) = flags { - let mut tag_lck = uid_store.tag_index.write().unwrap(); - 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); - } - } - debug!( - "Create event {} {} {}", - env.hash(), - env.subject(), - mailbox.path(), - ); - if !env.is_seen() { - mailbox.unseen.lock().unwrap().insert_new(env.hash()); - } - if uid_store.cache_headers { - cache::save_envelopes( - uid_store.account_hash, - mailbox_hash, - uidvalidity, - &[(uid, &env)], - )?; - } - mailbox.exists.lock().unwrap().insert_new(env.hash()); - - conn.add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: Create(Box::new(env)), - }); - } - } - } - Err(e) => { - debug!(e); - } - } - } - } - Err(e) => { - debug!("{:?}", e); - return Err(e).chain_err_summary(|| "could not select mailbox"); - } - }; - Ok(()) -} diff --git a/src/managesieve.rs b/src/managesieve.rs index 1ec102e6..c2516a7e 100644 --- a/src/managesieve.rs +++ b/src/managesieve.rs @@ -73,6 +73,8 @@ pub mod jobs; pub mod mailcap; pub mod plugins; +use futures::executor::block_on; + /// Opens an interactive shell on a managesieve server. Suggested use is with rlwrap(1) /// /// # Example invocation: @@ -108,7 +110,7 @@ fn main() -> Result<()> { std::process::exit(1); } let mut conn = new_managesieve_connection(&settings.accounts[&account_name].account)?; - conn.connect()?; + block_on(conn.connect())?; let mut res = String::with_capacity(8 * 1024); let mut input = String::new(); @@ -124,8 +126,8 @@ fn main() -> Result<()> { if input.trim().eq_ignore_ascii_case("logout") { break; } - conn.send_command(input.as_bytes()).unwrap(); - conn.read_lines(&mut res, String::new()).unwrap(); + block_on(conn.send_command(input.as_bytes()))?; + block_on(conn.read_lines(&mut res, String::new()))?; println!("out: {}", res.trim()); } Err(error) => println!("error: {}", error), diff --git a/testing/Cargo.toml b/testing/Cargo.toml index eb2d0a70..3ad49874 100644 --- a/testing/Cargo.toml +++ b/testing/Cargo.toml @@ -17,10 +17,6 @@ path = "src/mboxparse.rs" name = "imapconn" path = "src/imap_conn.rs" -[[bin]] -name = "managesieve_conn" -path = "src/managesieve.rs" - [dependencies] melib = { path = "../melib", version = "*", features = ["debug-tracing", "unicode_algorithms"] } diff --git a/testing/src/managesieve.rs b/testing/src/managesieve.rs deleted file mode 100644 index 75d57787..00000000 --- a/testing/src/managesieve.rs +++ /dev/null @@ -1,69 +0,0 @@ -extern crate melib; - -use melib::backends::imap::managesieve::new_managesieve_connection; -use melib::AccountSettings; -use melib::Result; - -/// Opens an interactive shell on a managesieve server. Suggested use is with rlwrap(1) -/// -/// # Example invocation: -/// ```sh -/// ./manage_sieve server_hostname server_username server_password server_port"); -/// ``` -/// -/// `danger_accept_invalid_certs` is turned on by default, so no certificate validation is performed. - -fn main() -> Result<()> { - let mut args = std::env::args().skip(1).collect::>(); - if args.len() != 4 { - eprintln!( - "Usage: manage_sieve server_hostname server_username server_password server_port" - ); - std::process::exit(1); - } - - let (a, b, c, d) = ( - std::mem::replace(&mut args[0], String::new()), - std::mem::replace(&mut args[1], String::new()), - std::mem::replace(&mut args[2], String::new()), - std::mem::replace(&mut args[3], String::new()), - ); - let set = AccountSettings { - extra: [ - ("server_hostname".to_string(), a), - ("server_username".to_string(), b), - ("server_password".to_string(), c), - ("server_port".to_string(), d), - ( - "danger_accept_invalid_certs".to_string(), - "true".to_string(), - ), - ] - .iter() - .cloned() - .collect(), - ..Default::default() - }; - let mut conn = new_managesieve_connection(&set)?; - conn.connect()?; - let mut res = String::with_capacity(8 * 1024); - - let mut input = String::new(); - loop { - use std::io; - input.clear(); - match io::stdin().read_line(&mut input) { - Ok(_) => { - if input.trim().eq_ignore_ascii_case("logout") { - break; - } - conn.send_command(input.as_bytes()).unwrap(); - conn.read_lines(&mut res, String::new()).unwrap(); - println!("out: {}", &res); - } - Err(error) => println!("error: {}", error), - } - } - - Ok(()) -}