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,144 @@
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::gmail::GmailError;
18
+ use super::headless::HeadlessError;
19
+ use super::outlook::microsoft365::Microsoft365Error;
20
+ use super::parser;
21
+ use super::yahoo::YahooError;
22
+ use crate::util::ser_with_display::ser_with_display;
23
+ use async_smtp::error::Error as AsyncSmtpError;
24
+ use serde::Serialize;
25
+ use std::time::Duration;
26
+ use thiserror::Error;
27
+
28
+ /// Error occurred connecting to this email server via SMTP.
29
+ #[derive(Debug, Error, Serialize)]
30
+ #[serde(tag = "type", content = "message")]
31
+ pub enum SmtpError {
32
+ /// Error when verifying a Yahoo email via HTTP requests.
33
+ #[error("Yahoo error: {0}")]
34
+ YahooError(YahooError),
35
+ /// Error when verifying a Gmail email via a HTTP request.
36
+ #[error("Gmail error: {0}")]
37
+ GmailError(GmailError),
38
+ /// Error when verifying a Hotmail email via headless browser.
39
+ #[error("Headless verification error: {0}")]
40
+ HeadlessError(HeadlessError),
41
+ /// Error when verifying a Microsoft 365 email via HTTP request.
42
+ #[error("Microsoft 365 API error: {0}")]
43
+ Microsoft365Error(Microsoft365Error),
44
+ /// Error from async-smtp crate.
45
+ #[error("SMTP error: {0}")]
46
+ #[serde(serialize_with = "ser_with_display")]
47
+ AsyncSmtpError(AsyncSmtpError),
48
+ /// I/O error.
49
+ #[error("I/O error: {0}")]
50
+ #[serde(serialize_with = "ser_with_display")]
51
+ IOError(std::io::Error),
52
+ /// Timeout error.
53
+ #[error("Timeout error: {0:?}")]
54
+ Timeout(Duration),
55
+ /// SOCKS5 proxy error.
56
+ #[error("SOCKS5 error: {0}")]
57
+ #[serde(serialize_with = "ser_with_display")]
58
+ Socks5(fast_socks5::SocksError),
59
+ /// Anyhow error.
60
+ /// This is a catch-all error type for any error that can't be categorized
61
+ /// into the above types.
62
+ #[error("Anyhow error: {0}")]
63
+ #[serde(serialize_with = "ser_with_display")]
64
+ AnyhowError(anyhow::Error),
65
+ }
66
+
67
+ impl From<YahooError> for SmtpError {
68
+ fn from(e: YahooError) -> Self {
69
+ SmtpError::YahooError(e)
70
+ }
71
+ }
72
+
73
+ impl From<GmailError> for SmtpError {
74
+ fn from(e: GmailError) -> Self {
75
+ SmtpError::GmailError(e)
76
+ }
77
+ }
78
+
79
+ impl From<HeadlessError> for SmtpError {
80
+ fn from(e: HeadlessError) -> Self {
81
+ SmtpError::HeadlessError(e)
82
+ }
83
+ }
84
+
85
+ impl From<Microsoft365Error> for SmtpError {
86
+ fn from(e: Microsoft365Error) -> Self {
87
+ SmtpError::Microsoft365Error(e)
88
+ }
89
+ }
90
+
91
+ impl From<AsyncSmtpError> for SmtpError {
92
+ fn from(e: AsyncSmtpError) -> Self {
93
+ SmtpError::AsyncSmtpError(e)
94
+ }
95
+ }
96
+
97
+ impl From<std::io::Error> for SmtpError {
98
+ fn from(e: std::io::Error) -> Self {
99
+ SmtpError::IOError(e)
100
+ }
101
+ }
102
+
103
+ impl From<fast_socks5::SocksError> for SmtpError {
104
+ fn from(e: fast_socks5::SocksError) -> Self {
105
+ SmtpError::Socks5(e)
106
+ }
107
+ }
108
+
109
+ impl From<anyhow::Error> for SmtpError {
110
+ fn from(e: anyhow::Error) -> Self {
111
+ SmtpError::AnyhowError(e)
112
+ }
113
+ }
114
+
115
+ impl SmtpError {
116
+ /// Get a human-understandable description of the error, in form of an enum
117
+ /// SmtpErrorDesc. This only parses the following known errors:
118
+ /// - IP blacklisted
119
+ /// - IP needs reverse DNS
120
+ pub fn get_description(&self) -> Option<SmtpErrorDesc> {
121
+ match self {
122
+ SmtpError::AsyncSmtpError(_) => {
123
+ if parser::is_err_ip_blacklisted(self) {
124
+ Some(SmtpErrorDesc::IpBlacklisted)
125
+ } else if parser::is_err_needs_rdns(self) {
126
+ Some(SmtpErrorDesc::NeedsRDNS)
127
+ } else {
128
+ None
129
+ }
130
+ }
131
+ _ => None,
132
+ }
133
+ }
134
+ }
135
+
136
+ #[derive(Debug, Serialize)]
137
+ /// SmtpErrorDesc describes a description of which category the error belongs
138
+ /// to.
139
+ pub enum SmtpErrorDesc {
140
+ /// The IP is blacklisted.
141
+ IpBlacklisted,
142
+ /// The IP needs a reverse DNS entry.
143
+ NeedsRDNS,
144
+ }
@@ -0,0 +1,99 @@
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::SmtpDetails;
18
+ use crate::EmailAddress;
19
+ use crate::LOG_TARGET;
20
+ use crate::{
21
+ smtp::http_api::create_client,
22
+ util::{input_output::CheckEmailInput, ser_with_display::ser_with_display},
23
+ };
24
+ use reqwest::Error as ReqwestError;
25
+ use serde::Serialize;
26
+ use thiserror::Error;
27
+
28
+ const GLXU_PAGE: &str = "https://mail.google.com/mail/gxlu";
29
+
30
+ /// Possible errors when checking Gmail email addresses.
31
+ #[derive(Debug, Error, Serialize)]
32
+ pub enum GmailError {
33
+ /// Error when serializing or deserializing HTTP requests and responses.
34
+ #[serde(serialize_with = "ser_with_display")]
35
+ #[error("Error serializing or deserializing HTTP requests and responses: {0}")]
36
+ ReqwestError(ReqwestError),
37
+ }
38
+
39
+ impl From<ReqwestError> for GmailError {
40
+ fn from(error: ReqwestError) -> Self {
41
+ GmailError::ReqwestError(error)
42
+ }
43
+ }
44
+
45
+ #[allow(dead_code)]
46
+ /// Use HTTP request to verify if a Gmail email address exists.
47
+ /// See: <https://blog.0day.rocks/abusing-gmail-to-get-previously-unlisted-e-mail-addresses-41544b62b2>
48
+ ///
49
+ /// This method doesn't work anymore, as Google has patched the vulnerability,
50
+ /// but it's kept here for historical reasons.
51
+ #[deprecated]
52
+ pub async fn check_gmail_via_api(
53
+ to_email: &EmailAddress,
54
+ input: &CheckEmailInput,
55
+ ) -> Result<SmtpDetails, GmailError> {
56
+ let response = create_client(input, "gmail")?
57
+ .head(GLXU_PAGE)
58
+ .query(&[("email", to_email)])
59
+ .send()
60
+ .await?;
61
+
62
+ let email_exists = response.headers().contains_key("Set-Cookie");
63
+
64
+ tracing::debug!(
65
+ target: LOG_TARGET,
66
+ email = %to_email,
67
+ response = ?response,
68
+ "Gmail API response"
69
+ );
70
+
71
+ Ok(SmtpDetails {
72
+ can_connect_smtp: true,
73
+ is_deliverable: email_exists,
74
+ ..Default::default()
75
+ })
76
+ }
77
+
78
+ #[cfg(test)]
79
+ mod tests {
80
+ use super::*;
81
+ use crate::CheckEmailInputBuilder;
82
+ use std::str::FromStr;
83
+
84
+ #[tokio::test]
85
+ #[ignore] // ref: https://github.com/reacherhq/check-if-email-exists/issues/1431
86
+ async fn should_return_is_deliverable_true() {
87
+ let to_email = EmailAddress::from_str("someone@gmail.com").unwrap();
88
+ let input = CheckEmailInputBuilder::default()
89
+ .to_email("someone@gmail.com".to_owned())
90
+ .build()
91
+ .unwrap();
92
+
93
+ #[allow(deprecated)]
94
+ let smtp_details = check_gmail_via_api(&to_email, &input).await;
95
+
96
+ assert!(smtp_details.is_ok());
97
+ assert!(smtp_details.unwrap().is_deliverable);
98
+ }
99
+ }
@@ -0,0 +1,82 @@
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 crate::{util::ser_with_display::ser_with_display, WebdriverConfig};
18
+ use fantoccini::{
19
+ error::{CmdError, NewSessionError},
20
+ Client, ClientBuilder,
21
+ };
22
+ use serde::Serialize;
23
+ use serde_json::Map;
24
+ use thiserror::Error;
25
+
26
+ #[derive(Debug, Error, Serialize)]
27
+ pub enum HeadlessError {
28
+ #[serde(serialize_with = "ser_with_display")]
29
+ #[error("Fantoccini error: {0}")]
30
+ Fantoccini(#[from] std::io::Error),
31
+ #[serde(serialize_with = "ser_with_display")]
32
+ #[error("Cmd error: {0}")]
33
+ Cmd(#[from] CmdError),
34
+ #[serde(serialize_with = "ser_with_display")]
35
+ #[error("New session error: {0}")]
36
+ NewSession(#[from] NewSessionError),
37
+ }
38
+
39
+ pub async fn create_headless_client(
40
+ webdriver: &str,
41
+ webdriver_config: &WebdriverConfig,
42
+ ) -> Result<Client, HeadlessError> {
43
+ let mut opts = serde_json::json!({
44
+ "args": [
45
+ "--headless=new", "--disable-gpu", "--disable-dev-shm-usage",
46
+ // Running in a Docker container, I run into the following error:
47
+ // Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
48
+ // In searching around I found a few different workarounds:
49
+ // - Enable namespaces: https://github.com/jessfraz/dockerfiles/issues/65#issuecomment-266532289
50
+ // - Run it with a custom seccomp: https://github.com/jessfraz/dockerfiles/issues/65#issuecomment-217214671
51
+ // - Run with --no-sandbox: https://github.com/karma-runner/karma-chrome-launcher/issues/125#issuecomment-312668593
52
+ // For now I went with the --no-sandbox.
53
+ //
54
+ // TODO Look into security implications...
55
+ "--no-sandbox",
56
+ // From https://github.com/chromium-for-lambda/chromium-binaries/tree/b23e11c2f2859b177fd08fe50a0826c17652d846?tab=readme-ov-file#installation-via-a-lambda-layer
57
+ "--use-gl=angle", "--use-angle=swiftshader", "--single-process", "--no-zygote",
58
+ // Disable anything that might consume memory
59
+ "--window-size=800x600",
60
+ "--disable-extensions",
61
+ "--disable-software-rasterizer",
62
+ "--disable-dev-shm-usage",
63
+ "--disable-background-networking",
64
+ "--js-flags=\"--max-old-space-size=256\"",
65
+ ]
66
+ });
67
+
68
+ if let Some(binary) = &webdriver_config.binary {
69
+ opts["binary"] = serde_json::json!(binary);
70
+ }
71
+
72
+ let mut caps = Map::new();
73
+ caps.insert("goog:chromeOptions".to_string(), opts);
74
+
75
+ // Connect to WebDriver instance that is listening on `webdriver`
76
+ let c = ClientBuilder::rustls()?
77
+ .capabilities(caps)
78
+ .connect(webdriver)
79
+ .await?;
80
+
81
+ Ok(c)
82
+ }
@@ -0,0 +1,27 @@
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 crate::util::input_output::CheckEmailInput;
18
+ use reqwest::Error as ReqwestError;
19
+
20
+ /// Helper function to create a reqwest client, with optional proxy.
21
+ pub fn create_client(
22
+ _input: &CheckEmailInput,
23
+ _api_name: &str,
24
+ ) -> Result<reqwest::Client, ReqwestError> {
25
+ // TODO: Allow proxying for HTTP API requests.
26
+ Ok(reqwest::Client::new())
27
+ }
@@ -0,0 +1,234 @@
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
+ mod connect;
18
+ mod error;
19
+ mod gmail;
20
+ mod headless;
21
+ mod http_api;
22
+ mod outlook;
23
+ mod parser;
24
+ pub mod verif_method;
25
+ mod yahoo;
26
+
27
+ use crate::util::input_output::CheckEmailInput;
28
+ use crate::EmailAddress;
29
+ use connect::check_smtp_with_retry;
30
+ use hickory_proto::rr::Name;
31
+ use serde::{Deserialize, Serialize};
32
+ use std::default::Default;
33
+ use verif_method::{
34
+ EmailProvider, EverythingElseVerifMethod, GmailVerifMethod, HotmailB2BVerifMethod,
35
+ HotmailB2CVerifMethod, VerifMethodSmtp, VerifMethodSmtpConfig, YahooVerifMethod,
36
+ };
37
+
38
+ pub use crate::mx::{is_gmail, is_hotmail, is_hotmail_b2b, is_hotmail_b2c, is_yahoo};
39
+ pub use error::*;
40
+
41
+ #[derive(Debug, Deserialize, Serialize)]
42
+ pub struct SmtpDebugVerifMethodSmtp {
43
+ /// The host we connected to via SMTP.
44
+ pub host: String,
45
+ /// The proxy used for the SMTP connection.
46
+ pub verif_method: VerifMethodSmtpConfig,
47
+ }
48
+
49
+ #[derive(Debug, Default, Deserialize, Serialize)]
50
+ #[serde(tag = "type")]
51
+ pub enum SmtpDebugVerifMethod {
52
+ /// Email verification was done via SMTP.
53
+ Smtp(SmtpDebugVerifMethodSmtp),
54
+ /// Email verification was done via an HTTP API.
55
+ Api,
56
+ /// Email verification was done via a headless browser.
57
+ Headless,
58
+ /// Email verification was skipped.
59
+ #[default]
60
+ Skipped,
61
+ }
62
+
63
+ /// Details that we gathered from connecting to this email via SMTP
64
+ #[derive(Debug, Default, Deserialize, Serialize)]
65
+ pub struct SmtpDetails {
66
+ /// Are we able to connect to the SMTP server?
67
+ pub can_connect_smtp: bool,
68
+ /// Is this email account's inbox full?
69
+ pub has_full_inbox: bool,
70
+ /// Does this domain have a catch-all email address?
71
+ pub is_catch_all: bool,
72
+ /// Can we send an email to this address?
73
+ pub is_deliverable: bool,
74
+ /// Is the email blocked or disabled by the provider?
75
+ pub is_disabled: bool,
76
+ }
77
+
78
+ /// Debug information on how the SMTP verification went.
79
+ #[derive(Debug, Default, Deserialize, Serialize)]
80
+ pub struct SmtpDebug {
81
+ /// The verification method used for the email.
82
+ pub verif_method: SmtpDebugVerifMethod,
83
+ }
84
+
85
+ /// Get all email details we can from one single `EmailAddress`, without
86
+ /// retries.
87
+ pub async fn check_smtp(
88
+ to_email: &EmailAddress,
89
+ host: &Name,
90
+ domain: &str,
91
+ input: &CheckEmailInput,
92
+ ) -> (Result<SmtpDetails, SmtpError>, SmtpDebug) {
93
+ let host_str = host.to_string();
94
+ let to_email_str = to_email.to_string();
95
+ let email_provider = EmailProvider::from_mx_host(&host_str);
96
+
97
+ // Handle all non-SMTP verifications first, and return early. For the rest,
98
+ // we'll use SMTP, and return the config.
99
+ let smtp_verif_method_config = match &email_provider {
100
+ EmailProvider::HotmailB2C => match &input.verif_method.hotmailb2c {
101
+ HotmailB2CVerifMethod::Headless => {
102
+ return (
103
+ outlook::headless::check_password_recovery(
104
+ &to_email_str,
105
+ &input.webdriver_addr,
106
+ &input.webdriver_config,
107
+ )
108
+ .await
109
+ .map_err(Into::into),
110
+ SmtpDebug {
111
+ verif_method: SmtpDebugVerifMethod::Headless,
112
+ },
113
+ );
114
+ }
115
+ HotmailB2CVerifMethod::Smtp(c) => c,
116
+ },
117
+ EmailProvider::Yahoo => match &input.verif_method.yahoo {
118
+ YahooVerifMethod::Api => {
119
+ return (
120
+ yahoo::check_api(&to_email_str, input)
121
+ .await
122
+ .map_err(Into::into),
123
+ SmtpDebug {
124
+ verif_method: SmtpDebugVerifMethod::Api,
125
+ },
126
+ );
127
+ }
128
+ YahooVerifMethod::Headless => {
129
+ return (
130
+ yahoo::check_headless(
131
+ &to_email_str,
132
+ &input.webdriver_addr,
133
+ &input.webdriver_config,
134
+ )
135
+ .await
136
+ .map_err(Into::into),
137
+ SmtpDebug {
138
+ verif_method: SmtpDebugVerifMethod::Headless,
139
+ },
140
+ );
141
+ }
142
+ YahooVerifMethod::Smtp(c) => c,
143
+ },
144
+ EmailProvider::Gmail => match &input.verif_method.gmail {
145
+ GmailVerifMethod::Smtp(c) => c,
146
+ },
147
+ EmailProvider::HotmailB2B => match &input.verif_method.hotmailb2b {
148
+ HotmailB2BVerifMethod::Smtp(c) => c,
149
+ },
150
+ EmailProvider::Mimecast => match &input.verif_method.mimecast {
151
+ verif_method::MimecastVerifMethod::Smtp(c) => c,
152
+ },
153
+ EmailProvider::Proofpoint => match &input.verif_method.proofpoint {
154
+ verif_method::ProofpointVerifMethod::Smtp(c) => c,
155
+ },
156
+ EmailProvider::EverythingElse => match &input.verif_method.everything_else {
157
+ EverythingElseVerifMethod::Smtp(c) => c,
158
+ },
159
+ }
160
+ .clone();
161
+
162
+ // TODO: There's surely a way to not clone here.
163
+ let verif_method = VerifMethodSmtp::new(
164
+ smtp_verif_method_config.clone(),
165
+ input.verif_method.get_proxy(email_provider).cloned(),
166
+ );
167
+
168
+ (
169
+ check_smtp_with_retry(
170
+ to_email,
171
+ &host_str,
172
+ domain,
173
+ &verif_method,
174
+ verif_method.config.retries,
175
+ )
176
+ .await,
177
+ SmtpDebug {
178
+ verif_method: SmtpDebugVerifMethod::Smtp(SmtpDebugVerifMethodSmtp {
179
+ host: host_str,
180
+ verif_method: smtp_verif_method_config,
181
+ }),
182
+ },
183
+ )
184
+ }
185
+
186
+ #[cfg(test)]
187
+ mod tests {
188
+ use super::*;
189
+ use crate::smtp::verif_method::GmailVerifMethod;
190
+ use crate::smtp::verif_method::VerifMethod;
191
+ use crate::smtp::verif_method::VerifMethodSmtpConfig;
192
+ use crate::CheckEmailInputBuilder;
193
+ use crate::EmailAddress;
194
+ use hickory_proto::rr::Name;
195
+ use std::{str::FromStr, time::Duration};
196
+ use tokio::runtime::Runtime;
197
+
198
+ #[test]
199
+ fn should_timeout() {
200
+ let runtime = Runtime::new().unwrap();
201
+
202
+ let to_email = EmailAddress::from_str("foo@gmail.com").unwrap();
203
+ let host = Name::from_str("alt4.aspmx.l.google.com.").unwrap();
204
+ let input = CheckEmailInputBuilder::default()
205
+ .to_email("foo@gmail.com".into())
206
+ .verif_method(VerifMethod {
207
+ gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
208
+ smtp_timeout: Some(Duration::from_millis(1)),
209
+ retries: 1,
210
+ ..Default::default()
211
+ }),
212
+ ..Default::default()
213
+ })
214
+ .build()
215
+ .unwrap();
216
+
217
+ let (res, smtp_debug) = runtime.block_on(check_smtp(&to_email, &host, "gmail.com", &input));
218
+ match smtp_debug.verif_method {
219
+ SmtpDebugVerifMethod::Smtp(SmtpDebugVerifMethodSmtp { host, verif_method }) => {
220
+ assert_eq!(host, "alt4.aspmx.l.google.com.");
221
+ assert_eq!(verif_method.smtp_port, 25);
222
+ assert_eq!(verif_method.smtp_timeout, Some(Duration::from_millis(1)));
223
+ assert_eq!(verif_method.retries, 1);
224
+ assert_eq!(verif_method.proxy, None);
225
+ }
226
+ _ => panic!("Expected SmtpDebugVerifMethod::Smtp"),
227
+ }
228
+
229
+ match res {
230
+ Err(SmtpError::Timeout(_)) => (), // ErrorKind == Timeout
231
+ _ => panic!("check_smtp did not time out"),
232
+ }
233
+ }
234
+ }