deltachat/
provider.rs

1//! [Provider database](https://providers.delta.chat/) module.
2
3pub(crate) mod data;
4
5use anyhow::Result;
6use deltachat_contact_tools::EmailAddress;
7use serde::{Deserialize, Serialize};
8
9use crate::config::Config;
10use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS};
11
12/// Provider status according to manual testing.
13#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
14#[repr(u8)]
15pub enum Status {
16    /// Provider is known to be working with Delta Chat.
17    Ok = 1,
18
19    /// Provider works with Delta Chat, but requires some preparation,
20    /// such as changing the settings in the web interface.
21    Preparation = 2,
22
23    /// Provider is known not to work with Delta Chat.
24    Broken = 3,
25}
26
27/// Server protocol.
28#[derive(Debug, Display, PartialEq, Eq, Copy, Clone, FromPrimitive, ToPrimitive)]
29#[repr(u8)]
30pub enum Protocol {
31    /// SMTP protocol.
32    Smtp = 1,
33
34    /// IMAP protocol.
35    Imap = 2,
36}
37
38/// Socket security.
39#[derive(
40    Debug,
41    Default,
42    Display,
43    PartialEq,
44    Eq,
45    Copy,
46    Clone,
47    FromPrimitive,
48    ToPrimitive,
49    Serialize,
50    Deserialize,
51)]
52#[repr(u8)]
53pub enum Socket {
54    /// Unspecified socket security, select automatically.
55    #[default]
56    Automatic = 0,
57
58    /// TLS connection.
59    Ssl = 1,
60
61    /// STARTTLS connection.
62    Starttls = 2,
63
64    /// No TLS, plaintext connection.
65    Plain = 3,
66}
67
68/// Pattern used to construct login usernames from email addresses.
69#[derive(Debug, PartialEq, Eq, Clone)]
70#[repr(u8)]
71pub enum UsernamePattern {
72    /// Whole email is used as username.
73    Email = 1,
74
75    /// Part of address before `@` is used as username.
76    Emaillocalpart = 2,
77}
78
79/// Type of OAuth 2 authorization.
80#[derive(Debug, PartialEq, Eq)]
81pub enum Oauth2Authorizer {
82    /// Yandex.
83    Yandex,
84}
85
86/// Email server endpoint.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Server {
89    /// Server protocol, e.g. SMTP or IMAP.
90    pub protocol: Protocol,
91
92    /// Port security, e.g. TLS or STARTTLS.
93    pub socket: Socket,
94
95    /// Server host.
96    pub hostname: &'static str,
97
98    /// Server port.
99    pub port: u16,
100
101    /// Pattern used to construct login usernames from email addresses.
102    pub username_pattern: UsernamePattern,
103}
104
105/// Pair of key and value for default configuration.
106#[derive(Debug, PartialEq, Eq)]
107pub struct ConfigDefault {
108    /// Configuration variable name.
109    pub key: Config,
110
111    /// Configuration variable value.
112    pub value: &'static str,
113}
114
115/// Provider database entry.
116#[derive(Debug, PartialEq, Eq)]
117pub struct Provider {
118    /// Unique ID, corresponding to provider database filename.
119    pub id: &'static str,
120
121    /// Provider status according to manual testing.
122    pub status: Status,
123
124    /// Hint to be shown to the user on the login screen.
125    pub before_login_hint: &'static str,
126
127    /// Hint to be added to the device chat after provider configuration.
128    pub after_login_hint: &'static str,
129
130    /// URL of the page with provider overview.
131    pub overview_page: &'static str,
132
133    /// List of provider servers.
134    pub server: &'static [Server],
135
136    /// Default configuration values to set when provider is configured.
137    pub config_defaults: Option<&'static [ConfigDefault]>,
138
139    /// Type of OAuth 2 authorization if provider supports it.
140    pub oauth2_authorizer: Option<Oauth2Authorizer>,
141
142    /// Options with good defaults.
143    pub opt: ProviderOptions,
144}
145
146/// Provider options with good defaults.
147#[derive(Debug, PartialEq, Eq)]
148pub struct ProviderOptions {
149    /// True if provider is known to use use proper,
150    /// not self-signed certificates.
151    pub strict_tls: bool,
152
153    /// Maximum number of recipients the provider allows to send a single email to.
154    pub max_smtp_rcpt_to: Option<u16>,
155
156    /// Move messages to the Trash folder instead of marking them "\Deleted".
157    pub delete_to_trash: bool,
158}
159
160impl ProviderOptions {
161    const fn new() -> Self {
162        Self {
163            strict_tls: true,
164            max_smtp_rcpt_to: None,
165            delete_to_trash: false,
166        }
167    }
168}
169
170/// Returns provider for the given an e-mail address.
171///
172/// Returns an error if provided address is not valid.
173pub fn get_provider_info_by_addr(addr: &str) -> Result<Option<&'static Provider>> {
174    let addr = EmailAddress::new(addr)?;
175
176    Ok(get_provider_info(&addr.domain))
177}
178
179/// Finds a provider in offline database based on domain.
180pub fn get_provider_info(domain: &str) -> Option<&'static Provider> {
181    let domain = domain.to_lowercase();
182    for (pattern, provider) in PROVIDER_DATA {
183        if let Some(suffix) = pattern.strip_prefix('*') {
184            // Wildcard domain pattern.
185            //
186            // For example, `suffix` is ".hermes.radio" for "*.hermes.radio" pattern.
187            if domain.ends_with(suffix) {
188                return Some(provider);
189            }
190        } else if pattern == domain {
191            return Some(provider);
192        }
193    }
194
195    None
196}
197
198/// Returns a provider with the given ID from the database.
199pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> {
200    if let Some(provider) = PROVIDER_IDS.get(id) {
201        Some(provider)
202    } else {
203        None
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_get_provider_by_domain_unexistant() {
213        let provider = get_provider_info("unexistant.org");
214        assert!(provider.is_none());
215    }
216
217    #[test]
218    fn test_get_provider_by_domain_mixed_case() {
219        let provider = get_provider_info("nAUta.Cu").unwrap();
220        assert!(provider.status == Status::Ok);
221    }
222
223    #[test]
224    fn test_get_provider_info() {
225        let addr = "nauta.cu";
226        let provider = get_provider_info(addr).unwrap();
227        assert!(provider.status == Status::Ok);
228        let server = &provider.server[0];
229        assert_eq!(server.protocol, Protocol::Imap);
230        assert_eq!(server.socket, Socket::Starttls);
231        assert_eq!(server.hostname, "imap.nauta.cu");
232        assert_eq!(server.port, 143);
233        assert_eq!(server.username_pattern, UsernamePattern::Email);
234        let server = &provider.server[1];
235        assert_eq!(server.protocol, Protocol::Smtp);
236        assert_eq!(server.socket, Socket::Starttls);
237        assert_eq!(server.hostname, "smtp.nauta.cu");
238        assert_eq!(server.port, 25);
239        assert_eq!(server.username_pattern, UsernamePattern::Email);
240
241        let provider = get_provider_info("gmail.com").unwrap();
242        assert!(provider.status == Status::Preparation);
243        assert!(!provider.before_login_hint.is_empty());
244        assert!(!provider.overview_page.is_empty());
245
246        let provider = get_provider_info("googlemail.com").unwrap();
247        assert!(provider.status == Status::Preparation);
248
249        assert!(get_provider_info("").is_none());
250        assert!(get_provider_info("google.com").unwrap().id == "gmail");
251        assert!(get_provider_info("example@google.com").is_none());
252    }
253
254    #[test]
255    fn test_get_provider_by_id() {
256        let provider = get_provider_by_id("gmail").unwrap();
257        assert!(provider.id == "gmail");
258    }
259
260    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
261    async fn test_get_provider_info_by_addr() -> Result<()> {
262        assert!(get_provider_info_by_addr("google.com").is_err());
263        assert!(get_provider_info_by_addr("example@google.com")?.unwrap().id == "gmail");
264        Ok(())
265    }
266}