/* * meli - jmap 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::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; use crate::backends::*; use crate::conf::AccountSettings; use crate::email::*; use crate::error::{MeliError, Result}; use reqwest::blocking::Client; use std::collections::{BTreeMap, HashMap}; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; use std::time::Instant; #[macro_export] macro_rules! _impl { ($(#[$outer:meta])*$field:ident : $t:ty) => { $(#[$outer])* pub fn $field(mut self, new_val: $t) -> Self { self.$field = new_val; self } }; (get_mut $(#[$outer:meta])*$method:ident, $field:ident : $t:ty) => { $(#[$outer])* pub fn $method(&mut self) -> &mut $t { &mut self.$field } }; (get $(#[$outer:meta])*$method:ident, $field:ident : $t:ty) => { $(#[$outer])* pub fn $method(&self) -> &$t { &self.$field } } } pub mod operations; use operations::*; pub mod connection; use connection::*; pub mod protocol; use protocol::*; pub mod rfc8620; use rfc8620::*; pub mod objects; use objects::*; pub mod mailbox; use mailbox::*; #[derive(Debug, Default)] pub struct EnvelopeCache { bytes: Option, headers: Option, body: Option, flags: Option, } #[derive(Debug, Clone)] pub struct JmapServerConf { pub server_hostname: String, pub server_username: String, pub server_password: String, pub server_port: u16, pub danger_accept_invalid_certs: bool, } macro_rules! get_conf_val { ($s:ident[$var:literal]) => { $s.extra.get($var).ok_or_else(|| { MeliError::new(format!( "Configuration error ({}): JMAP 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)) }; } impl JmapServerConf { pub fn new(s: &AccountSettings) -> Result { Ok(JmapServerConf { server_hostname: get_conf_val!(s["server_hostname"])?.to_string(), server_username: get_conf_val!(s["server_username"])?.to_string(), server_password: get_conf_val!(s["server_password"])?.to_string(), server_port: get_conf_val!(s["server_port"], 443)?, danger_accept_invalid_certs: get_conf_val!(s["danger_accept_invalid_certs"], false)?, }) } } 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 } } macro_rules! get_conf_val { ($s:ident[$var:literal]) => { $s.extra.get($var).ok_or_else(|| { MeliError::new(format!( "Configuration error ({}): JMAP 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, Default)] pub struct Store { byte_cache: HashMap, id_store: HashMap, blob_id_store: HashMap, } #[derive(Debug)] pub struct JmapType { account_name: String, online: Arc)>>, is_subscribed: Arc, server_conf: JmapServerConf, connection: Arc, store: Arc>, tag_index: Arc>>, mailboxes: Arc>>, } impl MailBackend for JmapType { fn is_async(&self) -> bool { false } fn is_remote(&self) -> bool { true } fn supports_search(&self) -> bool { true } fn is_online(&self) -> Result<()> { if self.online.lock().unwrap().1.is_err() && Instant::now().duration_since(self.online.lock().unwrap().0) >= std::time::Duration::new(2, 0) { let _ = self.mailboxes(); } self.online.lock().unwrap().1.clone() } fn fetch(&mut self, mailbox: &Mailbox) -> Result>>> { let mut w = AsyncBuilder::new(); let mailboxes = self.mailboxes.clone(); let store = self.store.clone(); let tag_index = self.tag_index.clone(); let connection = self.connection.clone(); let mailbox_hash = mailbox.hash(); let handle = { let tx = w.tx(); let closure = move |_work_context| { tx.send(AsyncStatus::Payload(protocol::get( &connection, &store, &tag_index, &mailboxes.read().unwrap()[&mailbox_hash], ))) .unwrap(); tx.send(AsyncStatus::Finished).unwrap(); }; Box::new(closure) }; Ok(w.build(handle)) } fn watch( &self, _sender: RefreshEventConsumer, _work_context: WorkContext, ) -> Result { Err(MeliError::from("JMAP watch for updates is unimplemented")) } fn mailboxes(&self) -> Result> { if self.mailboxes.read().unwrap().is_empty() { let mailboxes = debug!(protocol::get_mailboxes(&self.connection))?; *self.mailboxes.write().unwrap() = mailboxes; } Ok(self .mailboxes .read() .unwrap() .iter() .filter(|(_, f)| f.is_subscribed) .map(|(&h, f)| (h, BackendMailbox::clone(f) as Mailbox)) .collect()) } fn operation(&self, hash: EnvelopeHash) -> Result> { Ok(Box::new(JmapOp::new( hash, self.connection.clone(), self.store.clone(), ))) } fn save( &self, _bytes: Vec, _mailbox_hash: MailboxHash, _flags: Option, ) -> ResultFuture<()> { Err(MeliError::new("Unimplemented.")) } fn as_any(&self) -> &dyn ::std::any::Any { self } fn tags(&self) -> Option>>> { Some(self.tag_index.clone()) } } impl JmapType { pub fn new( s: &AccountSettings, is_subscribed: Box bool + Send + Sync>, ) -> Result> { let online = Arc::new(Mutex::new(( std::time::Instant::now(), Err(MeliError::new("Account is uninitialised.")), ))); let server_conf = JmapServerConf::new(s)?; Ok(Box::new(JmapType { connection: Arc::new(JmapConnection::new(&server_conf, online.clone())?), store: Arc::new(RwLock::new(Store::default())), tag_index: Arc::new(RwLock::new(Default::default())), mailboxes: Arc::new(RwLock::new(HashMap::default())), account_name: s.name.clone(), online, is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)), server_conf, })) } pub fn validate_config(s: &AccountSettings) -> Result<()> { get_conf_val!(s["server_hostname"])?; get_conf_val!(s["server_username"])?; get_conf_val!(s["server_password"])?; get_conf_val!(s["server_port"], 443)?; get_conf_val!(s["danger_accept_invalid_certs"], false)?; Ok(()) } }