valid_email2 7.0.9 → 7.0.11
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 +4 -4
- data/.github/workflows/release.yaml +1 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +17 -0
- data/.rubocop_todo.yml +263 -0
- data/CHANGELOG.md +14 -0
- data/config/disposable_email_domains.txt +4933 -5703
- data/lib/valid_email2/address.rb +20 -23
- data/lib/valid_email2/version.rb +1 -1
- data/pull_mailchecker_emails.rb +13 -3
- data/spec/address_spec.rb +89 -3
- data/spec/benchmark_spec.rb +13 -2
- data/valid_email2.gemspec +4 -4
- metadata +19 -6
data/lib/valid_email2/address.rb
CHANGED
@@ -82,19 +82,15 @@ module ValidEmail2
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def disposable_domain?
|
85
|
-
domain_is_in?(ValidEmail2.disposable_emails)
|
86
|
-
end
|
87
|
-
|
88
|
-
def disposable_mx_server?
|
89
|
-
valid? && mx_server_is_in?(ValidEmail2.disposable_emails)
|
85
|
+
domain_is_in?(address.domain, ValidEmail2.disposable_emails)
|
90
86
|
end
|
91
87
|
|
92
88
|
def allow_listed?
|
93
|
-
domain_is_in?(ValidEmail2.allow_list)
|
89
|
+
domain_is_in?(address.domain, ValidEmail2.allow_list)
|
94
90
|
end
|
95
91
|
|
96
92
|
def deny_listed?
|
97
|
-
valid? && domain_is_in?(ValidEmail2.deny_list)
|
93
|
+
valid? && domain_is_in?(address.domain, ValidEmail2.deny_list)
|
98
94
|
end
|
99
95
|
|
100
96
|
def valid_mx?
|
@@ -113,26 +109,27 @@ module ValidEmail2
|
|
113
109
|
|
114
110
|
private
|
115
111
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
i = address_domain.index('.')
|
121
|
-
return false unless i
|
122
|
-
|
123
|
-
domain_list.include?(address_domain[(i + 1)..-1])
|
112
|
+
def disposable_mx_server?
|
113
|
+
address_domains = @dns.mx_servers(address.domain).map(&:exchange).map(&:to_s)
|
114
|
+
domain_is_in?(address_domains, ValidEmail2.disposable_emails)
|
124
115
|
end
|
125
116
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
117
|
+
def domain_is_in?(address_domains, domain_list)
|
118
|
+
Array(address_domains).any? do |address_domain|
|
119
|
+
address_domain = address_domain.downcase
|
120
|
+
return true if domain_list.include?(address_domain)
|
129
121
|
|
130
|
-
|
122
|
+
tokens = address_domain.split('.')
|
123
|
+
return false if tokens.length < 3
|
131
124
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
125
|
+
# check only 6 elements deep
|
126
|
+
2.upto(6).each do |depth|
|
127
|
+
limited_sub_domain_part = tokens.reverse.first(depth).reverse.join('.')
|
128
|
+
return true if domain_list.include?(limited_sub_domain_part)
|
129
|
+
end
|
130
|
+
|
131
|
+
false
|
132
|
+
end
|
136
133
|
end
|
137
134
|
|
138
135
|
def address_contain_multibyte_characters?
|
data/lib/valid_email2/version.rb
CHANGED
data/pull_mailchecker_emails.rb
CHANGED
@@ -29,6 +29,16 @@ remote_emails = [
|
|
29
29
|
resp.body.split("\n").flatten
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
deny_listed_tlds = %w[me ml id]
|
33
|
+
|
34
|
+
result_emails = (existing_emails + remote_emails).map(&:strip) - allow_listed_emails
|
35
|
+
result_emails = result_emails.map do |line|
|
36
|
+
ts = line.chomp.split(".")
|
37
|
+
if ts.last.size == 2 && !deny_listed_tlds.include?(ts.last)
|
38
|
+
ts.last(3).join(".")
|
39
|
+
else
|
40
|
+
ts.last(2).join(".")
|
41
|
+
end
|
42
|
+
end.uniq.sort
|
43
|
+
|
44
|
+
File.open("config/disposable_email_domains.txt", "w") { |f| f.puts(result_emails) }
|
data/spec/address_spec.rb
CHANGED
@@ -55,7 +55,52 @@ describe ValidEmail2::Address do
|
|
55
55
|
|
56
56
|
it "is valid if it contains special scandinavian characters" do
|
57
57
|
address = described_class.new("jørgen@email.dk")
|
58
|
-
expect(address.valid?).to
|
58
|
+
expect(address.valid?).to be_truthy
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#disposable_domain?" do
|
64
|
+
context "when the disposable domain does not have subdomains" do
|
65
|
+
let(:disposable_domain) { ValidEmail2.disposable_emails.select { |domain| domain.count(".") == 1 }.sample }
|
66
|
+
|
67
|
+
it "is true if the domain is in the disposable_emails list" do
|
68
|
+
address = described_class.new("foo@#{disposable_domain}")
|
69
|
+
expect(address.disposable_domain?).to be_truthy
|
70
|
+
end
|
71
|
+
|
72
|
+
it "is true if the domain is a subdomain of a disposable domain" do
|
73
|
+
address = described_class.new("foo@sub.#{disposable_domain}")
|
74
|
+
expect(address.disposable_domain?).to be_truthy
|
75
|
+
end
|
76
|
+
|
77
|
+
it "is true if the domain is a deeply nested subdomain of a disposable domain" do
|
78
|
+
address = described_class.new("foo@sub3.sub2.sub1.#{disposable_domain}")
|
79
|
+
expect(address.disposable_domain?).to be_truthy
|
80
|
+
end
|
81
|
+
|
82
|
+
it "is false if the domain is not in the disposable_emails list" do
|
83
|
+
address = described_class.new("foo@example.com")
|
84
|
+
expect(address.disposable_domain?).to eq false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when the disposable domain has subdomains" do
|
89
|
+
let(:disposable_domain) { ValidEmail2.disposable_emails.select { |domain| domain.count(".") > 1 }.sample }
|
90
|
+
|
91
|
+
it "is true if the domain is in the disposable_emails list" do
|
92
|
+
address = described_class.new("foo@#{disposable_domain}")
|
93
|
+
expect(address.disposable_domain?).to be_truthy
|
94
|
+
end
|
95
|
+
|
96
|
+
it "is true if the domain is a subdomain of a disposable domain" do
|
97
|
+
address = described_class.new("foo@sub.#{disposable_domain}")
|
98
|
+
expect(address.disposable_domain?).to be_truthy
|
99
|
+
end
|
100
|
+
|
101
|
+
it "is true if the domain is a deeply nested subdomain of a disposable domain" do
|
102
|
+
address = described_class.new("foo@sub3.sub2.sub1.#{disposable_domain}")
|
103
|
+
expect(address.disposable_domain?).to be_truthy
|
59
104
|
end
|
60
105
|
end
|
61
106
|
end
|
@@ -66,7 +111,7 @@ describe ValidEmail2::Address do
|
|
66
111
|
let(:email_instance) { described_class.new(email_address, dns_instance) }
|
67
112
|
let(:ttl) { 1_000 }
|
68
113
|
let(:mock_resolv_dns) { instance_double(Resolv::DNS) }
|
69
|
-
let(:mock_mx_records) { [double("MX", exchange: "mx.ymail.com", preference: 10, ttl:
|
114
|
+
let(:mock_mx_records) { [double("MX", exchange: "mx.ymail.com", preference: 10, ttl:)] }
|
70
115
|
|
71
116
|
before do
|
72
117
|
allow(email_instance).to receive(:null_mx?).and_return(false)
|
@@ -74,6 +119,47 @@ describe ValidEmail2::Address do
|
|
74
119
|
allow(mock_resolv_dns).to receive(:timeouts=)
|
75
120
|
end
|
76
121
|
|
122
|
+
describe "#disposable_mx_server?" do
|
123
|
+
let(:disposable_email_address) { "example@10minutemail.com" }
|
124
|
+
let(:disposable_mx_server) { ValidEmail2.disposable_emails.select { |domain| domain.count(".") == 1 }.sample }
|
125
|
+
let(:disposable_email_instance) { described_class.new(disposable_email_address, dns_instance) }
|
126
|
+
let(:mock_disposable_mx_records) { [double("MX", exchange: "mx.#{disposable_mx_server}", preference: 10, ttl:)] }
|
127
|
+
|
128
|
+
before do
|
129
|
+
allow(mock_resolv_dns).to receive(:getresources)
|
130
|
+
.with(disposable_email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
131
|
+
.and_return(mock_disposable_mx_records)
|
132
|
+
|
133
|
+
allow(mock_resolv_dns).to receive(:getresources)
|
134
|
+
.with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
135
|
+
.and_return(mock_mx_records)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "is false if the MX server is not in the disposable_emails list" do
|
139
|
+
expect(email_instance.send(:disposable_mx_server?)).not_to be_truthy
|
140
|
+
end
|
141
|
+
|
142
|
+
it "is true if the MX server is in the disposable_emails list" do
|
143
|
+
expect(disposable_email_instance.send(:disposable_mx_server?)).to be_truthy
|
144
|
+
end
|
145
|
+
|
146
|
+
it "is false and then true when the MX record changes from non-disposable to disposable" do
|
147
|
+
allow(mock_resolv_dns).to receive(:getresources)
|
148
|
+
.with(disposable_email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
149
|
+
.and_return(mock_mx_records) # non-disposable MX records
|
150
|
+
|
151
|
+
expect(disposable_email_instance.send(:disposable_mx_server?)).not_to be_truthy
|
152
|
+
|
153
|
+
ValidEmail2::Dns.clear_cache
|
154
|
+
|
155
|
+
allow(mock_resolv_dns).to receive(:getresources)
|
156
|
+
.with(disposable_email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
157
|
+
.and_return(mock_disposable_mx_records) # disposable MX records
|
158
|
+
|
159
|
+
expect(disposable_email_instance.send(:disposable_mx_server?)).to be_truthy
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
77
163
|
describe "#valid_strict_mx?" do
|
78
164
|
let(:cached_at) { Time.now }
|
79
165
|
let(:mock_cache_data) { { [email_instance.address.domain, Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_mx_records, cached_at, ttl) } }
|
@@ -206,7 +292,7 @@ describe ValidEmail2::Address do
|
|
206
292
|
describe "#valid_mx?" do
|
207
293
|
let(:cached_at) { Time.now }
|
208
294
|
let(:mock_cache_data) { { [email_instance.address.domain, Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_a_records, cached_at, ttl) } }
|
209
|
-
let(:mock_a_records) { [double("A", address: "192.168.1.1", ttl:
|
295
|
+
let(:mock_a_records) { [double("A", address: "192.168.1.1", ttl:)] }
|
210
296
|
|
211
297
|
before do
|
212
298
|
allow(email_instance).to receive(:mx_servers).and_return(mock_mx_records)
|
data/spec/benchmark_spec.rb
CHANGED
@@ -5,7 +5,7 @@ require "spec_helper"
|
|
5
5
|
describe "Performance testing" do
|
6
6
|
let(:disposable_domain) { ValidEmail2.disposable_emails.first }
|
7
7
|
|
8
|
-
it "has acceptable lookup performance" do
|
8
|
+
it "disposable_domain? has acceptable lookup performance" do
|
9
9
|
address = ValidEmail2::Address.new("test@example.com")
|
10
10
|
|
11
11
|
# preload list and check size
|
@@ -13,6 +13,17 @@ describe "Performance testing" do
|
|
13
13
|
expect(ValidEmail2.disposable_emails.count).to be > 30000
|
14
14
|
|
15
15
|
# check lookup timing
|
16
|
-
expect { address.disposable_domain? }.to perform_under(0.0001).sample(10).times
|
16
|
+
expect { address.disposable_domain? }.to perform_under(0.0001).sec.sample(10).times
|
17
|
+
end
|
18
|
+
|
19
|
+
it "disposable_mx_server? has acceptable lookup performance" do
|
20
|
+
address = ValidEmail2::Address.new("test@gmail.com")
|
21
|
+
|
22
|
+
# preload list and check size
|
23
|
+
expect(ValidEmail2.disposable_emails).to be_a(Set)
|
24
|
+
expect(ValidEmail2.disposable_emails.count).to be > 30000
|
25
|
+
|
26
|
+
# check lookup timing
|
27
|
+
expect { address.send(:disposable_mx_server?) }.to perform_under(0.00015).sec.sample(10).times
|
17
28
|
end
|
18
29
|
end
|
data/valid_email2.gemspec
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require "valid_email2/version"
|
5
4
|
|
@@ -8,8 +7,8 @@ Gem::Specification.new do |spec|
|
|
8
7
|
spec.version = ValidEmail2::VERSION
|
9
8
|
spec.authors = ["Micke Lisinge"]
|
10
9
|
spec.email = ["hi@micke.me"]
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
10
|
+
spec.description = 'ActiveModel validation for email. Including MX lookup and disposable email deny list'
|
11
|
+
spec.summary = 'ActiveModel validation for email. Including MX lookup and disposable email deny list'
|
13
12
|
spec.homepage = "https://github.com/micke/valid_email2"
|
14
13
|
spec.license = "MIT"
|
15
14
|
|
@@ -27,6 +26,7 @@ Gem::Specification.new do |spec|
|
|
27
26
|
spec.add_development_dependency "rspec-benchmark", "~> 0.6"
|
28
27
|
spec.add_development_dependency "net-smtp"
|
29
28
|
spec.add_development_dependency "debug"
|
29
|
+
spec.add_development_dependency "rubocop"
|
30
30
|
spec.add_runtime_dependency "mail", "~> 2.5"
|
31
31
|
spec.add_runtime_dependency "activemodel", ">= 6.0"
|
32
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: valid_email2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.0.
|
4
|
+
version: 7.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Micke Lisinge
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date: 2025-02-
|
10
|
+
date: 2025-02-28 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: bundler
|
@@ -108,6 +107,20 @@ dependencies:
|
|
108
107
|
- - ">="
|
109
108
|
- !ruby/object:Gem::Version
|
110
109
|
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rubocop
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
111
124
|
- !ruby/object:Gem::Dependency
|
112
125
|
name: mail
|
113
126
|
requirement: !ruby/object:Gem::Requirement
|
@@ -148,6 +161,8 @@ files:
|
|
148
161
|
- ".github/workflows/release.yaml"
|
149
162
|
- ".gitignore"
|
150
163
|
- ".release-please-manifest.json"
|
164
|
+
- ".rubocop.yml"
|
165
|
+
- ".rubocop_todo.yml"
|
151
166
|
- ".ruby-version"
|
152
167
|
- CHANGELOG.md
|
153
168
|
- Gemfile
|
@@ -176,7 +191,6 @@ homepage: https://github.com/micke/valid_email2
|
|
176
191
|
licenses:
|
177
192
|
- MIT
|
178
193
|
metadata: {}
|
179
|
-
post_install_message:
|
180
194
|
rdoc_options: []
|
181
195
|
require_paths:
|
182
196
|
- lib
|
@@ -191,8 +205,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
191
205
|
- !ruby/object:Gem::Version
|
192
206
|
version: '0'
|
193
207
|
requirements: []
|
194
|
-
rubygems_version: 3.5
|
195
|
-
signing_key:
|
208
|
+
rubygems_version: 3.6.5
|
196
209
|
specification_version: 4
|
197
210
|
summary: ActiveModel validation for email. Including MX lookup and disposable email
|
198
211
|
deny list
|