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,60 @@
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::LOG_TARGET;
18
+ use md5::Digest;
19
+
20
+ const API_BASE_URL: &str = "https://www.gravatar.com/avatar/";
21
+
22
+ pub async fn check_gravatar(to_email: &str) -> Option<String> {
23
+ let client = reqwest::Client::new();
24
+
25
+ let mail_hash: Digest = md5::compute(to_email);
26
+
27
+ let url = format!("{API_BASE_URL}{mail_hash:x}");
28
+
29
+ tracing::debug!(
30
+ target: LOG_TARGET,
31
+ email=to_email,
32
+ url=url,
33
+ "Request Gravatar API"
34
+ );
35
+
36
+ let response = client
37
+ .get(&url)
38
+ // This option is necessary to return a NotFound exception instead of the default gravatar
39
+ // image if none for the given email is found.
40
+ .query(&[("d", "404")])
41
+ .send()
42
+ .await;
43
+
44
+ tracing::debug!(
45
+ target: LOG_TARGET,
46
+ "[email={}] Gravatar response: {:?}",
47
+ to_email,
48
+ response
49
+ );
50
+
51
+ let response = match response {
52
+ Ok(response) => response,
53
+ Err(_) => return None,
54
+ };
55
+
56
+ match response.status() {
57
+ reqwest::StatusCode::OK => Some(url),
58
+ _ => None,
59
+ }
60
+ }
@@ -0,0 +1,124 @@
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 gravatar;
18
+ use crate::haveibeenpwned::check_haveibeenpwned;
19
+ use crate::syntax::SyntaxDetails;
20
+ use gravatar::check_gravatar;
21
+ use once_cell::sync::Lazy;
22
+ use serde::{Deserialize, Serialize};
23
+ use std::{collections::HashSet, default::Default};
24
+ use thiserror::Error;
25
+
26
+ const ROLE_ACCOUNTS: &str = include_str!("./roles.txt");
27
+ const FREE_EMAIL_PROVIDERS: &str = include_str!("./b2c.txt");
28
+
29
+ // Lazy static initialization of domain sets
30
+ static ROLE_ACCOUNTS_SET: Lazy<HashSet<String>> = Lazy::new(|| load_str_as_hashset(ROLE_ACCOUNTS));
31
+ static FREE_EMAIL_PROVIDERS_SET: Lazy<HashSet<String>> =
32
+ Lazy::new(|| load_str_as_hashset(FREE_EMAIL_PROVIDERS));
33
+
34
+ // Function to load a file with `\n`-separated lines into a HashSet.
35
+ fn load_str_as_hashset(file_content: &str) -> HashSet<String> {
36
+ file_content
37
+ .lines()
38
+ .map(|line| line.trim().to_string())
39
+ .collect()
40
+ }
41
+
42
+ /// Miscellaneous details about the email address.
43
+ #[derive(Debug, Default, Deserialize, Serialize)]
44
+ pub struct MiscDetails {
45
+ /// Is this a DEA (disposable email account)?
46
+ pub is_disposable: bool,
47
+ /// Is this email a role-based account?
48
+ pub is_role_account: bool,
49
+ /// Is this email a B2C email address?
50
+ pub is_b2c: bool,
51
+ /// If set, the gravatar URL for this email address.
52
+ pub gravatar_url: Option<String>,
53
+ /// Is this email address listed in the haveibeenpwned database for
54
+ /// previous breaches?
55
+ pub haveibeenpwned: Option<bool>,
56
+ }
57
+
58
+ /// Error occurred connecting to this email server via SMTP. Right now this
59
+ /// enum has no variant, as `check_misc` cannot fail. But putting a placeholder
60
+ /// right now to avoid future breaking changes.
61
+ #[derive(Debug, Error, Serialize)]
62
+ #[serde(tag = "type", content = "message")]
63
+ pub enum MiscError {}
64
+
65
+ /// Fetch misc details about the email address, such as whether it's disposable.
66
+ pub async fn check_misc(
67
+ syntax: &SyntaxDetails,
68
+ cfg_check_gravatar: bool,
69
+ haveibeenpwned_api_key: Option<String>,
70
+ ) -> MiscDetails {
71
+ let address = syntax
72
+ .address
73
+ .as_ref()
74
+ .expect("We already checked that the syntax was valid. qed.")
75
+ .to_string();
76
+
77
+ let mut gravatar_url: Option<String> = None;
78
+
79
+ if cfg_check_gravatar {
80
+ gravatar_url = check_gravatar(address.as_ref()).await;
81
+ }
82
+
83
+ let mut haveibeenpwned: Option<bool> = None;
84
+
85
+ if haveibeenpwned_api_key.is_some() {
86
+ haveibeenpwned = check_haveibeenpwned(address.as_ref(), haveibeenpwned_api_key).await;
87
+ }
88
+
89
+ MiscDetails {
90
+ // mailchecker::is_valid checks also if the syntax is valid. But if
91
+ // we're here, it means we're sure the syntax is valid, so is_valid
92
+ // actually will only check if it's disposable.
93
+ is_disposable: !mailchecker::is_valid(address.as_ref()),
94
+ is_role_account: ROLE_ACCOUNTS_SET.contains(&syntax.username.to_lowercase()),
95
+ is_b2c: FREE_EMAIL_PROVIDERS_SET.contains(&syntax.domain.to_lowercase()),
96
+ gravatar_url,
97
+ haveibeenpwned,
98
+ }
99
+ }
100
+ #[cfg(test)]
101
+ mod tests {
102
+ use std::str::FromStr;
103
+
104
+ use super::*;
105
+ use crate::{syntax::SyntaxDetails, EmailAddress};
106
+
107
+ #[tokio::test]
108
+ async fn test_check_misc() {
109
+ let syntax = SyntaxDetails {
110
+ address: Some(EmailAddress::from_str("test@gmail.com").unwrap()),
111
+ is_valid_syntax: true,
112
+ username: "test".to_string(),
113
+ domain: "gmail.com".to_string(),
114
+ normalized_email: None,
115
+ suggestion: None,
116
+ };
117
+
118
+ let misc_details = check_misc(&syntax, true, None).await;
119
+
120
+ assert!(!misc_details.is_disposable); // gmail.com is not in mailchecker
121
+ assert!(misc_details.is_role_account); // test is in roles.txt
122
+ assert!(misc_details.is_b2c); // gmail.com is in b2c.txt
123
+ }
124
+ }