valid_email2 6.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,10 +1,8 @@
1
1
  # frozen_string_literal:true
2
2
 
3
3
  require "valid_email2"
4
- require "resolv"
5
4
  require "mail"
6
- require "unicode/emoji"
7
- require "valid_email2/dns_records_cache"
5
+ require "valid_email2/dns"
8
6
 
9
7
  module ValidEmail2
10
8
  class Address
@@ -22,11 +20,18 @@ module ValidEmail2
22
20
  @prohibited_domain_characters_regex = val
23
21
  end
24
22
 
25
- def initialize(address, dns_timeout = 5, dns_nameserver = nil)
23
+ def self.permitted_multibyte_characters_regex
24
+ @permitted_multibyte_characters_regex
25
+ end
26
+
27
+ def self.permitted_multibyte_characters_regex=(val)
28
+ @permitted_multibyte_characters_regex = val
29
+ end
30
+
31
+ def initialize(address, dns = Dns.new)
26
32
  @parse_error = false
27
33
  @raw_address = address
28
- @dns_timeout = dns_timeout
29
- @dns_nameserver = dns_nameserver
34
+ @dns = dns
30
35
 
31
36
  begin
32
37
  @address = Mail::Address.new(address)
@@ -34,7 +39,7 @@ module ValidEmail2
34
39
  @parse_error = true
35
40
  end
36
41
 
37
- @parse_error ||= address_contain_emoticons?
42
+ @parse_error ||= address_contain_multibyte_characters?
38
43
  end
39
44
 
40
45
  def valid?
@@ -96,14 +101,14 @@ module ValidEmail2
96
101
  return false unless valid?
97
102
  return false if null_mx?
98
103
 
99
- mx_or_a_servers.any?
104
+ @dns.mx_servers(address.domain).any? || @dns.a_servers(address.domain).any?
100
105
  end
101
106
 
102
107
  def valid_strict_mx?
103
108
  return false unless valid?
104
109
  return false if null_mx?
105
110
 
106
- mx_servers.any?
111
+ @dns.mx_servers(address.domain).any?
107
112
  end
108
113
 
109
114
  private
@@ -119,7 +124,7 @@ module ValidEmail2
119
124
  end
120
125
 
121
126
  def mx_server_is_in?(domain_list)
122
- mx_servers.any? { |mx_server|
127
+ @dns.mx_servers(address.domain).any? { |mx_server|
123
128
  return false unless mx_server.respond_to?(:exchange)
124
129
 
125
130
  mx_server = mx_server.exchange.to_s
@@ -130,47 +135,17 @@ module ValidEmail2
130
135
  }
131
136
  end
132
137
 
133
- def address_contain_emoticons?
138
+ def address_contain_multibyte_characters?
134
139
  return false if @raw_address.nil?
135
140
 
136
- @raw_address.scan(Unicode::Emoji::REGEX).length >= 1
137
- end
138
-
139
- def resolv_config
140
- @resolv_config ||= begin
141
- config = Resolv::DNS::Config.default_config_hash
142
- config[:nameserver] = @dns_nameserver if @dns_nameserver
143
- config
144
- end
141
+ return false if @raw_address.ascii_only?
145
142
 
146
- @resolv_config
147
- end
148
-
149
- def mx_servers
150
- @mx_servers_cache ||= ValidEmail2::DnsRecordsCache.new
151
-
152
- @mx_servers_cache.fetch(address.domain.downcase) do
153
- Resolv::DNS.open(resolv_config) do |dns|
154
- dns.timeouts = @dns_timeout
155
- dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
156
- end
157
- end
143
+ @raw_address.each_char.any? { |char| char.bytesize > 1 && char !~ self.class.permitted_multibyte_characters_regex }
158
144
  end
159
145
 
160
146
  def null_mx?
147
+ mx_servers = @dns.mx_servers(address.domain)
161
148
  mx_servers.length == 1 && mx_servers.first.preference == 0 && mx_servers.first.exchange.length == 0
162
149
  end
163
-
164
- def mx_or_a_servers
165
- @mx_or_a_servers_cache ||= ValidEmail2::DnsRecordsCache.new
166
-
167
- @mx_or_a_servers_cache.fetch(address.domain.downcase) do
168
- Resolv::DNS.open(resolv_config) do |dns|
169
- dns.timeouts = @dns_timeout
170
- (mx_servers.any? && mx_servers) ||
171
- dns.getresources(address.domain, Resolv::DNS::Resource::IN::A)
172
- end
173
- end
174
- end
175
150
  end
176
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 = "6.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")
@@ -34,16 +38,32 @@ describe ValidEmail2::Address do
34
38
  expect(address.valid?).to be false
35
39
  end
36
40
 
37
- it "is valid if it contains special scandinavian characters" do
41
+ it "is invalid if it contains Japanese characters" do
42
+ address = described_class.new("あいうえお@example.com")
43
+ expect(address.valid?).to be false
44
+ end
45
+
46
+ it "is invalid if it contains special scandinavian characters" do
38
47
  address = described_class.new("jørgen@email.dk")
39
- expect(address.valid?).to eq true
48
+ expect(address.valid?).to eq false
49
+ end
50
+
51
+ context "permitted_multibyte_characters_regex is set" do
52
+ before do
53
+ described_class.permitted_multibyte_characters_regex = /[ÆæØøÅåÄäÖöÞþÐð]/
54
+ end
55
+
56
+ it "is valid if it contains special scandinavian characters" do
57
+ address = described_class.new("jørgen@email.dk")
58
+ expect(address.valid?).to eq true
59
+ end
40
60
  end
41
61
  end
42
62
 
43
63
  describe "caching" do
44
64
  let(:email_address) { "example@ymail.com" }
45
- let(:email_instance) { described_class.new(email_address) }
46
- 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) }
47
67
  let(:ttl) { 1_000 }
48
68
  let(:mock_resolv_dns) { instance_double(Resolv::DNS) }
49
69
  let(:mock_mx_records) { [double("MX", exchange: "mx.ymail.com", preference: 10, ttl: ttl)] }
@@ -56,7 +76,7 @@ describe ValidEmail2::Address do
56
76
 
57
77
  describe "#valid_strict_mx?" do
58
78
  let(:cached_at) { Time.now }
59
- 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) } }
60
80
 
61
81
  before do
62
82
  allow(mock_resolv_dns).to receive(:getresources)
@@ -92,9 +112,9 @@ describe ValidEmail2::Address do
92
112
 
93
113
  describe "ttl" do
94
114
  before do
95
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
96
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
97
- 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
98
118
  end
99
119
 
100
120
  context "when the time since last lookup is less than the cached ttl entry" do
@@ -120,18 +140,18 @@ describe ValidEmail2::Address do
120
140
 
121
141
  describe "cache size" do
122
142
  before do
123
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
124
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
125
- 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
126
146
  end
127
147
 
128
148
  context "when the cache size is less than or equal to the max cache size" do
129
149
  before do
130
- stub_const("ValidEmail2::DnsRecordsCache::MAX_CACHE_SIZE", 1)
150
+ stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 1)
131
151
  end
132
152
 
133
153
  it "does not prune the cache" do
134
- expect(dns_records_cache_instance).not_to receive(:prune_cache)
154
+ expect(dns_instance).not_to receive(:prune_cache)
135
155
 
136
156
  email_instance.valid_strict_mx?
137
157
  end
@@ -143,42 +163,40 @@ describe ValidEmail2::Address do
143
163
  end
144
164
 
145
165
  context "and there are older cached entries" do
146
- 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) } }
147
167
 
148
168
  it "does not prune those entries" do
149
169
  email_instance.valid_strict_mx?
150
170
 
151
- expect(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 2
152
- 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]])
153
172
  end
154
173
  end
155
174
  end
156
175
 
157
176
  context "when the cache size is greater than the max cache size" do
158
177
  before do
159
- stub_const("ValidEmail2::DnsRecordsCache::MAX_CACHE_SIZE", 0)
178
+ stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
160
179
  end
161
180
 
162
181
  it "prunes the cache" do
163
- expect(dns_records_cache_instance).to receive(:prune_cache).once
182
+ expect(dns_instance).to receive(:prune_cache).once
164
183
 
165
184
  email_instance.valid_strict_mx?
166
185
  end
167
186
 
168
- it "calls the the MX servers lookup" do
187
+ it "calls the the MX servers lookup" do
169
188
  email_instance.valid_strict_mx?
170
189
 
171
190
  expect(Resolv::DNS).to have_received(:open).once
172
191
  end
173
192
 
174
193
  context "and there are older cached entries" do
175
- 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) } }
176
195
 
177
196
  it "prunes those entries" do
178
197
  email_instance.valid_strict_mx?
179
198
 
180
- expect(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 1
181
- 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]])
182
200
  end
183
201
  end
184
202
  end
@@ -187,13 +205,13 @@ describe ValidEmail2::Address do
187
205
 
188
206
  describe "#valid_mx?" do
189
207
  let(:cached_at) { Time.now }
190
- 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) } }
191
209
  let(:mock_a_records) { [double("A", address: "192.168.1.1", ttl: ttl)] }
192
210
 
193
211
  before do
194
212
  allow(email_instance).to receive(:mx_servers).and_return(mock_mx_records)
195
213
  allow(mock_resolv_dns).to receive(:getresources)
196
- .with(email_instance.address.domain, Resolv::DNS::Resource::IN::A)
214
+ .with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
197
215
  .and_return(mock_a_records)
198
216
  end
199
217
 
@@ -225,9 +243,9 @@ describe ValidEmail2::Address do
225
243
 
226
244
  describe "ttl" do
227
245
  before do
228
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
229
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
230
- 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
231
249
  end
232
250
 
233
251
  context "when the time since last lookup is less than the cached ttl entry" do
@@ -253,14 +271,14 @@ describe ValidEmail2::Address do
253
271
 
254
272
  describe "cache size" do
255
273
  before do
256
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
257
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
258
- 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
259
277
  end
260
278
 
261
279
  context "when the cache size is less than or equal to the max cache size" do
262
280
  before do
263
- stub_const("ValidEmail2::DnsRecordsCache::MAX_CACHE_SIZE", 1)
281
+ stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 1)
264
282
  end
265
283
 
266
284
  it "does not prune the cache" do
@@ -276,24 +294,23 @@ describe ValidEmail2::Address do
276
294
  end
277
295
 
278
296
  context "and there are older cached entries" do
279
- 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) } }
280
298
 
281
299
  it "does not prune those entries" do
282
300
  email_instance.valid_mx?
283
301
 
284
- expect(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 2
285
- 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]])
286
303
  end
287
304
  end
288
305
  end
289
306
 
290
307
  context "when the cache size is greater than the max cache size" do
291
308
  before do
292
- stub_const("ValidEmail2::DnsRecordsCache::MAX_CACHE_SIZE", 0)
309
+ stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
293
310
  end
294
311
 
295
- it "prunes the cache" do
296
- 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
297
314
 
298
315
  email_instance.valid_mx?
299
316
  end
@@ -305,17 +322,16 @@ describe ValidEmail2::Address do
305
322
  end
306
323
 
307
324
  context "and there are older cached entries" do
308
- 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) } }
309
326
 
310
327
  it "prunes those entries" do
311
328
  email_instance.valid_mx?
312
329
 
313
- expect(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 1
314
- 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]])
315
331
  end
316
332
  end
317
333
  end
318
- end
334
+ end
319
335
  end
320
336
  end
321
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,11 +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
- spec.add_runtime_dependency "unicode-emoji", "~> 3.7.0"
32
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: 6.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-03 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
  - - ">="
@@ -122,20 +136,6 @@ dependencies:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
138
  version: '6.0'
125
- - !ruby/object:Gem::Dependency
126
- name: unicode-emoji
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: 3.7.0
132
- type: :runtime
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: 3.7.0
139
139
  description: ActiveModel validation for email. Including MX lookup and disposable
140
140
  email deny list
141
141
  email:
@@ -158,15 +158,17 @@ files:
158
158
  - config/disposable_email_domains.txt
159
159
  - gemfiles/activemodel6.gemfile
160
160
  - gemfiles/activemodel7.gemfile
161
+ - gemfiles/activemodel8.gemfile
161
162
  - lib/valid_email2.rb
162
163
  - lib/valid_email2/address.rb
163
- - lib/valid_email2/dns_records_cache.rb
164
+ - lib/valid_email2/dns.rb
164
165
  - lib/valid_email2/email_validator.rb
165
166
  - lib/valid_email2/version.rb
166
167
  - pull_mailchecker_emails.rb
167
168
  - release-please-config.json
168
169
  - spec/address_spec.rb
169
170
  - spec/benchmark_spec.rb
171
+ - spec/dns_spec.rb
170
172
  - spec/spec_helper.rb
171
173
  - spec/valid_email2_spec.rb
172
174
  - valid_email2.gemspec
@@ -174,7 +176,7 @@ homepage: https://github.com/micke/valid_email2
174
176
  licenses:
175
177
  - MIT
176
178
  metadata: {}
177
- post_install_message:
179
+ post_install_message:
178
180
  rdoc_options: []
179
181
  require_paths:
180
182
  - lib
@@ -190,12 +192,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
190
192
  version: '0'
191
193
  requirements: []
192
194
  rubygems_version: 3.5.3
193
- signing_key:
195
+ signing_key:
194
196
  specification_version: 4
195
197
  summary: ActiveModel validation for email. Including MX lookup and disposable email
196
198
  deny list
197
199
  test_files:
198
200
  - spec/address_spec.rb
199
201
  - spec/benchmark_spec.rb
202
+ - spec/dns_spec.rb
200
203
  - spec/spec_helper.rb
201
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