email/autoconfig/
config.rs

1//! # Account configuration discovery
2//!
3//! This module contains the [`serde`] representation of the Mozilla
4//! [Autoconfiguration].
5//!
6//! [Autoconfiguration]: https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
7
8use std::time::Duration;
9
10use serde::Deserialize;
11
12#[derive(Debug, Deserialize)]
13#[serde(rename_all = "camelCase")]
14/// The root level of the Mozilla Autoconfiguration.
15pub struct AutoConfig {
16    pub version: String,
17    pub email_provider: EmailProvider,
18    #[serde(rename = "oAuth2")]
19    pub oauth2: Option<OAuth2Config>,
20}
21
22impl AutoConfig {
23    pub fn is_gmail(&self) -> bool {
24        self.email_provider.id == "googlemail.com"
25    }
26
27    /// The config version
28    pub fn version(&self) -> &str {
29        &self.version
30    }
31
32    /// Information about the email provider for the given email
33    /// address, e.g. Google or Microsoft
34    pub fn email_provider(&self) -> &EmailProvider {
35        &self.email_provider
36    }
37
38    /// If the provider supports oAuth2, it SHOULD be specified here,
39    /// but some providers don't.
40    pub fn oauth2(&self) -> Option<&OAuth2Config> {
41        self.oauth2.as_ref()
42    }
43}
44
45#[derive(Debug, Deserialize)]
46#[serde(rename_all = "camelCase")]
47pub struct OAuth2Config {
48    issuer: String,
49    scope: String,
50    #[serde(rename = "authURL")]
51    auth_url: String,
52    #[serde(rename = "tokenURL")]
53    token_url: String,
54}
55
56impl OAuth2Config {
57    /// The implementer of the oAuth2 protocol for this email
58    /// provider, which is usually the email provider itself.
59    pub fn issuer(&self) -> &str {
60        &self.issuer
61    }
62
63    /// The scopes needed from the oAuth2 API to be able to login
64    /// using an oAuth2 generated token.
65    pub fn scope(&self) -> Vec<&str> {
66        self.scope.split(' ').collect()
67    }
68
69    /// The url where the initial oAuth2 login takes place.
70    pub fn auth_url(&self) -> &str {
71        &self.auth_url
72    }
73
74    /// The url used to refresh the oAuth2 token.
75    pub fn token_url(&self) -> &str {
76        &self.token_url
77    }
78}
79
80#[derive(Debug, Deserialize)]
81pub struct EmailProvider {
82    pub id: String,
83    #[serde(rename = "$value")]
84    pub properties: Vec<EmailProviderProperty>,
85}
86
87impl EmailProvider {
88    /// Just an array containing all of the email providers
89    /// properties, usefull if you want to get multiple properties in
90    /// 1 for loop.
91    pub fn properties(&self) -> &Vec<EmailProviderProperty> {
92        &self.properties
93    }
94
95    /// The email providers unique id.
96    pub fn id(&self) -> &str {
97        &self.id
98    }
99
100    /// The domain name that the email provider uses in their email
101    /// addresses.
102    pub fn domain(&self) -> Vec<&str> {
103        let mut domains: Vec<&str> = Vec::new();
104
105        for property in &self.properties {
106            if let EmailProviderProperty::Domain(domain) = property {
107                domains.push(domain)
108            }
109        }
110
111        domains
112    }
113
114    /// The email providers display name. e.g. Google Mail
115    pub fn display_name(&self) -> Option<&str> {
116        for property in &self.properties {
117            if let EmailProviderProperty::DisplayName(display_name) = property {
118                return Some(display_name);
119            }
120        }
121
122        None
123    }
124
125    /// The email providers short display name. e.g. GMail
126    pub fn display_short_name(&self) -> Option<&str> {
127        for property in &self.properties {
128            if let EmailProviderProperty::DisplayShortName(short_name) = property {
129                return Some(short_name);
130            }
131        }
132
133        None
134    }
135
136    /// An array containing info about all of an email providers
137    /// incoming mail servers
138    pub fn incoming_servers(&self) -> Vec<&Server> {
139        let mut servers: Vec<&Server> = Vec::new();
140
141        for property in &self.properties {
142            if let EmailProviderProperty::IncomingServer(server) = property {
143                servers.push(server)
144            }
145        }
146
147        servers
148    }
149
150    /// An array containing info about all of an email providers
151    /// outgoing mail servers
152    pub fn outgoing_servers(&self) -> Vec<&Server> {
153        let mut servers: Vec<&Server> = Vec::new();
154
155        for property in &self.properties {
156            if let EmailProviderProperty::OutgoingServer(server) = property {
157                servers.push(server)
158            }
159        }
160
161        servers
162    }
163
164    /// An array containing info about all of an email providers mail
165    /// servers
166    pub fn servers(&self) -> Vec<&Server> {
167        let mut servers: Vec<&Server> = Vec::new();
168
169        for property in &self.properties {
170            match property {
171                EmailProviderProperty::IncomingServer(server) => servers.push(server),
172                EmailProviderProperty::OutgoingServer(server) => servers.push(server),
173                _ => {}
174            }
175        }
176
177        servers
178    }
179
180    /// Documentation on how to setup the email client, provided by
181    /// the email provider.
182    pub fn documentation(&self) -> Option<&Documentation> {
183        for property in &self.properties {
184            if let EmailProviderProperty::Documentation(documentation) = property {
185                return Some(documentation);
186            }
187        }
188
189        None
190    }
191}
192
193#[derive(Debug, Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub enum EmailProviderProperty {
196    Domain(String),
197    DisplayName(String),
198    DisplayShortName(String),
199    IncomingServer(Server),
200    OutgoingServer(Server),
201    Documentation(Documentation),
202}
203
204#[derive(Debug, Deserialize)]
205pub struct Server {
206    pub r#type: ServerType,
207    #[serde(rename = "$value")]
208    pub properties: Vec<ServerProperty>,
209}
210
211impl Server {
212    /// Just an array containing all of a mail servers properties,
213    /// usefull if you want to get multiple properties in 1 for loop.
214    pub fn properties(&self) -> &Vec<ServerProperty> {
215        &self.properties
216    }
217
218    /// What type of mail server this server is.
219    pub fn server_type(&self) -> &ServerType {
220        &self.r#type
221    }
222
223    /// The mail servers domain/ip
224    pub fn hostname(&self) -> Option<&str> {
225        for property in &self.properties {
226            if let ServerProperty::Hostname(hostname) = property {
227                return Some(hostname);
228            }
229        }
230
231        None
232    }
233
234    /// The mail servers port
235    pub fn port(&self) -> Option<&u16> {
236        for property in &self.properties {
237            if let ServerProperty::Port(port) = property {
238                return Some(port);
239            }
240        }
241
242        None
243    }
244
245    /// The kind of security the mail server prefers
246    pub fn security_type(&self) -> Option<&SecurityType> {
247        for property in &self.properties {
248            if let ServerProperty::SocketType(socket_type) = property {
249                return Some(socket_type);
250            }
251        }
252
253        None
254    }
255
256    /// The kind of authentication is needed to login to this mail
257    /// server
258    pub fn authentication_type(&self) -> Vec<&AuthenticationType> {
259        let mut types: Vec<&AuthenticationType> = Vec::new();
260
261        for property in &self.properties {
262            if let ServerProperty::Authentication(authentication_type) = property {
263                types.push(authentication_type)
264            }
265        }
266
267        types
268    }
269
270    /// The users username
271    pub fn username(&self) -> Option<&str> {
272        for property in &self.properties {
273            if let ServerProperty::Username(username) = property {
274                return Some(username);
275            }
276        }
277
278        None
279    }
280
281    /// The users password
282    pub fn password(&self) -> Option<&str> {
283        for property in &self.properties {
284            if let ServerProperty::Password(password) = property {
285                return Some(password);
286            }
287        }
288
289        None
290    }
291}
292
293#[derive(Debug, Deserialize)]
294#[serde(rename_all = "camelCase")]
295pub enum ServerProperty {
296    Hostname(String),
297    Port(u16),
298    SocketType(SecurityType),
299    Authentication(AuthenticationType),
300    OwaURL(String),
301    EwsURL(String),
302    UseGlobalPreferredServer(bool),
303    Pop3(Pop3Config),
304    Username(String),
305    Password(String),
306}
307
308#[derive(Debug, Deserialize)]
309pub enum SecurityType {
310    #[serde(rename = "plain")]
311    Plain,
312    #[serde(rename = "STARTTLS")]
313    Starttls,
314    #[serde(rename = "SSL")]
315    Tls,
316}
317
318#[derive(Debug, Deserialize)]
319#[serde(rename_all = "camelCase")]
320pub enum ServerType {
321    Exchange,
322    #[cfg(feature = "imap")]
323    Imap,
324    #[cfg(feature = "smtp")]
325    Smtp,
326    #[serde(other)]
327    Unknown,
328}
329
330#[derive(Debug, Deserialize)]
331pub enum AuthenticationType {
332    #[serde(rename = "password-cleartext")]
333    PasswordCleartext,
334    #[serde(rename = "password-encrypted")]
335    PasswordEncrypted,
336    #[serde(rename = "NTLM")]
337    Ntlm,
338    #[serde(rename = "GSAPI")]
339    GsApi,
340    #[serde(rename = "client-IP-address")]
341    ClientIPAddress,
342    #[serde(rename = "TLS-client-cert")]
343    TlsClientCert,
344    OAuth2,
345    #[serde(rename = "None")]
346    None,
347}
348
349#[derive(Debug, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct Pop3Config {
352    leave_messages_on_server: bool,
353    download_on_biff: Option<bool>,
354    days_to_leave_messages_on_server: Option<u64>,
355    check_interval: Option<CheckInterval>,
356}
357
358impl Pop3Config {
359    /// If the server should leave all of the read messages on the
360    /// server after the client quits the connection.
361    pub fn leave_messages_on_server(&self) -> &bool {
362        &self.leave_messages_on_server
363    }
364
365    pub fn download_on_biff(&self) -> Option<&bool> {
366        self.download_on_biff.as_ref()
367    }
368
369    /// How long the Pop messages will be stored on the server.
370    pub fn time_to_leave_messages_on_server(&self) -> Option<Duration> {
371        self.days_to_leave_messages_on_server
372            .as_ref()
373            .map(|days| Duration::from_secs(days * 24 * 60 * 60))
374    }
375
376    /// The interval in which the server will allow a check for new
377    /// messages. Not supported on all servers.
378    pub fn check_interval(&self) -> Option<Duration> {
379        match self.check_interval.as_ref() {
380            Some(interval) => {
381                if let Some(minutes) = interval.minutes {
382                    return Some(Duration::from_secs(minutes * 60));
383                };
384
385                None
386            }
387            None => None,
388        }
389    }
390}
391
392#[derive(Debug, Deserialize)]
393struct CheckInterval {
394    minutes: Option<u64>,
395}
396
397#[derive(Debug, Deserialize)]
398pub struct Documentation {
399    url: String,
400    #[serde(rename = "$value")]
401    properties: Vec<DocumentationDescription>,
402}
403
404impl Documentation {
405    /// Where the documentation can be found.
406    pub fn url(&self) -> &str {
407        &self.url
408    }
409
410    /// The documentation in different languages.
411    pub fn properties(&self) -> &Vec<DocumentationDescription> {
412        &self.properties
413    }
414}
415
416#[derive(Debug, Deserialize)]
417pub struct DocumentationDescription {
418    lang: Option<String>,
419    #[serde(rename = "$value")]
420    description: String,
421}
422
423impl DocumentationDescription {
424    /// What language the documentation is in.
425    pub fn language(&self) -> Option<&str> {
426        self.lang.as_deref()
427    }
428
429    /// The documentation.
430    pub fn description(&self) -> &str {
431        &self.description
432    }
433}