valid_email2 7.0.0 → 7.0.9
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/ci.yaml +5 -2
- data/.github/workflows/release.yaml +1 -0
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +64 -0
- data/README.md +11 -0
- data/config/disposable_email_domains.txt +228 -2
- data/gemfiles/activemodel8.gemfile +5 -0
- data/lib/valid_email2/address.rb +7 -41
- data/lib/valid_email2/dns.rb +71 -0
- data/lib/valid_email2/email_validator.rb +3 -1
- data/lib/valid_email2/version.rb +1 -1
- data/spec/address_spec.rb +39 -39
- data/spec/dns_spec.rb +21 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/valid_email2_spec.rb +3 -0
- data/valid_email2.gemspec +2 -1
- metadata +24 -7
- data/lib/valid_email2/dns_records_cache.rb +0 -37
data/lib/valid_email2/address.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal:true
|
2
2
|
|
3
3
|
require "valid_email2"
|
4
|
-
require "resolv"
|
5
4
|
require "mail"
|
6
|
-
require "valid_email2/
|
5
|
+
require "valid_email2/dns"
|
7
6
|
|
8
7
|
module ValidEmail2
|
9
8
|
class Address
|
@@ -29,11 +28,10 @@ module ValidEmail2
|
|
29
28
|
@permitted_multibyte_characters_regex = val
|
30
29
|
end
|
31
30
|
|
32
|
-
def initialize(address,
|
31
|
+
def initialize(address, dns = Dns.new)
|
33
32
|
@parse_error = false
|
34
33
|
@raw_address = address
|
35
|
-
@
|
36
|
-
@dns_nameserver = dns_nameserver
|
34
|
+
@dns = dns
|
37
35
|
|
38
36
|
begin
|
39
37
|
@address = Mail::Address.new(address)
|
@@ -103,14 +101,14 @@ module ValidEmail2
|
|
103
101
|
return false unless valid?
|
104
102
|
return false if null_mx?
|
105
103
|
|
106
|
-
|
104
|
+
@dns.mx_servers(address.domain).any? || @dns.a_servers(address.domain).any?
|
107
105
|
end
|
108
106
|
|
109
107
|
def valid_strict_mx?
|
110
108
|
return false unless valid?
|
111
109
|
return false if null_mx?
|
112
110
|
|
113
|
-
mx_servers.any?
|
111
|
+
@dns.mx_servers(address.domain).any?
|
114
112
|
end
|
115
113
|
|
116
114
|
private
|
@@ -126,7 +124,7 @@ module ValidEmail2
|
|
126
124
|
end
|
127
125
|
|
128
126
|
def mx_server_is_in?(domain_list)
|
129
|
-
mx_servers.any? { |mx_server|
|
127
|
+
@dns.mx_servers(address.domain).any? { |mx_server|
|
130
128
|
return false unless mx_server.respond_to?(:exchange)
|
131
129
|
|
132
130
|
mx_server = mx_server.exchange.to_s
|
@@ -145,41 +143,9 @@ module ValidEmail2
|
|
145
143
|
@raw_address.each_char.any? { |char| char.bytesize > 1 && char !~ self.class.permitted_multibyte_characters_regex }
|
146
144
|
end
|
147
145
|
|
148
|
-
def resolv_config
|
149
|
-
@resolv_config ||= begin
|
150
|
-
config = Resolv::DNS::Config.default_config_hash
|
151
|
-
config[:nameserver] = @dns_nameserver if @dns_nameserver
|
152
|
-
config
|
153
|
-
end
|
154
|
-
|
155
|
-
@resolv_config
|
156
|
-
end
|
157
|
-
|
158
|
-
def mx_servers
|
159
|
-
@mx_servers_cache ||= ValidEmail2::DnsRecordsCache.new
|
160
|
-
|
161
|
-
@mx_servers_cache.fetch(address.domain.downcase) do
|
162
|
-
Resolv::DNS.open(resolv_config) do |dns|
|
163
|
-
dns.timeouts = @dns_timeout
|
164
|
-
dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
146
|
def null_mx?
|
147
|
+
mx_servers = @dns.mx_servers(address.domain)
|
170
148
|
mx_servers.length == 1 && mx_servers.first.preference == 0 && mx_servers.first.exchange.length == 0
|
171
149
|
end
|
172
|
-
|
173
|
-
def mx_or_a_servers
|
174
|
-
@mx_or_a_servers_cache ||= ValidEmail2::DnsRecordsCache.new
|
175
|
-
|
176
|
-
@mx_or_a_servers_cache.fetch(address.domain.downcase) do
|
177
|
-
Resolv::DNS.open(resolv_config) do |dns|
|
178
|
-
dns.timeouts = @dns_timeout
|
179
|
-
(mx_servers.any? && mx_servers) ||
|
180
|
-
dns.getresources(address.domain, Resolv::DNS::Resource::IN::A)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
150
|
end
|
185
151
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "resolv"
|
2
|
+
|
3
|
+
module ValidEmail2
|
4
|
+
class Dns
|
5
|
+
MAX_CACHE_SIZE = 1_000
|
6
|
+
CACHE = {}
|
7
|
+
|
8
|
+
CacheEntry = Struct.new(:records, :cached_at, :ttl)
|
9
|
+
|
10
|
+
def self.prune_cache
|
11
|
+
entries_sorted_by_cached_at_asc = CACHE.sort_by { |key, data| data.cached_at }
|
12
|
+
entries_to_remove = entries_sorted_by_cached_at_asc.first(CACHE.size - MAX_CACHE_SIZE)
|
13
|
+
entries_to_remove.each { |key, _value| CACHE.delete(key) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.clear_cache
|
17
|
+
CACHE.clear
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(dns_timeout = 5, dns_nameserver = nil)
|
21
|
+
@dns_timeout = dns_timeout
|
22
|
+
@dns_nameserver = dns_nameserver
|
23
|
+
end
|
24
|
+
|
25
|
+
def mx_servers(domain)
|
26
|
+
fetch(domain, Resolv::DNS::Resource::IN::MX)
|
27
|
+
end
|
28
|
+
|
29
|
+
def a_servers(domain)
|
30
|
+
fetch(domain, Resolv::DNS::Resource::IN::A)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def prune_cache
|
36
|
+
self.class.prune_cache
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch(domain, type)
|
40
|
+
prune_cache if CACHE.size > MAX_CACHE_SIZE
|
41
|
+
|
42
|
+
domain = domain.downcase
|
43
|
+
cache_key = [domain, type]
|
44
|
+
cache_entry = CACHE[cache_key]
|
45
|
+
|
46
|
+
if cache_entry && Time.now - cache_entry.cached_at < cache_entry.ttl
|
47
|
+
return cache_entry.records
|
48
|
+
else
|
49
|
+
CACHE.delete(cache_key)
|
50
|
+
end
|
51
|
+
|
52
|
+
records = Resolv::DNS.open(resolv_config) do |dns|
|
53
|
+
dns.timeouts = @dns_timeout
|
54
|
+
dns.getresources(domain, type)
|
55
|
+
end
|
56
|
+
|
57
|
+
if records.any?
|
58
|
+
ttl = records.map(&:ttl).min
|
59
|
+
CACHE[cache_key] = CacheEntry.new(records, Time.now, ttl)
|
60
|
+
end
|
61
|
+
|
62
|
+
records
|
63
|
+
end
|
64
|
+
|
65
|
+
def resolv_config
|
66
|
+
config = Resolv::DNS::Config.default_config_hash
|
67
|
+
config[:nameserver] = @dns_nameserver if @dns_nameserver
|
68
|
+
config
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "valid_email2/address"
|
2
|
+
require "logger" # Fix concurrent-ruby removing logger dependency which Rails itself does not have
|
2
3
|
require "active_model"
|
3
4
|
require "active_model/validations"
|
4
5
|
|
@@ -12,7 +13,8 @@ module ValidEmail2
|
|
12
13
|
return unless value.present?
|
13
14
|
options = default_options.merge(self.options)
|
14
15
|
|
15
|
-
|
16
|
+
dns = ValidEmail2::Dns.new(options[:dns_timeout], options[:dns_nameserver])
|
17
|
+
addresses = sanitized_values(value).map { |v| ValidEmail2::Address.new(v, dns) }
|
16
18
|
|
17
19
|
error(record, attribute) && return unless addresses.all?(&:valid?)
|
18
20
|
|
data/lib/valid_email2/version.rb
CHANGED
data/spec/address_spec.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
describe ValidEmail2::Address do
|
6
|
+
before do
|
7
|
+
ValidEmail2::Dns.clear_cache
|
8
|
+
end
|
9
|
+
|
6
10
|
describe "#valid?" do
|
7
11
|
it "is valid" do
|
8
12
|
address = described_class.new("foo@bar123.com")
|
@@ -58,8 +62,8 @@ describe ValidEmail2::Address do
|
|
58
62
|
|
59
63
|
describe "caching" do
|
60
64
|
let(:email_address) { "example@ymail.com" }
|
61
|
-
let(:
|
62
|
-
let(:
|
65
|
+
let(:dns_instance) { ValidEmail2::Dns.new }
|
66
|
+
let(:email_instance) { described_class.new(email_address, dns_instance) }
|
63
67
|
let(:ttl) { 1_000 }
|
64
68
|
let(:mock_resolv_dns) { instance_double(Resolv::DNS) }
|
65
69
|
let(:mock_mx_records) { [double("MX", exchange: "mx.ymail.com", preference: 10, ttl: ttl)] }
|
@@ -72,7 +76,7 @@ describe ValidEmail2::Address do
|
|
72
76
|
|
73
77
|
describe "#valid_strict_mx?" do
|
74
78
|
let(:cached_at) { Time.now }
|
75
|
-
let(:mock_cache_data) { { email_instance.address.domain =>
|
79
|
+
let(:mock_cache_data) { { [email_instance.address.domain, Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_mx_records, cached_at, ttl) } }
|
76
80
|
|
77
81
|
before do
|
78
82
|
allow(mock_resolv_dns).to receive(:getresources)
|
@@ -108,9 +112,9 @@ describe ValidEmail2::Address do
|
|
108
112
|
|
109
113
|
describe "ttl" do
|
110
114
|
before do
|
111
|
-
|
112
|
-
allow(ValidEmail2::
|
113
|
-
allow(
|
115
|
+
stub_const("ValidEmail2::Dns::CACHE", mock_cache_data)
|
116
|
+
allow(ValidEmail2::Dns).to receive(:new).and_return(dns_instance)
|
117
|
+
allow(dns_instance).to receive(:fetch).with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX).and_call_original
|
114
118
|
end
|
115
119
|
|
116
120
|
context "when the time since last lookup is less than the cached ttl entry" do
|
@@ -136,18 +140,18 @@ describe ValidEmail2::Address do
|
|
136
140
|
|
137
141
|
describe "cache size" do
|
138
142
|
before do
|
139
|
-
|
140
|
-
allow(ValidEmail2::
|
141
|
-
allow(
|
143
|
+
stub_const("ValidEmail2::Dns::CACHE", mock_cache_data)
|
144
|
+
allow(ValidEmail2::Dns).to receive(:new).and_return(dns_instance)
|
145
|
+
allow(dns_instance).to receive(:fetch).with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX).and_call_original
|
142
146
|
end
|
143
147
|
|
144
148
|
context "when the cache size is less than or equal to the max cache size" do
|
145
149
|
before do
|
146
|
-
stub_const("ValidEmail2::
|
150
|
+
stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 1)
|
147
151
|
end
|
148
152
|
|
149
153
|
it "does not prune the cache" do
|
150
|
-
expect(
|
154
|
+
expect(dns_instance).not_to receive(:prune_cache)
|
151
155
|
|
152
156
|
email_instance.valid_strict_mx?
|
153
157
|
end
|
@@ -159,42 +163,40 @@ describe ValidEmail2::Address do
|
|
159
163
|
end
|
160
164
|
|
161
165
|
context "and there are older cached entries" do
|
162
|
-
let(:mock_cache_data) { { "another_domain.com" =>
|
166
|
+
let(:mock_cache_data) { { ["another_domain.com", Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_mx_records, cached_at - 100, ttl) } }
|
163
167
|
|
164
168
|
it "does not prune those entries" do
|
165
169
|
email_instance.valid_strict_mx?
|
166
170
|
|
167
|
-
expect(
|
168
|
-
expect(dns_records_cache_instance.instance_variable_get(:@cache).keys).to match_array([email_instance.address.domain, "another_domain.com"])
|
171
|
+
expect(ValidEmail2::Dns::CACHE.keys).to match_array([[email_instance.address.domain, Resolv::DNS::Resource::IN::MX], ["another_domain.com", Resolv::DNS::Resource::IN::MX]])
|
169
172
|
end
|
170
173
|
end
|
171
174
|
end
|
172
175
|
|
173
176
|
context "when the cache size is greater than the max cache size" do
|
174
177
|
before do
|
175
|
-
stub_const("ValidEmail2::
|
178
|
+
stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
|
176
179
|
end
|
177
180
|
|
178
181
|
it "prunes the cache" do
|
179
|
-
expect(
|
182
|
+
expect(dns_instance).to receive(:prune_cache).once
|
180
183
|
|
181
184
|
email_instance.valid_strict_mx?
|
182
185
|
end
|
183
186
|
|
184
|
-
it "calls the the MX servers lookup" do
|
187
|
+
it "calls the the MX servers lookup" do
|
185
188
|
email_instance.valid_strict_mx?
|
186
189
|
|
187
190
|
expect(Resolv::DNS).to have_received(:open).once
|
188
191
|
end
|
189
192
|
|
190
193
|
context "and there are older cached entries" do
|
191
|
-
let(:mock_cache_data) { { "another_domain.com" =>
|
194
|
+
let(:mock_cache_data) { { ["another_domain.com", Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_mx_records, cached_at - 100, ttl) } }
|
192
195
|
|
193
196
|
it "prunes those entries" do
|
194
197
|
email_instance.valid_strict_mx?
|
195
198
|
|
196
|
-
expect(
|
197
|
-
expect(dns_records_cache_instance.instance_variable_get(:@cache).keys).to match_array([email_instance.address.domain])
|
199
|
+
expect(ValidEmail2::Dns::CACHE.keys).to match_array([[email_instance.address.domain, Resolv::DNS::Resource::IN::MX]])
|
198
200
|
end
|
199
201
|
end
|
200
202
|
end
|
@@ -203,13 +205,13 @@ describe ValidEmail2::Address do
|
|
203
205
|
|
204
206
|
describe "#valid_mx?" do
|
205
207
|
let(:cached_at) { Time.now }
|
206
|
-
let(:mock_cache_data) { { email_instance.address.domain =>
|
208
|
+
let(:mock_cache_data) { { [email_instance.address.domain, Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_a_records, cached_at, ttl) } }
|
207
209
|
let(:mock_a_records) { [double("A", address: "192.168.1.1", ttl: ttl)] }
|
208
210
|
|
209
211
|
before do
|
210
212
|
allow(email_instance).to receive(:mx_servers).and_return(mock_mx_records)
|
211
213
|
allow(mock_resolv_dns).to receive(:getresources)
|
212
|
-
.with(email_instance.address.domain, Resolv::DNS::Resource::IN::
|
214
|
+
.with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
213
215
|
.and_return(mock_a_records)
|
214
216
|
end
|
215
217
|
|
@@ -241,9 +243,9 @@ describe ValidEmail2::Address do
|
|
241
243
|
|
242
244
|
describe "ttl" do
|
243
245
|
before do
|
244
|
-
|
245
|
-
allow(ValidEmail2::
|
246
|
-
allow(
|
246
|
+
stub_const("ValidEmail2::Dns::CACHE", mock_cache_data)
|
247
|
+
allow(ValidEmail2::Dns).to receive(:new).and_return(dns_instance)
|
248
|
+
allow(dns_instance).to receive(:fetch).with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX).and_call_original
|
247
249
|
end
|
248
250
|
|
249
251
|
context "when the time since last lookup is less than the cached ttl entry" do
|
@@ -269,14 +271,14 @@ describe ValidEmail2::Address do
|
|
269
271
|
|
270
272
|
describe "cache size" do
|
271
273
|
before do
|
272
|
-
|
273
|
-
allow(ValidEmail2::
|
274
|
-
allow(
|
274
|
+
stub_const("ValidEmail2::Dns::CACHE", mock_cache_data)
|
275
|
+
allow(ValidEmail2::Dns).to receive(:new).and_return(dns_instance)
|
276
|
+
allow(dns_instance).to receive(:fetch).with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX).and_call_original
|
275
277
|
end
|
276
278
|
|
277
279
|
context "when the cache size is less than or equal to the max cache size" do
|
278
280
|
before do
|
279
|
-
stub_const("ValidEmail2::
|
281
|
+
stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 1)
|
280
282
|
end
|
281
283
|
|
282
284
|
it "does not prune the cache" do
|
@@ -292,24 +294,23 @@ describe ValidEmail2::Address do
|
|
292
294
|
end
|
293
295
|
|
294
296
|
context "and there are older cached entries" do
|
295
|
-
let(:mock_cache_data) { { "another_domain.com" =>
|
297
|
+
let(:mock_cache_data) { { ["another_domain.com", Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_a_records, cached_at - 100, ttl) } }
|
296
298
|
|
297
299
|
it "does not prune those entries" do
|
298
300
|
email_instance.valid_mx?
|
299
301
|
|
300
|
-
expect(
|
301
|
-
expect(dns_records_cache_instance.instance_variable_get(:@cache).keys).to match_array([email_instance.address.domain, "another_domain.com"])
|
302
|
+
expect(ValidEmail2::Dns::CACHE.keys).to match_array([[email_instance.address.domain, Resolv::DNS::Resource::IN::MX], ["another_domain.com", Resolv::DNS::Resource::IN::MX]])
|
302
303
|
end
|
303
304
|
end
|
304
305
|
end
|
305
306
|
|
306
307
|
context "when the cache size is greater than the max cache size" do
|
307
308
|
before do
|
308
|
-
stub_const("ValidEmail2::
|
309
|
+
stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
|
309
310
|
end
|
310
311
|
|
311
|
-
it "prunes the cache" do
|
312
|
-
expect(
|
312
|
+
it "prunes the cache" do
|
313
|
+
expect(dns_instance).to receive(:prune_cache).once
|
313
314
|
|
314
315
|
email_instance.valid_mx?
|
315
316
|
end
|
@@ -321,17 +322,16 @@ describe ValidEmail2::Address do
|
|
321
322
|
end
|
322
323
|
|
323
324
|
context "and there are older cached entries" do
|
324
|
-
let(:mock_cache_data) { { "another_domain.com" =>
|
325
|
+
let(:mock_cache_data) { { ["another_domain.com", Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_a_records, cached_at - 100, ttl) } }
|
325
326
|
|
326
327
|
it "prunes those entries" do
|
327
328
|
email_instance.valid_mx?
|
328
329
|
|
329
|
-
expect(
|
330
|
-
expect(dns_records_cache_instance.instance_variable_get(:@cache).keys).to match_array([email_instance.address.domain])
|
330
|
+
expect(ValidEmail2::Dns::CACHE.keys).to match_array([[email_instance.address.domain, Resolv::DNS::Resource::IN::MX]])
|
331
331
|
end
|
332
332
|
end
|
333
333
|
end
|
334
|
-
end
|
334
|
+
end
|
335
335
|
end
|
336
336
|
end
|
337
337
|
end
|
data/spec/dns_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe ValidEmail2::Dns do
|
6
|
+
describe "#mx_servers" do
|
7
|
+
it "gets a record" do
|
8
|
+
dns = described_class.new
|
9
|
+
records = dns.mx_servers("gmail.com")
|
10
|
+
expect(records.size).to_not be_zero
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#a_servers" do
|
15
|
+
it "gets a record" do
|
16
|
+
dns = described_class.new
|
17
|
+
records = dns.a_servers("gmail.com")
|
18
|
+
expect(records.size).to_not be_zero
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/valid_email2_spec.rb
CHANGED
data/valid_email2.gemspec
CHANGED
@@ -22,10 +22,11 @@ Gem::Specification.new do |spec|
|
|
22
22
|
|
23
23
|
spec.add_development_dependency "bundler", "~> 2.0"
|
24
24
|
spec.add_development_dependency "rake", "~> 12.3"
|
25
|
+
spec.add_development_dependency "securerandom", "0.3.1" # https://github.com/micke/valid_email2/actions/runs/13325070756/job/37216488894
|
25
26
|
spec.add_development_dependency "rspec", "~> 3.5"
|
26
27
|
spec.add_development_dependency "rspec-benchmark", "~> 0.6"
|
27
28
|
spec.add_development_dependency "net-smtp"
|
28
|
-
spec.add_development_dependency "
|
29
|
+
spec.add_development_dependency "debug"
|
29
30
|
spec.add_runtime_dependency "mail", "~> 2.5"
|
30
31
|
spec.add_runtime_dependency "activemodel", ">= 6.0"
|
31
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Micke Lisinge
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '12.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: securerandom
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.3.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.3.1
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,7 +95,7 @@ dependencies:
|
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
98
|
+
name: debug
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - ">="
|
@@ -144,15 +158,17 @@ files:
|
|
144
158
|
- config/disposable_email_domains.txt
|
145
159
|
- gemfiles/activemodel6.gemfile
|
146
160
|
- gemfiles/activemodel7.gemfile
|
161
|
+
- gemfiles/activemodel8.gemfile
|
147
162
|
- lib/valid_email2.rb
|
148
163
|
- lib/valid_email2/address.rb
|
149
|
-
- lib/valid_email2/
|
164
|
+
- lib/valid_email2/dns.rb
|
150
165
|
- lib/valid_email2/email_validator.rb
|
151
166
|
- lib/valid_email2/version.rb
|
152
167
|
- pull_mailchecker_emails.rb
|
153
168
|
- release-please-config.json
|
154
169
|
- spec/address_spec.rb
|
155
170
|
- spec/benchmark_spec.rb
|
171
|
+
- spec/dns_spec.rb
|
156
172
|
- spec/spec_helper.rb
|
157
173
|
- spec/valid_email2_spec.rb
|
158
174
|
- valid_email2.gemspec
|
@@ -160,7 +176,7 @@ homepage: https://github.com/micke/valid_email2
|
|
160
176
|
licenses:
|
161
177
|
- MIT
|
162
178
|
metadata: {}
|
163
|
-
post_install_message:
|
179
|
+
post_install_message:
|
164
180
|
rdoc_options: []
|
165
181
|
require_paths:
|
166
182
|
- lib
|
@@ -176,12 +192,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
192
|
version: '0'
|
177
193
|
requirements: []
|
178
194
|
rubygems_version: 3.5.3
|
179
|
-
signing_key:
|
195
|
+
signing_key:
|
180
196
|
specification_version: 4
|
181
197
|
summary: ActiveModel validation for email. Including MX lookup and disposable email
|
182
198
|
deny list
|
183
199
|
test_files:
|
184
200
|
- spec/address_spec.rb
|
185
201
|
- spec/benchmark_spec.rb
|
202
|
+
- spec/dns_spec.rb
|
186
203
|
- spec/spec_helper.rb
|
187
204
|
- spec/valid_email2_spec.rb
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module ValidEmail2
|
2
|
-
class DnsRecordsCache
|
3
|
-
MAX_CACHE_SIZE = 1_000
|
4
|
-
|
5
|
-
def initialize
|
6
|
-
# Cache structure: { domain (String): { records: [], cached_at: Time, ttl: Integer } }
|
7
|
-
@cache = {}
|
8
|
-
end
|
9
|
-
|
10
|
-
def fetch(domain, &block)
|
11
|
-
prune_cache if @cache.size > MAX_CACHE_SIZE
|
12
|
-
|
13
|
-
cache_entry = @cache[domain]
|
14
|
-
|
15
|
-
if cache_entry && (Time.now - cache_entry[:cached_at]) < cache_entry[:ttl]
|
16
|
-
return cache_entry[:records]
|
17
|
-
else
|
18
|
-
@cache.delete(domain)
|
19
|
-
end
|
20
|
-
|
21
|
-
records = block.call
|
22
|
-
|
23
|
-
if records.any?
|
24
|
-
ttl = records.map(&:ttl).min
|
25
|
-
@cache[domain] = { records: records, cached_at: Time.now, ttl: ttl }
|
26
|
-
end
|
27
|
-
|
28
|
-
records
|
29
|
-
end
|
30
|
-
|
31
|
-
def prune_cache
|
32
|
-
entries_sorted_by_cached_at_asc = (@cache.sort_by { |_domain, data| data[:cached_at] }).flatten
|
33
|
-
entries_to_remove = entries_sorted_by_cached_at_asc.first(@cache.size - MAX_CACHE_SIZE)
|
34
|
-
entries_to_remove.each { |domain| @cache.delete(domain) }
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|