1pub(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#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
14#[repr(u8)]
15pub enum Status {
16 Ok = 1,
18
19 Preparation = 2,
22
23 Broken = 3,
25}
26
27#[derive(Debug, Display, PartialEq, Eq, Copy, Clone, FromPrimitive, ToPrimitive)]
29#[repr(u8)]
30pub enum Protocol {
31 Smtp = 1,
33
34 Imap = 2,
36}
37
38#[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 #[default]
56 Automatic = 0,
57
58 Ssl = 1,
60
61 Starttls = 2,
63
64 Plain = 3,
66}
67
68#[derive(Debug, PartialEq, Eq, Clone)]
70#[repr(u8)]
71pub enum UsernamePattern {
72 Email = 1,
74
75 Emaillocalpart = 2,
77}
78
79#[derive(Debug, PartialEq, Eq)]
81pub enum Oauth2Authorizer {
82 Yandex,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Server {
89 pub protocol: Protocol,
91
92 pub socket: Socket,
94
95 pub hostname: &'static str,
97
98 pub port: u16,
100
101 pub username_pattern: UsernamePattern,
103}
104
105#[derive(Debug, PartialEq, Eq)]
107pub struct ConfigDefault {
108 pub key: Config,
110
111 pub value: &'static str,
113}
114
115#[derive(Debug, PartialEq, Eq)]
117pub struct Provider {
118 pub id: &'static str,
120
121 pub status: Status,
123
124 pub before_login_hint: &'static str,
126
127 pub after_login_hint: &'static str,
129
130 pub overview_page: &'static str,
132
133 pub server: &'static [Server],
135
136 pub config_defaults: Option<&'static [ConfigDefault]>,
138
139 pub oauth2_authorizer: Option<Oauth2Authorizer>,
141
142 pub opt: ProviderOptions,
144}
145
146#[derive(Debug, PartialEq, Eq)]
148#[non_exhaustive]
149pub struct ProviderOptions {
150 pub strict_tls: bool,
153
154 pub max_smtp_rcpt_to: Option<u16>,
156}
157
158impl ProviderOptions {
159 const fn new() -> Self {
160 Self {
161 strict_tls: true,
162 max_smtp_rcpt_to: None,
163 }
164 }
165}
166
167pub fn get_provider_info_by_addr(addr: &str) -> Result<Option<&'static Provider>> {
171 let addr = EmailAddress::new(addr)?;
172
173 Ok(get_provider_info(&addr.domain))
174}
175
176pub fn get_provider_info(domain: &str) -> Option<&'static Provider> {
178 let domain = domain.to_lowercase();
179 for (pattern, provider) in PROVIDER_DATA {
180 if let Some(suffix) = pattern.strip_prefix('*') {
181 if domain.ends_with(suffix) {
185 return Some(provider);
186 }
187 } else if pattern == domain {
188 return Some(provider);
189 }
190 }
191
192 None
193}
194
195pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> {
197 if let Some(provider) = PROVIDER_IDS.get(id) {
198 Some(provider)
199 } else {
200 None
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_get_provider_by_domain_unexistant() {
210 let provider = get_provider_info("unexistant.org");
211 assert!(provider.is_none());
212 }
213
214 #[test]
215 fn test_get_provider_by_domain_mixed_case() {
216 let provider = get_provider_info("nAUta.Cu").unwrap();
217 assert!(provider.status == Status::Ok);
218 }
219
220 #[test]
221 fn test_get_provider_info() {
222 let addr = "nauta.cu";
223 let provider = get_provider_info(addr).unwrap();
224 assert!(provider.status == Status::Ok);
225 let server = &provider.server[0];
226 assert_eq!(server.protocol, Protocol::Imap);
227 assert_eq!(server.socket, Socket::Starttls);
228 assert_eq!(server.hostname, "imap.nauta.cu");
229 assert_eq!(server.port, 143);
230 assert_eq!(server.username_pattern, UsernamePattern::Email);
231 let server = &provider.server[1];
232 assert_eq!(server.protocol, Protocol::Smtp);
233 assert_eq!(server.socket, Socket::Starttls);
234 assert_eq!(server.hostname, "smtp.nauta.cu");
235 assert_eq!(server.port, 25);
236 assert_eq!(server.username_pattern, UsernamePattern::Email);
237
238 let provider = get_provider_info("gmail.com").unwrap();
239 assert!(provider.status == Status::Preparation);
240 assert!(!provider.before_login_hint.is_empty());
241 assert!(!provider.overview_page.is_empty());
242
243 let provider = get_provider_info("googlemail.com").unwrap();
244 assert!(provider.status == Status::Preparation);
245
246 assert!(get_provider_info("").is_none());
247 assert!(get_provider_info("google.com").unwrap().id == "gmail");
248 assert!(get_provider_info("example@google.com").is_none());
249 }
250
251 #[test]
252 fn test_get_provider_by_id() {
253 let provider = get_provider_by_id("gmail").unwrap();
254 assert!(provider.id == "gmail");
255 }
256
257 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
258 async fn test_get_provider_info_by_addr() -> Result<()> {
259 assert!(get_provider_info_by_addr("google.com").is_err());
260 assert!(get_provider_info_by_addr("example@google.com")?.unwrap().id == "gmail");
261 Ok(())
262 }
263}