#[doc(inline)]
use super::{Error, Result};
#[cfg(feature = "oauth2")]
use crate::account::config::oauth2::OAuth2Config;
use crate::{account::config::passwd::PasswordConfig, tls::Encryption};
#[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,
pub encryption: Option<Encryption>,
pub login: String,
pub auth: ImapAuthConfig,
pub extensions: Option<ImapExtensionsConfig>,
pub watch: Option<ImapWatchConfig>,
pub clients_pool_size: Option<u8>,
}
impl ImapConfig {
pub fn clients_pool_size(&self) -> u8 {
self.clients_pool_size.unwrap_or(1)
}
pub fn send_id_after_auth(&self) -> bool {
self.extensions
.as_ref()
.and_then(|ext| ext.id.as_ref())
.and_then(|id| id.send_after_auth)
.unwrap_or_default()
}
pub fn is_encryption_enabled(&self) -> bool {
matches!(
self.encryption.as_ref(),
None | Some(Encryption::Tls(_)) | Some(Encryption::StartTls(_))
)
}
pub fn is_start_tls_encryption_enabled(&self) -> bool {
matches!(self.encryption.as_ref(), Some(Encryption::StartTls(_)))
}
pub fn is_encryption_disabled(&self) -> bool {
matches!(self.encryption.as_ref(), Some(Encryption::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 = "sync")]
impl crate::sync::hash::SyncHash for ImapConfig {
fn sync_hash(&self, state: &mut std::hash::DefaultHasher) {
use std::hash::Hash;
Hash::hash(&self.host, state);
Hash::hash(&self.port, state);
Hash::hash(&self.login, state);
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "lowercase"),
serde(tag = "type"),
serde(from = "ImapAuthConfigDerive")
)]
pub enum ImapAuthConfig {
Password(PasswordConfig),
#[cfg(feature = "oauth2")]
OAuth2(OAuth2Config),
}
impl ImapAuthConfig {
pub async fn reset(&self) -> Result<()> {
match self {
ImapAuthConfig::Password(config) => {
config.reset().await.map_err(Error::ResetPasswordError)
}
#[cfg(feature = "oauth2")]
ImapAuthConfig::OAuth2(config) => {
config.reset().await.map_err(Error::ResetOAuthSecretsError)
}
}
}
pub async fn build_credentials(&self) -> Result<String> {
match self {
ImapAuthConfig::Password(passwd) => {
let passwd = passwd.get().await.map_err(Error::GetPasswdImapError)?;
let passwd = passwd
.lines()
.next()
.ok_or(Error::GetPasswdEmptyImapError)?;
Ok(passwd.to_owned())
}
#[cfg(feature = "oauth2")]
ImapAuthConfig::OAuth2(oauth2) => Ok(oauth2
.access_token()
.await
.map_err(Error::AccessTokenNotAvailable)?),
}
}
#[cfg(feature = "keyring")]
pub fn replace_empty_secrets(&mut self, name: impl AsRef<str>) -> Result<()> {
let name = name.as_ref();
match self {
Self::Password(secret) => {
secret
.replace_with_keyring_if_empty(format!("{name}-imap-passwd"))
.map_err(Error::ReplacingUnidentifiedFailed)?;
}
#[cfg(feature = "oauth2")]
Self::OAuth2(config) => {
if let Some(secret) = config.client_secret.as_mut() {
secret
.replace_with_keyring_if_empty(format!("{name}-imap-oauth2-client-secret"))
.map_err(Error::ReplacingUnidentifiedFailed)?;
}
config
.access_token
.replace_with_keyring_if_empty(format!("{name}-imap-oauth2-access-token"))
.map_err(Error::ReplacingUnidentifiedFailed)?;
config
.refresh_token
.replace_with_keyring_if_empty(format!("{name}-imap-oauth2-refresh-token"))
.map_err(Error::ReplacingUnidentifiedFailed)?;
}
}
Ok(())
}
}
impl Default for ImapAuthConfig {
fn default() -> Self {
Self::Password(Default::default())
}
}
#[cfg(feature = "derive")]
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase", tag = "type")]
pub enum ImapAuthConfigDerive {
Password(PasswordConfig),
#[cfg(feature = "oauth2")]
OAuth2(OAuth2Config),
#[cfg(not(feature = "oauth2"))]
#[serde(skip_serializing, deserialize_with = "missing_oauth2_feature")]
OAuth2,
}
#[cfg(all(feature = "derive", not(feature = "oauth2")))]
fn missing_oauth2_feature<'de, D>(_: D) -> std::result::Result<(), D::Error>
where
D: serde::Deserializer<'de>,
{
Err(serde::de::Error::custom("missing `oauth2` cargo feature"))
}
#[cfg(feature = "derive")]
impl From<ImapAuthConfigDerive> for ImapAuthConfig {
fn from(config: ImapAuthConfigDerive) -> Self {
match config {
ImapAuthConfigDerive::Password(config) => Self::Password(config),
#[cfg(feature = "oauth2")]
ImapAuthConfigDerive::OAuth2(config) => Self::OAuth2(config),
#[cfg(not(feature = "oauth2"))]
ImapAuthConfigDerive::OAuth2 => unreachable!(),
}
}
}
#[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
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub struct ImapExtensionsConfig {
id: Option<ImapIdExtensionConfig>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub struct ImapIdExtensionConfig {
send_after_auth: Option<bool>,
}