valid_email_checker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +24 -0
  3. data/Cargo.lock +2940 -0
  4. data/Cargo.toml +22 -0
  5. data/LICENSE.txt +661 -0
  6. data/NOTICE.md +56 -0
  7. data/README.md +316 -0
  8. data/exe/valid_email_checker +148 -0
  9. data/ext/valid_email_checker/Cargo.toml +27 -0
  10. data/ext/valid_email_checker/extconf.rb +26 -0
  11. data/ext/valid_email_checker/src/lib.rs +371 -0
  12. data/ext/valid_email_checker/src/nogvl.rs +64 -0
  13. data/ext/valid_email_checker/vendor/UPSTREAM.json +9 -0
  14. data/ext/valid_email_checker/vendor/check-if-email-exists/Cargo.toml +52 -0
  15. data/ext/valid_email_checker/vendor/check-if-email-exists/LICENSE.AGPL +661 -0
  16. data/ext/valid_email_checker/vendor/check-if-email-exists/LICENSE.md +11 -0
  17. data/ext/valid_email_checker/vendor/check-if-email-exists/README.md +175 -0
  18. data/ext/valid_email_checker/vendor/check-if-email-exists/src/haveibeenpwned.rs +70 -0
  19. data/ext/valid_email_checker/vendor/check-if-email-exists/src/lib.rs +281 -0
  20. data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/b2c.txt +96640 -0
  21. data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/gravatar.rs +60 -0
  22. data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/mod.rs +124 -0
  23. data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/roles.txt +944 -0
  24. data/ext/valid_email_checker/vendor/check-if-email-exists/src/mx/mod.rs +165 -0
  25. data/ext/valid_email_checker/vendor/check-if-email-exists/src/rules.json +28 -0
  26. data/ext/valid_email_checker/vendor/check-if-email-exists/src/rules.rs +105 -0
  27. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/connect.rs +396 -0
  28. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/error.rs +144 -0
  29. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/gmail.rs +99 -0
  30. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/headless.rs +82 -0
  31. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/http_api.rs +27 -0
  32. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/mod.rs +234 -0
  33. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/headless.rs +181 -0
  34. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/microsoft365.rs +109 -0
  35. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/mod.rs +2 -0
  36. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/parser.rs +291 -0
  37. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/verif_method.rs +531 -0
  38. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/api.rs +174 -0
  39. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/headless.rs +188 -0
  40. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/mod.rs +62 -0
  41. data/ext/valid_email_checker/vendor/check-if-email-exists/src/syntax/mod.rs +199 -0
  42. data/ext/valid_email_checker/vendor/check-if-email-exists/src/syntax/normalize.rs +70 -0
  43. data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/input_output.rs +353 -0
  44. data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/mod.rs +20 -0
  45. data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/sentry.rs +173 -0
  46. data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/ser_with_display.rs +28 -0
  47. data/lib/valid_email_checker/configuration.rb +216 -0
  48. data/lib/valid_email_checker/errors.rb +32 -0
  49. data/lib/valid_email_checker/result.rb +241 -0
  50. data/lib/valid_email_checker/syntax.rb +72 -0
  51. data/lib/valid_email_checker/version.rb +5 -0
  52. data/lib/valid_email_checker.rb +217 -0
  53. metadata +118 -0
@@ -0,0 +1,531 @@
1
+ // check-if-email-exists
2
+ // Copyright (C) 2018-2023 Reacher
3
+
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU Affero General Public License as published
6
+ // by the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU Affero General Public License for more details.
13
+
14
+ // You should have received a copy of the GNU Affero General Public License
15
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ //! TODO: This will ultimately be moved to core.
18
+
19
+ use std::{collections::HashMap, time::Duration};
20
+
21
+ use crate::{
22
+ mx::{is_mimecast, is_proofpoint},
23
+ util::input_output::CheckEmailInputProxy,
24
+ };
25
+ use serde::{Deserialize, Serialize};
26
+
27
+ use super::{is_gmail, is_hotmail_b2b, is_hotmail_b2c, is_yahoo};
28
+
29
+ #[derive(Debug, thiserror::Error)]
30
+ pub enum VerifMethodError {
31
+ #[error("Invalid proxies: {0}")]
32
+ InvalidProxies(String),
33
+ }
34
+
35
+ /// Reacher categorizes each email into one of the following email providers.
36
+ /// This is used to determine the verification method to use for each email
37
+ /// provider.
38
+ pub enum EmailProvider {
39
+ Gmail,
40
+ HotmailB2B,
41
+ HotmailB2C,
42
+ Proofpoint,
43
+ Mimecast,
44
+ Yahoo,
45
+ EverythingElse,
46
+ }
47
+
48
+ impl EmailProvider {
49
+ /// Determine the email provider from the MX host.
50
+ pub fn from_mx_host(host: &str) -> Self {
51
+ if is_hotmail_b2c(host) {
52
+ EmailProvider::HotmailB2C
53
+ } else if is_hotmail_b2b(host) {
54
+ EmailProvider::HotmailB2B
55
+ } else if is_gmail(host) {
56
+ EmailProvider::Gmail
57
+ } else if is_yahoo(host) {
58
+ EmailProvider::Yahoo
59
+ } else if is_proofpoint(host) {
60
+ EmailProvider::Proofpoint
61
+ } else if is_mimecast(host) {
62
+ EmailProvider::Mimecast
63
+ } else {
64
+ EmailProvider::EverythingElse
65
+ }
66
+ }
67
+ }
68
+
69
+ type ProxyID = String;
70
+ // This is a convention ID for the proxy in the top-level "proxy" field of the
71
+ // request, which will be added as a key in the `proxies` map of the
72
+ // `VerifMethod` struct.
73
+ pub const DEFAULT_PROXY_ID: &str = "default";
74
+
75
+ /// The verification method to use for each email provider.
76
+ #[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
77
+ #[serde(default)]
78
+ pub struct VerifMethod {
79
+ /// Proxies to use for email verification. The key is any unique name for
80
+ /// the proxy, and the value is the proxy itself. For names, we recommend
81
+ /// "proxy1", "proxy2", etc.
82
+ pub proxies: HashMap<ProxyID, CheckEmailInputProxy>,
83
+ /// Verification method for Gmail.
84
+ pub gmail: GmailVerifMethod,
85
+ /// Verification method for Hotmail B2B.
86
+ pub hotmailb2b: HotmailB2BVerifMethod,
87
+ /// Verification method for Hotmail B2C.
88
+ pub hotmailb2c: HotmailB2CVerifMethod,
89
+ /// Verification method for Mimecast.
90
+ pub mimecast: MimecastVerifMethod,
91
+ /// Verification method for Proofpoint.
92
+ pub proofpoint: ProofpointVerifMethod,
93
+ /// Verification method for Yahoo.
94
+ pub yahoo: YahooVerifMethod,
95
+ /// Verification method for everything else.
96
+ pub everything_else: EverythingElseVerifMethod,
97
+ }
98
+
99
+ impl VerifMethod {
100
+ /// Create a new `VerifMethod` with the same configuration for all email
101
+ /// providers.
102
+ pub fn new_with_same_config_for_all(
103
+ proxy: Option<CheckEmailInputProxy>,
104
+ hello_name: String,
105
+ from_email: String,
106
+ smtp_port: u16,
107
+ smtp_timeout: Option<Duration>,
108
+ retries: usize,
109
+ ) -> Self {
110
+ let mut proxies = HashMap::new();
111
+ let proxy_id: Option<String> = if let Some(proxy) = proxy {
112
+ let proxy_id = DEFAULT_PROXY_ID.to_string();
113
+ proxies.insert(proxy_id.clone(), proxy);
114
+ Some(proxy_id)
115
+ } else {
116
+ None
117
+ };
118
+
119
+ Self {
120
+ proxies,
121
+ gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
122
+ proxy: proxy_id.clone(),
123
+ hello_name: hello_name.clone(),
124
+ from_email: from_email.clone(),
125
+ smtp_port,
126
+ smtp_timeout,
127
+ retries,
128
+ }),
129
+ hotmailb2b: HotmailB2BVerifMethod::Smtp(VerifMethodSmtpConfig {
130
+ proxy: proxy_id.clone(),
131
+ hello_name: hello_name.clone(),
132
+ from_email: from_email.clone(),
133
+ smtp_port,
134
+ smtp_timeout,
135
+ retries,
136
+ }),
137
+ mimecast: MimecastVerifMethod::Smtp(VerifMethodSmtpConfig {
138
+ proxy: proxy_id.clone(),
139
+ hello_name: hello_name.clone(),
140
+ from_email: from_email.clone(),
141
+ smtp_port,
142
+ smtp_timeout,
143
+ retries,
144
+ }),
145
+ proofpoint: ProofpointVerifMethod::Smtp(VerifMethodSmtpConfig {
146
+ proxy: proxy_id.clone(),
147
+ hello_name: hello_name.clone(),
148
+ from_email: from_email.clone(),
149
+ smtp_port,
150
+ smtp_timeout,
151
+ retries,
152
+ }),
153
+ everything_else: EverythingElseVerifMethod::Smtp(VerifMethodSmtpConfig {
154
+ proxy: proxy_id,
155
+ hello_name,
156
+ from_email,
157
+ smtp_port,
158
+ smtp_timeout,
159
+ retries,
160
+ }),
161
+ ..Default::default()
162
+ }
163
+ }
164
+
165
+ pub fn validate_proxies(&self) -> Result<(), VerifMethodError> {
166
+ match &self.gmail {
167
+ GmailVerifMethod::Smtp(c) => {
168
+ if let Some(proxy_id) = &c.proxy {
169
+ self.proxies.get(proxy_id).ok_or_else(|| {
170
+ VerifMethodError::InvalidProxies(format!("Invalid Gmail proxy {proxy_id}"))
171
+ })?;
172
+ }
173
+ }
174
+ };
175
+
176
+ match &self.hotmailb2b {
177
+ HotmailB2BVerifMethod::Smtp(c) => {
178
+ if let Some(proxy_id) = &c.proxy {
179
+ self.proxies.get(proxy_id).ok_or_else(|| {
180
+ VerifMethodError::InvalidProxies(format!(
181
+ "Invalid Hotmail B2B proxy {proxy_id}"
182
+ ))
183
+ })?;
184
+ }
185
+ }
186
+ };
187
+
188
+ if let HotmailB2CVerifMethod::Smtp(c) = &self.hotmailb2c {
189
+ if let Some(proxy_id) = &c.proxy {
190
+ self.proxies.get(proxy_id).ok_or_else(|| {
191
+ VerifMethodError::InvalidProxies(format!(
192
+ "Invalid Hotmail B2C proxy {proxy_id}"
193
+ ))
194
+ })?;
195
+ }
196
+ };
197
+
198
+ if let YahooVerifMethod::Smtp(c) = &self.yahoo {
199
+ if let Some(proxy_id) = &c.proxy {
200
+ self.proxies.get(proxy_id).ok_or_else(|| {
201
+ VerifMethodError::InvalidProxies(format!("Invalid Yahoo proxy {proxy_id}"))
202
+ })?;
203
+ }
204
+ };
205
+
206
+ match &self.everything_else {
207
+ EverythingElseVerifMethod::Smtp(c) => {
208
+ if let Some(proxy_id) = &c.proxy {
209
+ self.proxies.get(proxy_id).ok_or_else(|| {
210
+ VerifMethodError::InvalidProxies(format!(
211
+ "Invalid EverythingElse proxy {proxy_id}"
212
+ ))
213
+ })?;
214
+ }
215
+ }
216
+ };
217
+
218
+ Ok(())
219
+ }
220
+
221
+ /// Get the proxy to use for the email provider. If there is a
222
+ /// configuration error, such as an unconfigured proxy, this will return
223
+ /// an error.
224
+ pub fn get_proxy(&self, email_provider: EmailProvider) -> Option<&CheckEmailInputProxy> {
225
+ match email_provider {
226
+ EmailProvider::Gmail => match &self.gmail {
227
+ GmailVerifMethod::Smtp(c) => c
228
+ .proxy
229
+ .as_ref()
230
+ .and_then(|proxy_id| self.proxies.get(proxy_id)),
231
+ },
232
+ EmailProvider::HotmailB2B => match &self.hotmailb2b {
233
+ HotmailB2BVerifMethod::Smtp(c) => c
234
+ .proxy
235
+ .as_ref()
236
+ .and_then(|proxy_id| self.proxies.get(proxy_id)),
237
+ },
238
+ EmailProvider::HotmailB2C => match &self.hotmailb2c {
239
+ HotmailB2CVerifMethod::Smtp(c) => c
240
+ .proxy
241
+ .as_ref()
242
+ .and_then(|proxy_id| self.proxies.get(proxy_id)),
243
+ _ => None,
244
+ },
245
+ EmailProvider::Mimecast => match &self.mimecast {
246
+ MimecastVerifMethod::Smtp(c) => c
247
+ .proxy
248
+ .as_ref()
249
+ .and_then(|proxy_id| self.proxies.get(proxy_id)),
250
+ },
251
+ EmailProvider::Proofpoint => match &self.proofpoint {
252
+ ProofpointVerifMethod::Smtp(c) => c
253
+ .proxy
254
+ .as_ref()
255
+ .and_then(|proxy_id| self.proxies.get(proxy_id)),
256
+ },
257
+ EmailProvider::Yahoo => match &self.yahoo {
258
+ YahooVerifMethod::Smtp(c) => c
259
+ .proxy
260
+ .as_ref()
261
+ .and_then(|proxy_id| self.proxies.get(proxy_id)),
262
+ _ => None,
263
+ },
264
+ EmailProvider::EverythingElse => match &self.everything_else {
265
+ EverythingElseVerifMethod::Smtp(c) => c
266
+ .proxy
267
+ .as_ref()
268
+ .and_then(|proxy_id| self.proxies.get(proxy_id)),
269
+ },
270
+ }
271
+ }
272
+ }
273
+
274
+ #[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
275
+ #[serde(tag = "type", rename_all = "lowercase")]
276
+ pub enum GmailVerifMethod {
277
+ Smtp(VerifMethodSmtpConfig),
278
+ }
279
+
280
+ impl Default for GmailVerifMethod {
281
+ fn default() -> Self {
282
+ GmailVerifMethod::Smtp(VerifMethodSmtpConfig::default())
283
+ }
284
+ }
285
+
286
+ #[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
287
+ #[serde(tag = "type", rename_all = "lowercase")]
288
+ pub enum HotmailB2BVerifMethod {
289
+ /// Use Hotmail's SMTP servers to check if an email exists.
290
+ Smtp(VerifMethodSmtpConfig),
291
+ }
292
+
293
+ impl Default for HotmailB2BVerifMethod {
294
+ fn default() -> Self {
295
+ HotmailB2BVerifMethod::Smtp(VerifMethodSmtpConfig::default())
296
+ }
297
+ }
298
+
299
+ #[derive(Debug, Default, Deserialize, Clone, PartialEq, Serialize)]
300
+ #[serde(tag = "type", rename_all = "lowercase")]
301
+ pub enum HotmailB2CVerifMethod {
302
+ /// Use Hotmail's password recovery page to check if an email exists.
303
+ ///
304
+ /// This assumes you have a WebDriver compatible process running, then pass
305
+ /// its endpoint, usually http://localhost:9515, into the environment
306
+ /// variable RCH_WEBDRIVER_ADDR. We recommend running chromedriver (and not
307
+ /// geckodriver) as it allows parallel requests.
308
+ #[default]
309
+ Headless,
310
+ /// Use Hotmail's SMTP servers to check if an email exists.
311
+ Smtp(VerifMethodSmtpConfig),
312
+ }
313
+
314
+ #[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
315
+ #[serde(tag = "type", rename_all = "lowercase")]
316
+ pub enum MimecastVerifMethod {
317
+ /// Use Mimecast's SMTP servers to check if an email exists.
318
+ Smtp(VerifMethodSmtpConfig),
319
+ }
320
+
321
+ impl Default for MimecastVerifMethod {
322
+ fn default() -> Self {
323
+ MimecastVerifMethod::Smtp(VerifMethodSmtpConfig::default())
324
+ }
325
+ }
326
+
327
+ #[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
328
+ #[serde(tag = "type", rename_all = "lowercase")]
329
+ pub enum ProofpointVerifMethod {
330
+ /// Use Proofpoint's SMTP servers to check if an email exists.
331
+ Smtp(VerifMethodSmtpConfig),
332
+ }
333
+
334
+ impl Default for ProofpointVerifMethod {
335
+ fn default() -> Self {
336
+ ProofpointVerifMethod::Smtp(VerifMethodSmtpConfig::default())
337
+ }
338
+ }
339
+
340
+ #[derive(Debug, Default, Deserialize, Clone, PartialEq, Serialize)]
341
+ #[serde(tag = "type", rename_all = "lowercase")]
342
+ pub enum YahooVerifMethod {
343
+ /// Use Yahoo's API to check if an email exists.
344
+ Api,
345
+ /// Use Yahoo's password recovery page to check if an email exists.
346
+ ///
347
+ /// This assumes you have a WebDriver compatible process running, then pass
348
+ /// its endpoint, usually http://localhost:9515, into the environment
349
+ /// variable RCH_WEBDRIVER_ADDR. We recommend running chromedriver (and not
350
+ /// geckodriver) as it allows parallel requests.
351
+ #[default]
352
+ Headless,
353
+ /// Use Yahoo's SMTP servers to check if an email exists.
354
+ Smtp(VerifMethodSmtpConfig),
355
+ }
356
+
357
+ #[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
358
+ #[serde(tag = "type", rename_all = "lowercase")]
359
+ pub enum EverythingElseVerifMethod {
360
+ /// Use the SMTP server of the email provider to check if an email exists.
361
+ Smtp(VerifMethodSmtpConfig),
362
+ }
363
+
364
+ impl Default for EverythingElseVerifMethod {
365
+ fn default() -> Self {
366
+ EverythingElseVerifMethod::Smtp(VerifMethodSmtpConfig::default())
367
+ }
368
+ }
369
+
370
+ /// Configuration on the SMTP verification method. If it used mostly as a
371
+ /// serializable struct, to be converted into the domain type
372
+ /// `VerifMethodSmtp`.
373
+ #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
374
+ #[serde(default)]
375
+ pub struct VerifMethodSmtpConfig {
376
+ /// Email to use in the `MAIL FROM:` SMTP command.
377
+ ///
378
+ /// Defaults to "reacher.email@gmail.com", which is an unused addressed
379
+ /// owned by Reacher.
380
+ ///
381
+ pub from_email: String,
382
+ /// Name to use in the `EHLO:` SMTP command.
383
+ ///
384
+ /// Defaults to "gmail.com" (note: "localhost" is not a FQDN), mostly for
385
+ /// testing purposes. You should set this to a domain you own.
386
+ pub hello_name: String,
387
+ /// Use a proxy to check if an email exists. This proxy must exist in the
388
+ /// `VerifMethod.proxies` field, and it must be a SOCKS5 proxy.
389
+ pub proxy: Option<ProxyID>,
390
+ /// SMTP port to use for email validation. Generally, ports 25, 465, 587
391
+ /// and 2525 are used.
392
+ ///
393
+ /// Defaults to 25.
394
+ pub smtp_port: u16,
395
+ /// Add timeout for the SMTP verification step. Set to None if you don't
396
+ /// want to use a timeout. This timeout is per SMTP connection. For
397
+ /// instance, if you set the number of retries to 2, then the total time
398
+ /// for the SMTP verification step can be up to 2 * `smtp_timeout`.
399
+ ///
400
+ /// Defaults to None.
401
+ pub smtp_timeout: Option<Duration>,
402
+ /// Number of total SMTP connections to do. Setting to 2 might bypass
403
+ /// greylisting on some servers, but takes more time.
404
+ ///
405
+ /// This setting's naming is a bit misleading, as it's not really retries,
406
+ /// but the total number of SMTP connections to do.
407
+ ///
408
+ /// Defaults to 1.
409
+ pub retries: usize,
410
+ }
411
+
412
+ impl Default for VerifMethodSmtpConfig {
413
+ fn default() -> Self {
414
+ Self {
415
+ from_email: "reacher@gmail.com".to_string(),
416
+ hello_name: "gmail.com".to_string(),
417
+ proxy: None,
418
+ smtp_port: 25,
419
+ smtp_timeout: None,
420
+ retries: 1,
421
+ }
422
+ }
423
+ }
424
+
425
+ #[derive(Debug, Deserialize, Serialize)]
426
+ pub struct VerifMethodSmtp {
427
+ pub config: VerifMethodSmtpConfig,
428
+ pub proxy: Option<CheckEmailInputProxy>,
429
+ }
430
+
431
+ impl VerifMethodSmtp {
432
+ pub fn new(config: VerifMethodSmtpConfig, proxy: Option<CheckEmailInputProxy>) -> Self {
433
+ Self { config, proxy }
434
+ }
435
+ }
436
+
437
+ #[cfg(test)]
438
+ mod tests {
439
+ use super::*;
440
+
441
+ #[test]
442
+ fn test_validate_proxies() {
443
+ let mut proxies = HashMap::new();
444
+ proxies.insert("proxy1".to_string(), CheckEmailInputProxy::default());
445
+ proxies.insert("proxy2".to_string(), CheckEmailInputProxy::default());
446
+
447
+ // Test invalid proxies.
448
+ let verif_method = VerifMethod {
449
+ proxies: proxies.clone(),
450
+ gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
451
+ proxy: Some("proxy3".to_string()),
452
+ ..Default::default()
453
+ }),
454
+ ..Default::default()
455
+ };
456
+
457
+ assert!(verif_method.validate_proxies().is_err());
458
+
459
+ // Test valid proxies.
460
+ let verif_method = VerifMethod {
461
+ proxies,
462
+ gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
463
+ proxy: Some("proxy1".to_string()),
464
+ ..Default::default()
465
+ }),
466
+ hotmailb2b: HotmailB2BVerifMethod::Smtp(VerifMethodSmtpConfig {
467
+ proxy: Some("proxy2".to_string()),
468
+ ..Default::default()
469
+ }),
470
+ hotmailb2c: HotmailB2CVerifMethod::Smtp(VerifMethodSmtpConfig {
471
+ proxy: Some("proxy1".to_string()),
472
+ ..Default::default()
473
+ }),
474
+ yahoo: YahooVerifMethod::Smtp(VerifMethodSmtpConfig {
475
+ proxy: Some("proxy2".to_string()),
476
+ ..Default::default()
477
+ }),
478
+ ..Default::default()
479
+ };
480
+
481
+ assert!(verif_method.validate_proxies().is_ok());
482
+ }
483
+
484
+ #[test]
485
+ fn test_get_proxy() {
486
+ let mut proxies = HashMap::new();
487
+ proxies.insert("proxy1".to_string(), CheckEmailInputProxy::default());
488
+ proxies.insert("proxy2".to_string(), CheckEmailInputProxy::default());
489
+
490
+ let verif_method = VerifMethod {
491
+ proxies: proxies.clone(),
492
+ gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
493
+ proxy: Some("proxy1".to_string()),
494
+ ..Default::default()
495
+ }),
496
+ hotmailb2b: HotmailB2BVerifMethod::Smtp(VerifMethodSmtpConfig {
497
+ proxy: Some("proxy2".to_string()),
498
+ ..Default::default()
499
+ }),
500
+ hotmailb2c: HotmailB2CVerifMethod::Smtp(VerifMethodSmtpConfig {
501
+ proxy: Some("proxy1".to_string()),
502
+ ..Default::default()
503
+ }),
504
+ yahoo: YahooVerifMethod::Smtp(VerifMethodSmtpConfig {
505
+ proxy: Some("proxy2".to_string()),
506
+ ..Default::default()
507
+ }),
508
+ ..Default::default()
509
+ };
510
+
511
+ assert_eq!(
512
+ verif_method.get_proxy(EmailProvider::Gmail),
513
+ proxies.get("proxy1")
514
+ );
515
+ assert_eq!(
516
+ verif_method.get_proxy(EmailProvider::HotmailB2B),
517
+ proxies.get("proxy2")
518
+ );
519
+ assert_eq!(
520
+ verif_method.get_proxy(EmailProvider::HotmailB2C),
521
+ proxies.get("proxy1")
522
+ );
523
+ assert_eq!(
524
+ verif_method.get_proxy(EmailProvider::Yahoo),
525
+ proxies.get("proxy2")
526
+ );
527
+ assert!(verif_method
528
+ .get_proxy(EmailProvider::EverythingElse)
529
+ .is_none());
530
+ }
531
+ }
@@ -0,0 +1,174 @@
1
+ // check-if-email-exists
2
+ // Copyright (C) 2018-2023 Reacher
3
+
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU Affero General Public License as published
6
+ // by the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU Affero General Public License for more details.
13
+
14
+ // You should have received a copy of the GNU Affero General Public License
15
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ use super::YahooError;
18
+ use crate::LOG_TARGET;
19
+ use crate::{
20
+ smtp::{http_api::create_client, SmtpDetails},
21
+ util::input_output::CheckEmailInput,
22
+ };
23
+ use regex::Regex;
24
+ use serde::{Deserialize, Serialize};
25
+
26
+ const SIGNUP_PAGE: &str = "https://login.yahoo.com/account/create?specId=yidReg&lang=en-US&src=&done=https%3A%2F%2Fwww.yahoo.com&display=login";
27
+ const SIGNUP_API: &str = "https://login.yahoo.com/account/module/create?validateField=yid";
28
+ const USER_AGENT: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36"; // Fake one to use in API requests
29
+
30
+ /// The form inputs to pass into the HTTP request.
31
+ #[derive(Serialize)]
32
+ struct FormRequest {
33
+ acrumb: String,
34
+ #[serde(rename(serialize = "sessionIndex"))]
35
+ session_index: String,
36
+ #[serde(rename(serialize = "specId"))]
37
+ spec_id: String,
38
+ #[serde(rename(serialize = "userId"))]
39
+ user_id: String,
40
+ }
41
+
42
+ impl Default for FormRequest {
43
+ fn default() -> Self {
44
+ FormRequest {
45
+ acrumb: "".into(),
46
+ session_index: "".into(),
47
+ spec_id: "yidReg".into(),
48
+ user_id: "".into(),
49
+ }
50
+ }
51
+ }
52
+
53
+ impl FormRequest {
54
+ fn new(acrumb: String, session_index: String, user_id: String) -> Self {
55
+ FormRequest {
56
+ acrumb,
57
+ session_index,
58
+ user_id,
59
+ ..Default::default()
60
+ }
61
+ }
62
+ }
63
+
64
+ /// One item in the response of the HTTP form request.
65
+ #[derive(Debug, Deserialize)]
66
+ struct FormResponseItem {
67
+ error: String,
68
+ name: String,
69
+ }
70
+
71
+ /// The response of the HTTP form request.
72
+ #[derive(Debug, Deserialize)]
73
+ struct FormResponse {
74
+ errors: Vec<FormResponseItem>,
75
+ }
76
+
77
+ /// Use well-crafted HTTP requests to verify if a Yahoo email address exists.
78
+ /// Inspired by https://github.com/hbattat/verifyEmail.
79
+ pub async fn check_api(to_email: &str, input: &CheckEmailInput) -> Result<SmtpDetails, YahooError> {
80
+ let res = create_client(input, "yahoo")?
81
+ .get(SIGNUP_PAGE)
82
+ .header("User-Agent", USER_AGENT)
83
+ .send()
84
+ .await?;
85
+
86
+ // Get the cookies from the response.
87
+ let cookies = match res.headers().get("Set-Cookie") {
88
+ Some(x) => x.to_owned(),
89
+ _ => {
90
+ return Err(YahooError::NoCookie);
91
+ }
92
+ };
93
+
94
+ tracing::debug!(
95
+ target: LOG_TARGET,
96
+ email=to_email,
97
+ "Yahoo succesfully got cookies after response"
98
+ );
99
+
100
+ let body = res.text().await?;
101
+
102
+ let username = to_email
103
+ .split('@')
104
+ .next()
105
+ .expect("The email is well-formed. qed.");
106
+
107
+ // From the cookies, fetch the "acrumb" field.
108
+ let acrumb = match cookies.to_str() {
109
+ Ok(x) => x,
110
+ _ => {
111
+ return Err(YahooError::NoAcrumb);
112
+ }
113
+ };
114
+ let re = Regex::new(r"s=(?P<acrumb>[^;]*)&d").expect("Correct regex. qed.");
115
+ let acrumb = match re.captures(acrumb) {
116
+ Some(x) => x,
117
+ _ => {
118
+ return Err(YahooError::NoAcrumb);
119
+ }
120
+ };
121
+
122
+ let re =
123
+ Regex::new(r#"<input type="hidden" value="(?P<sessionIndex>.*)" name="sessionIndex">"#)
124
+ .expect("Correct regex. qed");
125
+ let session_index = match re.captures(&body) {
126
+ Some(y) => y,
127
+ _ => {
128
+ return Err(YahooError::NoSessionIndex);
129
+ }
130
+ };
131
+
132
+ // Mimic a real HTTP request.
133
+ let res = create_client(input, "yahoo")?
134
+ .post(SIGNUP_API)
135
+ .header("Origin", "https://login.yahoo.com")
136
+ .header("X-Requested-With", "XMLHttpRequest")
137
+ .header("User-Agent", USER_AGENT)
138
+ .header(
139
+ "Content-type",
140
+ "application/x-www-form-urlencoded; charset=UTF-8",
141
+ )
142
+ .header("Accept", "*/*")
143
+ .header("Referer", SIGNUP_PAGE)
144
+ .header("Accept-Encoding", "gzip, deflate, br")
145
+ .header("Accept-Language", "en-US,en;q=0.8,ar;q=0.6")
146
+ .header("Cookie", &cookies)
147
+ .json(&FormRequest::new(
148
+ acrumb["acrumb"].to_string(),
149
+ session_index["sessionIndex"].to_string(),
150
+ username.into(),
151
+ ))
152
+ .send()
153
+ .await?
154
+ .json::<FormResponse>()
155
+ .await?;
156
+
157
+ tracing::debug!(
158
+ target: LOG_TARGET,
159
+ email=to_email,
160
+ "Yahoo 2nd response: {:?}",
161
+ res
162
+ );
163
+
164
+ let username_exists = res.errors.iter().any(|item| {
165
+ item.name == "userId"
166
+ && (item.error == "IDENTIFIER_NOT_AVAILABLE" || item.error == "IDENTIFIER_EXISTS")
167
+ });
168
+
169
+ Ok(SmtpDetails {
170
+ can_connect_smtp: true,
171
+ is_deliverable: username_exists,
172
+ ..Default::default()
173
+ })
174
+ }