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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +24 -0
- data/Cargo.lock +2940 -0
- data/Cargo.toml +22 -0
- data/LICENSE.txt +661 -0
- data/NOTICE.md +56 -0
- data/README.md +316 -0
- data/exe/valid_email_checker +148 -0
- data/ext/valid_email_checker/Cargo.toml +27 -0
- data/ext/valid_email_checker/extconf.rb +26 -0
- data/ext/valid_email_checker/src/lib.rs +371 -0
- data/ext/valid_email_checker/src/nogvl.rs +64 -0
- data/ext/valid_email_checker/vendor/UPSTREAM.json +9 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/Cargo.toml +52 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/LICENSE.AGPL +661 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/LICENSE.md +11 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/README.md +175 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/haveibeenpwned.rs +70 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/lib.rs +281 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/b2c.txt +96640 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/gravatar.rs +60 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/mod.rs +124 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/roles.txt +944 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/mx/mod.rs +165 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/rules.json +28 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/rules.rs +105 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/connect.rs +396 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/error.rs +144 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/gmail.rs +99 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/headless.rs +82 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/http_api.rs +27 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/mod.rs +234 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/headless.rs +181 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/microsoft365.rs +109 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/mod.rs +2 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/parser.rs +291 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/verif_method.rs +531 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/api.rs +174 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/headless.rs +188 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/mod.rs +62 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/syntax/mod.rs +199 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/syntax/normalize.rs +70 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/input_output.rs +353 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/mod.rs +20 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/sentry.rs +173 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/ser_with_display.rs +28 -0
- data/lib/valid_email_checker/configuration.rb +216 -0
- data/lib/valid_email_checker/errors.rb +32 -0
- data/lib/valid_email_checker/result.rb +241 -0
- data/lib/valid_email_checker/syntax.rb +72 -0
- data/lib/valid_email_checker/version.rb +5 -0
- data/lib/valid_email_checker.rb +217 -0
- 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
|
+
}
|