use imap::ConnectionMode;
use std::fmt;
#[cfg(feature = "derive")]
use std::{marker::PhantomData, result};
use crate::account::config::{oauth2::OAuth2Config, passwd::PasswdConfig};
#[doc(inline)]
use super::{Error, Result};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub struct ImapConfig {
pub host: String,
pub port: u16,
#[cfg_attr(
feature = "derive",
serde(default, deserialize_with = "some_bool_or_kind")
)]
pub encryption: Option<ImapEncryptionKind>,
pub login: String,
#[cfg_attr(feature = "derive", serde(flatten))]
pub auth: ImapAuthConfig,
pub watch: Option<ImapWatchConfig>,
}
impl ImapConfig {
pub fn is_encryption_enabled(&self) -> bool {
matches!(
self.encryption.as_ref(),
None | Some(ImapEncryptionKind::Tls) | Some(ImapEncryptionKind::StartTls)
)
}
pub fn is_start_tls_encryption_enabled(&self) -> bool {
matches!(self.encryption.as_ref(), Some(ImapEncryptionKind::StartTls))
}
pub fn is_encryption_disabled(&self) -> bool {
matches!(self.encryption.as_ref(), Some(ImapEncryptionKind::None))
}
pub async fn build_credentials(&self) -> Result<String> {
self.auth.build_credentials().await
}
pub fn find_watch_timeout(&self) -> Option<u64> {
self.watch.as_ref().and_then(|c| c.find_timeout())
}
}
#[cfg(feature = "account-sync")]
impl crate::sync::hash::SyncHash for ImapConfig {
fn sync_hash(&self, state: &mut std::hash::DefaultHasher) {
std::hash::Hash::hash(&self.host, state);
std::hash::Hash::hash(&self.login, state);
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub enum ImapEncryptionKind {
#[default]
#[cfg_attr(feature = "derive", serde(alias = "ssl"))]
Tls,
#[cfg_attr(feature = "derive", serde(alias = "starttls"))]
StartTls,
None,
}
impl fmt::Display for ImapEncryptionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tls => write!(f, "SSL/TLS"),
Self::StartTls => write!(f, "StartTLS"),
Self::None => write!(f, "None"),
}
}
}
impl From<bool> for ImapEncryptionKind {
fn from(value: bool) -> Self {
if value {
Self::Tls
} else {
Self::None
}
}
}
impl From<ImapEncryptionKind> for ConnectionMode {
fn from(val: ImapEncryptionKind) -> Self {
match val {
ImapEncryptionKind::Tls => ConnectionMode::Tls,
ImapEncryptionKind::StartTls => ConnectionMode::StartTls,
ImapEncryptionKind::None => ConnectionMode::Plaintext,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "lowercase")
)]
pub enum ImapAuthConfig {
#[cfg_attr(feature = "derive", serde(alias = "password"))]
Passwd(PasswdConfig),
OAuth2(OAuth2Config),
}
impl Default for ImapAuthConfig {
fn default() -> Self {
Self::Passwd(Default::default())
}
}
impl ImapAuthConfig {
pub async fn reset(&self) -> Result<()> {
match self {
ImapAuthConfig::Passwd(config) => {
config.reset().await.map_err(Error::ResetPasswordError)
}
ImapAuthConfig::OAuth2(config) => {
config.reset().await.map_err(Error::ResetOAuthSecretsError)
}
}
}
pub async fn build_credentials(&self) -> Result<String> {
match self {
ImapAuthConfig::Passwd(passwd) => {
let passwd = passwd.get().await.map_err(Error::GetPasswdImapError)?;
let passwd = passwd
.lines()
.next()
.ok_or(Error::GetPasswdEmptyImapError)?;
Ok(passwd.to_owned())
}
ImapAuthConfig::OAuth2(oauth2) => Ok(oauth2
.access_token()
.await
.map_err(Error::AccessTokenNotAvailable)?),
}
}
pub fn replace_undefined_keyring_entries(&mut self, name: impl AsRef<str>) -> Result<()> {
let name = name.as_ref();
match self {
Self::Passwd(secret) => {
secret
.replace_undefined_to_keyring(format!("{name}-imap-passwd"))
.map_err(Error::ReplacingUnidentifiedFailed)?;
}
Self::OAuth2(config) => {
config
.client_secret
.replace_undefined_to_keyring(format!("{name}-imap-oauth2-client-secret"))
.map_err(Error::ReplacingUnidentifiedFailed)?;
config
.access_token
.replace_undefined_to_keyring(format!("{name}-imap-oauth2-access-token"))
.map_err(Error::ReplacingUnidentifiedFailed)?;
config
.refresh_token
.replace_undefined_to_keyring(format!("{name}-imap-oauth2-refresh-token"))
.map_err(Error::ReplacingUnidentifiedFailed)?;
}
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub struct ImapWatchConfig {
timeout: Option<u64>,
}
impl ImapWatchConfig {
pub fn find_timeout(&self) -> Option<u64> {
self.timeout
}
}
#[cfg(feature = "derive")]
fn some_bool_or_kind<'de, D>(
deserializer: D,
) -> result::Result<Option<ImapEncryptionKind>, D::Error>
where
D: serde::Deserializer<'de>,
{
struct SomeBoolOrKind(PhantomData<fn() -> Option<ImapEncryptionKind>>);
impl<'de> serde::de::Visitor<'de> for SomeBoolOrKind {
type Value = Option<ImapEncryptionKind>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("some or none")
}
fn visit_some<D>(self, deserializer: D) -> result::Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
struct BoolOrKind(PhantomData<fn() -> ImapEncryptionKind>);
impl<'de> serde::de::Visitor<'de> for BoolOrKind {
type Value = ImapEncryptionKind;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("boolean or string")
}
fn visit_bool<E>(self, v: bool) -> result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.into())
}
fn visit_str<E>(self, v: &str) -> result::Result<Self::Value, E>
where
E: serde::de::Error,
{
serde::Deserialize::deserialize(serde::de::value::StrDeserializer::new(v))
}
}
deserializer
.deserialize_any(BoolOrKind(PhantomData))
.map(Option::Some)
}
}
deserializer.deserialize_option(SomeBoolOrKind(PhantomData))
}