valid_email2 7.0.0 → 7.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activemodel', '~> 8.0'
4
+
5
+ gemspec path: '../'
@@ -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/dns_records_cache"
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, dns_timeout = 5, dns_nameserver = nil)
31
+ def initialize(address, dns = Dns.new)
33
32
  @parse_error = false
34
33
  @raw_address = address
35
- @dns_timeout = dns_timeout
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
- mx_or_a_servers.any?
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
- addresses = sanitized_values(value).map { |v| ValidEmail2::Address.new(v, options[:dns_timeout], options[:dns_nameserver]) }
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal:true
2
2
 
3
3
  module ValidEmail2
4
- VERSION = "7.0.0"
4
+ VERSION = "7.0.9"
5
5
  end
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(:email_instance) { described_class.new(email_address) }
62
- let(:dns_records_cache_instance) { ValidEmail2::DnsRecordsCache.new }
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 => { records: mock_mx_records, cached_at: cached_at, ttl: ttl } } }
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
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
112
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
113
- allow(dns_records_cache_instance).to receive(:fetch).with(email_instance.address.domain).and_call_original
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
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
140
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
141
- allow(dns_records_cache_instance).to receive(:fetch).with(email_instance.address.domain).and_call_original
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::DnsRecordsCache::MAX_CACHE_SIZE", 1)
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(dns_records_cache_instance).not_to receive(:prune_cache)
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" => { records: mock_mx_records, cached_at: cached_at - 100, ttl: ttl } } }
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(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 2
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::DnsRecordsCache::MAX_CACHE_SIZE", 0)
178
+ stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
176
179
  end
177
180
 
178
181
  it "prunes the cache" do
179
- expect(dns_records_cache_instance).to receive(:prune_cache).once
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" => { records: mock_mx_records, cached_at: cached_at - 100, ttl: ttl } } }
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(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 1
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 => { records: mock_a_records, cached_at: cached_at, ttl: ttl } } }
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::A)
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
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
245
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
246
- allow(dns_records_cache_instance).to receive(:fetch).with(email_instance.address.domain).and_call_original
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
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
273
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
274
- allow(dns_records_cache_instance).to receive(:fetch).with(email_instance.address.domain).and_call_original
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::DnsRecordsCache::MAX_CACHE_SIZE", 1)
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" => { records: mock_a_records, cached_at: cached_at - 100, ttl: ttl } } }
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(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 2
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::DnsRecordsCache::MAX_CACHE_SIZE", 0)
309
+ stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
309
310
  end
310
311
 
311
- it "prunes the cache" do
312
- expect(dns_records_cache_instance).to receive(:prune_cache).once
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" => { records: mock_a_records, cached_at: cached_at - 100, ttl: ttl } } }
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(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 1
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
@@ -1,5 +1,6 @@
1
1
  $:.unshift File.expand_path("../lib",__FILE__)
2
2
  require "valid_email2"
3
+ require "debug"
3
4
 
4
5
  # Include and configure benchmark
5
6
  require 'rspec-benchmark'
@@ -76,6 +76,9 @@ class TestUserMultiple < TestModel
76
76
  end
77
77
 
78
78
  describe ValidEmail2 do
79
+ before do
80
+ ValidEmail2::Dns.clear_cache
81
+ end
79
82
 
80
83
  let(:disposable_domain) { ValidEmail2.disposable_emails.first }
81
84
 
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 "pry"
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.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: 2024-11-19 00:00:00.000000000 Z
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: pry
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/dns_records_cache.rb
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