email_assessor 0.4.13 → 0.7.1
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 +5 -5
- data/email_assessor.gemspec +1 -1
- data/lib/email_assessor/address.rb +100 -14
- data/lib/email_assessor/version.rb +1 -1
- data/lib/email_assessor.rb +5 -5
- data/pull_mailchecker_emails.rb +5 -4
- data/spec/email_assessor_spec.rb +68 -16
- data/vendor/disposable_domains.txt +29105 -19
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 28e8db711b1571f9f105f0e341b8798bf557edcdd21bfa9af93b97a2fc6e7989
|
4
|
+
data.tar.gz: 697a161767368eed35a1cf12eaca2fc9c129b6e27f4ab48a4bd07362597a678c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac12ac0944a707a72b3d7f507b016c0d4dad6cd3cd924c1f229178e8dd806cff1e2bdf4eca887566c2271c15ea4a9210f02b2f6e24e26d768c85e47e73104fdb
|
7
|
+
data.tar.gz: fd148a7343b485d42f320c0e7fcc2265a636f2973faacf3e928d5315c39e8b909c970809c6ebd07b3d929484a2239b33be3c7b2bb38ef0aeb0bd3648e2288db4
|
data/email_assessor.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
|
24
24
|
spec.required_ruby_version = ">= 2.4.0"
|
25
25
|
|
26
|
-
spec.add_development_dependency "bundler", "~>
|
26
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
27
27
|
spec.add_development_dependency "rake"
|
28
28
|
spec.add_development_dependency "rspec", "~> 3.4"
|
29
29
|
spec.add_runtime_dependency "mail", "~> 2.5"
|
@@ -7,6 +7,82 @@ module EmailAssessor
|
|
7
7
|
class Address
|
8
8
|
attr_accessor :address
|
9
9
|
|
10
|
+
PROHIBITED_DOMAIN_PREFIXES = [
|
11
|
+
'.',
|
12
|
+
'-',
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
PROHIBITED_DOMAIN_CONTENT = [
|
16
|
+
'+',
|
17
|
+
'!',
|
18
|
+
'_',
|
19
|
+
'/',
|
20
|
+
' ',
|
21
|
+
'..',
|
22
|
+
'-.',
|
23
|
+
"'",
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
PROHIBITED_DOMAIN_SUFFIXES = [
|
27
|
+
# none
|
28
|
+
].freeze
|
29
|
+
|
30
|
+
PROHIBITED_LOCAL_PREFIXES = [
|
31
|
+
'.',
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
PROHIBITED_LOCAL_CONTENT = [
|
35
|
+
'..',
|
36
|
+
].freeze
|
37
|
+
|
38
|
+
PROHIBITED_LOCAL_SUFFIXES = [
|
39
|
+
'.',
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def prohibited_domain_regex
|
44
|
+
@prohibited_domain_content_regex ||= make_regex(
|
45
|
+
prefixes: PROHIBITED_DOMAIN_PREFIXES,
|
46
|
+
content: PROHIBITED_DOMAIN_CONTENT,
|
47
|
+
suffixes: PROHIBITED_DOMAIN_SUFFIXES
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def prohibited_local_regex
|
52
|
+
@prohibited_local_content_regex ||= make_regex(
|
53
|
+
prefixes: PROHIBITED_LOCAL_PREFIXES,
|
54
|
+
content: PROHIBITED_LOCAL_CONTENT,
|
55
|
+
suffixes: PROHIBITED_LOCAL_SUFFIXES
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def make_regex(prefixes: nil, content: nil, suffixes: nil)
|
62
|
+
parts = []
|
63
|
+
|
64
|
+
unless prefixes.nil?
|
65
|
+
prefixes.each do |prefix|
|
66
|
+
parts << "\\A#{Regexp.escape(prefix)}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
unless content.nil?
|
71
|
+
content.each do |prefix|
|
72
|
+
parts << Regexp.escape(prefix)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
unless suffixes.nil?
|
77
|
+
suffixes.each do |prefix|
|
78
|
+
parts << "#{Regexp.escape(prefix)}\\z"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
Regexp.new(parts.join("|"), Regexp::IGNORECASE)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
10
86
|
def initialize(address)
|
11
87
|
@parse_error = false
|
12
88
|
@raw_address = address
|
@@ -19,18 +95,19 @@ module EmailAssessor
|
|
19
95
|
end
|
20
96
|
|
21
97
|
def valid?
|
98
|
+
return @valid unless @valid.nil?
|
22
99
|
return false if @parse_error
|
23
100
|
|
24
|
-
|
25
|
-
domain
|
101
|
+
@valid =
|
102
|
+
if address.domain && address.address == @raw_address
|
103
|
+
domain = address.domain
|
26
104
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
105
|
+
domain.include?('.') &&
|
106
|
+
!domain.match?(self.class.prohibited_domain_regex) &&
|
107
|
+
!address.local.match?(self.class.prohibited_local_regex)
|
108
|
+
else
|
109
|
+
false
|
110
|
+
end
|
34
111
|
end
|
35
112
|
|
36
113
|
def disposable?
|
@@ -42,15 +119,24 @@ module EmailAssessor
|
|
42
119
|
end
|
43
120
|
|
44
121
|
def valid_mx?
|
45
|
-
|
122
|
+
valid? && mx_servers.any?
|
123
|
+
end
|
46
124
|
|
47
|
-
|
125
|
+
def mx_server_is_in?(domain_list_file)
|
126
|
+
mx_servers.any? do |mx_server|
|
127
|
+
return false unless mx_server.respond_to?(:exchange)
|
128
|
+
mx_server = mx_server.exchange.to_s
|
48
129
|
|
49
|
-
|
50
|
-
mx.concat dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
|
130
|
+
EmailAssessor.domain_in_file?(mx_server, domain_list_file)
|
51
131
|
end
|
132
|
+
end
|
52
133
|
|
53
|
-
|
134
|
+
def mx_servers
|
135
|
+
@mx_servers ||= Resolv::DNS.open do |dns|
|
136
|
+
mx_servers = dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
|
137
|
+
(mx_servers.any? && mx_servers) ||
|
138
|
+
dns.getresources(address.domain, Resolv::DNS::Resource::IN::A)
|
139
|
+
end
|
54
140
|
end
|
55
141
|
end
|
56
142
|
end
|
data/lib/email_assessor.rb
CHANGED
@@ -13,14 +13,14 @@ module EmailAssessor
|
|
13
13
|
domain_in_file?(domain, BLACKLISTED_DOMAINS_FILE_NAME)
|
14
14
|
end
|
15
15
|
|
16
|
-
protected
|
17
|
-
|
18
16
|
def self.domain_in_file?(domain, file_name)
|
19
17
|
file_name ||= ""
|
20
18
|
domain = domain.downcase
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
File.foreach(file_name, chomp: true).any? do |line|
|
21
|
+
# String#end_with? is used as a cheaper initial check but due to potential false positives
|
22
|
+
# (hotmail.com is valid but tmail.com is not) regex is also necessary.
|
23
|
+
domain.end_with?(line) && domain.match?(%r{\A(?:.*\.)?#{line}\z}i)
|
24
|
+
end
|
25
25
|
end
|
26
26
|
end
|
data/pull_mailchecker_emails.rb
CHANGED
@@ -5,16 +5,17 @@ require "yaml"
|
|
5
5
|
|
6
6
|
require "json"
|
7
7
|
require "net/http"
|
8
|
+
require "set"
|
8
9
|
|
9
10
|
whitelisted_domains = %w(poczta.onet.pl fastmail.fm hushmail.com naver.com qq.com nus.edu.sg)
|
10
11
|
|
11
12
|
existing_domains = File.readlines("vendor/disposable_domains.txt")
|
12
13
|
|
13
|
-
url = "https://raw.githubusercontent.com/FGRibreau/mailchecker/master/list.
|
14
|
+
url = "https://raw.githubusercontent.com/FGRibreau/mailchecker/master/list.txt"
|
14
15
|
resp = Net::HTTP.get_response(URI.parse(url))
|
15
16
|
|
16
|
-
remote_domains =
|
17
|
+
remote_domains = (resp.body.split("\n")) - whitelisted_domains
|
17
18
|
|
18
|
-
result_domains = (existing_domains + remote_domains).map { |domain| domain.strip.downcase }
|
19
|
+
result_domains = SortedSet.new((existing_domains + remote_domains).map! { |domain| domain.strip.downcase })
|
19
20
|
|
20
|
-
File.open("vendor/disposable_domains.txt", "w") { |f| f.write result_domains.join("\n") }
|
21
|
+
File.open("vendor/disposable_domains.txt", "w") { |f| f.write result_domains.to_a.join("\n") }
|
data/spec/email_assessor_spec.rb
CHANGED
@@ -32,55 +32,107 @@ describe EmailAssessor do
|
|
32
32
|
describe "basic validation" do
|
33
33
|
subject(:user) { plain_user }
|
34
34
|
|
35
|
-
it "
|
35
|
+
it "is valid when email is empty" do
|
36
36
|
is_expected.to be_valid
|
37
37
|
end
|
38
38
|
|
39
|
-
it "
|
39
|
+
it "is invalid if the address starts with a dot" do
|
40
|
+
user = TestUser.new(email: ".foo@bar.com")
|
41
|
+
expect(user.valid?).to be_falsey
|
42
|
+
end
|
43
|
+
|
44
|
+
it "is invalid if the address contains consecutive dots" do
|
45
|
+
user = TestUser.new(email: "foo..bar@gmail.com")
|
46
|
+
expect(user.valid?).to be_falsey
|
47
|
+
end
|
48
|
+
|
49
|
+
it "is invalid if the address ends with a dot" do
|
50
|
+
user = TestUser.new(email: "foo.@bar.com")
|
51
|
+
expect(user.valid?).to be_falsey
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is invalid when domain is missing" do
|
40
55
|
user.email = "foo@.com"
|
41
56
|
is_expected.to be_invalid
|
42
57
|
end
|
43
58
|
|
44
|
-
it "
|
59
|
+
it "is invalid when domain starts with a dot" do
|
60
|
+
user.email = "foo@.example.com"
|
61
|
+
is_expected.to be_invalid
|
62
|
+
end
|
63
|
+
|
64
|
+
it "is invalid when email is malformed" do
|
45
65
|
user.email = "foo@bar"
|
46
66
|
is_expected.to be_invalid
|
47
67
|
end
|
48
68
|
|
49
|
-
it "
|
69
|
+
it "is invalid when email contains a trailing symbol" do
|
50
70
|
user.email = "foo@bar.com/"
|
51
71
|
is_expected.to be_invalid
|
52
72
|
end
|
53
73
|
|
54
|
-
it "
|
74
|
+
it "is invalid if Mail::AddressListsParser raises exception" do
|
55
75
|
allow(Mail::Address).to receive(:new).and_raise(Mail::Field::ParseError.new(nil, nil, nil))
|
56
76
|
user.email = "foo@gmail.com"
|
57
77
|
is_expected.to be_invalid
|
58
78
|
end
|
59
79
|
|
60
|
-
it "
|
80
|
+
it "is invalid if the domain constains consecutives dots" do
|
61
81
|
user.email = "foo@bar..com"
|
62
82
|
is_expected.to be_invalid
|
63
83
|
end
|
84
|
+
|
85
|
+
it "is invalid if the domain contains emoticons" do
|
86
|
+
user.email = "foo🙈@gmail.com"
|
87
|
+
is_expected.to be_invalid
|
88
|
+
end
|
89
|
+
|
90
|
+
it "is invalid if the domain contains spaces" do
|
91
|
+
user.email = "user@gmail .com"
|
92
|
+
is_expected.to be_invalid
|
93
|
+
end
|
94
|
+
|
95
|
+
it "is invalid if the domain contains '.@'" do
|
96
|
+
user.email = "foo.@gmail.com"
|
97
|
+
expect(user.valid?).to be_falsy
|
98
|
+
end
|
99
|
+
|
100
|
+
it "is invalid if the domain begins with a hyphen" do
|
101
|
+
user.email = "foo@-gmail.com"
|
102
|
+
expect(user.valid?).to be_falsy
|
103
|
+
end
|
104
|
+
|
105
|
+
it "is invalid if the domain name ends with a hyphen" do
|
106
|
+
user.email = "foo@gmail-.com"
|
107
|
+
expect(user.valid?).to be_falsy
|
108
|
+
end
|
109
|
+
|
110
|
+
%w[+ _ ! / \ '].each do |invalid_character|
|
111
|
+
it "is invalid if domain contains a \"#{invalid_character}\" character" do
|
112
|
+
user.email = "foo@google#{invalid_character}yahoo.com"
|
113
|
+
is_expected.to be_invalid
|
114
|
+
end
|
115
|
+
end
|
64
116
|
end
|
65
117
|
|
66
118
|
describe "disposable domains" do
|
67
119
|
subject(:user) { disposable_user }
|
68
120
|
|
69
|
-
it "
|
121
|
+
it "is valid when email is not in the list of disposable domains" do
|
70
122
|
is_expected.to be_valid
|
71
123
|
end
|
72
124
|
|
73
|
-
it "
|
125
|
+
it "is invalid when email is in the list of disposable domains" do
|
74
126
|
user.email = "foo@#{disposable_domain}"
|
75
127
|
is_expected.to be_invalid
|
76
128
|
end
|
77
129
|
|
78
|
-
it "
|
130
|
+
it "is invalid when email is in the list of disposable domains regardless of case" do
|
79
131
|
user.email = "foo@#{disposable_domain.upcase}"
|
80
132
|
is_expected.to be_invalid
|
81
133
|
end
|
82
134
|
|
83
|
-
it "
|
135
|
+
it "is invalid when email is in the list of disposable domains regardless of subdomain" do
|
84
136
|
user.email = "foo@abc123.#{disposable_domain}"
|
85
137
|
is_expected.to be_invalid
|
86
138
|
end
|
@@ -89,21 +141,21 @@ describe EmailAssessor do
|
|
89
141
|
describe "blacklisted domains" do
|
90
142
|
subject(:user) { blacklist_user }
|
91
143
|
|
92
|
-
it "
|
144
|
+
it "is valid when email domain is not in the blacklist" do
|
93
145
|
is_expected.to be_valid
|
94
146
|
end
|
95
147
|
|
96
|
-
it "
|
148
|
+
it "is invalid when email domain is in the blacklist" do
|
97
149
|
user.email = "foo@#{blacklisted_domain}"
|
98
150
|
is_expected.to be_invalid
|
99
151
|
end
|
100
152
|
|
101
|
-
it "
|
153
|
+
it "is invalid when email is in the blacklist regardless of case" do
|
102
154
|
user.email = "foo@#{blacklisted_domain.upcase}"
|
103
155
|
is_expected.to be_invalid
|
104
156
|
end
|
105
157
|
|
106
|
-
it "
|
158
|
+
it "is invalid when email domain is in the blacklist regardless of subdomain" do
|
107
159
|
user.email = "foo@abc123.#{blacklisted_domain}"
|
108
160
|
is_expected.to be_invalid
|
109
161
|
end
|
@@ -112,11 +164,11 @@ describe EmailAssessor do
|
|
112
164
|
describe "mx lookup" do
|
113
165
|
subject(:user) { mx_user }
|
114
166
|
|
115
|
-
it "
|
167
|
+
it "is valid if mx records are found" do
|
116
168
|
is_expected.to be_valid
|
117
169
|
end
|
118
170
|
|
119
|
-
it "
|
171
|
+
it "is invalid if no mx records are found" do
|
120
172
|
user.email = "foo@subdomain.gmail.com"
|
121
173
|
is_expected.to be_invalid
|
122
174
|
end
|