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)]
148pub struct ProviderOptions {
149 pub strict_tls: bool,
152
153 pub max_smtp_rcpt_to: Option<u16>,
155
156 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
170pub 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
179pub 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 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
198pub 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}