1#[doc(inline)]
7use super::{Error, Result};
8#[cfg(feature = "oauth2")]
9use crate::account::config::oauth2::OAuth2Config;
10#[cfg(feature = "derive")]
11use crate::serde::deserialize_shell_expanded_string;
12use crate::{account::config::passwd::PasswordConfig, tls::Encryption};
13
14#[derive(Clone, Debug, Default, Eq, PartialEq)]
18#[cfg_attr(
19 feature = "derive",
20 derive(serde::Serialize, serde::Deserialize),
21 serde(rename_all = "kebab-case")
22)]
23pub struct ImapConfig {
24 pub host: String,
26
27 pub port: u16,
29
30 pub encryption: Option<Encryption>,
34
35 #[cfg_attr(
40 feature = "derive",
41 serde(deserialize_with = "deserialize_shell_expanded_string")
42 )]
43 pub login: String,
44
45 pub auth: ImapAuthConfig,
50
51 pub extensions: Option<ImapExtensionsConfig>,
53
54 pub watch: Option<ImapWatchConfig>,
59
60 pub clients_pool_size: Option<u8>,
65}
66
67impl ImapConfig {
68 pub fn clients_pool_size(&self) -> u8 {
69 self.clients_pool_size.unwrap_or(1)
70 }
71
72 pub fn send_id_after_auth(&self) -> bool {
73 self.extensions
74 .as_ref()
75 .and_then(|ext| ext.id.as_ref())
76 .and_then(|id| id.send_after_auth)
77 .unwrap_or_default()
78 }
79
80 pub fn is_encryption_enabled(&self) -> bool {
82 matches!(
83 self.encryption.as_ref(),
84 None | Some(Encryption::Tls(_)) | Some(Encryption::StartTls(_))
85 )
86 }
87
88 pub fn is_start_tls_encryption_enabled(&self) -> bool {
90 matches!(self.encryption.as_ref(), Some(Encryption::StartTls(_)))
91 }
92
93 pub fn is_encryption_disabled(&self) -> bool {
95 matches!(self.encryption.as_ref(), Some(Encryption::None))
96 }
97
98 pub async fn build_credentials(&self) -> Result<String> {
103 self.auth.build_credentials().await
104 }
105
106 pub fn find_watch_timeout(&self) -> Option<u64> {
108 self.watch.as_ref().and_then(|c| c.find_timeout())
109 }
110}
111
112#[cfg(feature = "sync")]
113impl crate::sync::hash::SyncHash for ImapConfig {
114 fn sync_hash(&self, state: &mut std::hash::DefaultHasher) {
115 use std::hash::Hash;
116
117 Hash::hash(&self.host, state);
118 Hash::hash(&self.port, state);
119 Hash::hash(&self.login, state);
120 }
121}
122
123#[derive(Clone, Debug, Eq, PartialEq)]
127#[cfg_attr(
128 feature = "derive",
129 derive(serde::Serialize, serde::Deserialize),
130 serde(rename_all = "lowercase"),
131 serde(tag = "type"),
132 serde(from = "ImapAuthConfigDerive")
133)]
134pub enum ImapAuthConfig {
135 Password(PasswordConfig),
137 #[cfg(feature = "oauth2")]
139 OAuth2(OAuth2Config),
140}
141
142impl ImapAuthConfig {
143 pub async fn reset(&self) -> Result<()> {
145 match self {
146 ImapAuthConfig::Password(config) => {
147 config.reset().await.map_err(Error::ResetPasswordError)
148 }
149 #[cfg(feature = "oauth2")]
150 ImapAuthConfig::OAuth2(config) => {
151 config.reset().await.map_err(Error::ResetOAuthSecretsError)
152 }
153 }
154 }
155
156 pub async fn build_credentials(&self) -> Result<String> {
161 match self {
162 ImapAuthConfig::Password(passwd) => {
163 let passwd = passwd.get().await.map_err(Error::GetPasswdImapError)?;
164 let passwd = passwd
165 .lines()
166 .next()
167 .ok_or(Error::GetPasswdEmptyImapError)?;
168 Ok(passwd.to_owned())
169 }
170 #[cfg(feature = "oauth2")]
171 ImapAuthConfig::OAuth2(oauth2) => Ok(oauth2
172 .access_token()
173 .await
174 .map_err(Error::AccessTokenNotAvailable)?),
175 }
176 }
177
178 #[cfg(feature = "keyring")]
179 pub fn replace_empty_secrets(&mut self, name: impl AsRef<str>) -> Result<()> {
180 let name = name.as_ref();
181
182 match self {
183 Self::Password(secret) => {
184 secret
185 .replace_with_keyring_if_empty(format!("{name}-imap-passwd"))
186 .map_err(Error::ReplacingUnidentifiedFailed)?;
187 }
188 #[cfg(feature = "oauth2")]
189 Self::OAuth2(config) => {
190 if let Some(secret) = config.client_secret.as_mut() {
191 secret
192 .replace_with_keyring_if_empty(format!("{name}-imap-oauth2-client-secret"))
193 .map_err(Error::ReplacingUnidentifiedFailed)?;
194 }
195
196 config
197 .access_token
198 .replace_with_keyring_if_empty(format!("{name}-imap-oauth2-access-token"))
199 .map_err(Error::ReplacingUnidentifiedFailed)?;
200 config
201 .refresh_token
202 .replace_with_keyring_if_empty(format!("{name}-imap-oauth2-refresh-token"))
203 .map_err(Error::ReplacingUnidentifiedFailed)?;
204 }
205 }
206
207 Ok(())
208 }
209}
210
211impl Default for ImapAuthConfig {
212 fn default() -> Self {
213 Self::Password(Default::default())
214 }
215}
216
217#[cfg(feature = "derive")]
218#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
219#[serde(rename_all = "lowercase", tag = "type")]
220pub enum ImapAuthConfigDerive {
221 Password(PasswordConfig),
222 #[cfg(feature = "oauth2")]
223 OAuth2(OAuth2Config),
224 #[cfg(not(feature = "oauth2"))]
225 #[serde(skip_serializing, deserialize_with = "missing_oauth2_feature")]
226 OAuth2,
227}
228
229#[cfg(all(feature = "derive", not(feature = "oauth2")))]
230fn missing_oauth2_feature<'de, D>(_: D) -> std::result::Result<(), D::Error>
231where
232 D: serde::Deserializer<'de>,
233{
234 Err(serde::de::Error::custom("missing `oauth2` cargo feature"))
235}
236
237#[cfg(feature = "derive")]
238impl From<ImapAuthConfigDerive> for ImapAuthConfig {
239 fn from(config: ImapAuthConfigDerive) -> Self {
240 match config {
241 ImapAuthConfigDerive::Password(config) => Self::Password(config),
242 #[cfg(feature = "oauth2")]
243 ImapAuthConfigDerive::OAuth2(config) => Self::OAuth2(config),
244 #[cfg(not(feature = "oauth2"))]
245 ImapAuthConfigDerive::OAuth2 => unreachable!(),
246 }
247 }
248}
249
250#[derive(Clone, Debug, Eq, PartialEq)]
255#[cfg_attr(
256 feature = "derive",
257 derive(serde::Serialize, serde::Deserialize),
258 serde(rename_all = "kebab-case")
259)]
260pub struct ImapWatchConfig {
261 timeout: Option<u64>,
266}
267
268impl ImapWatchConfig {
269 pub fn find_timeout(&self) -> Option<u64> {
271 self.timeout
272 }
273}
274
275#[derive(Clone, Debug, Default, Eq, PartialEq)]
277#[cfg_attr(
278 feature = "derive",
279 derive(serde::Serialize, serde::Deserialize),
280 serde(rename_all = "kebab-case")
281)]
282pub struct ImapExtensionsConfig {
283 id: Option<ImapIdExtensionConfig>,
284}
285
286#[derive(Clone, Debug, Default, Eq, PartialEq)]
290#[cfg_attr(
291 feature = "derive",
292 derive(serde::Serialize, serde::Deserialize),
293 serde(rename_all = "kebab-case")
294)]
295pub struct ImapIdExtensionConfig {
296 send_after_auth: Option<bool>,
299}