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.
@@ -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 domain_is_in?(domain_list)
117
- address_domain = address.domain.downcase
118
- return true if domain_list.include?(address_domain)
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 mx_server_is_in?(domain_list)
127
- @dns.mx_servers(address.domain).any? { |mx_server|
128
- return false unless mx_server.respond_to?(:exchange)
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
- mx_server = mx_server.exchange.to_s
122
+ tokens = address_domain.split('.')
123
+ return false if tokens.length < 3
131
124
 
132
- domain_list.any? { |domain|
133
- mx_server.end_with?(domain) && mx_server =~ /\A(?:.+\.)*?#{domain}\z/
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?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal:true
2
2
 
3
3
  module ValidEmail2
4
- VERSION = "7.0.9"
4
+ VERSION = "7.0.11"
5
5
  end
@@ -29,6 +29,16 @@ remote_emails = [
29
29
  resp.body.split("\n").flatten
30
30
  end
31
31
 
32
- result_emails = (existing_emails + remote_emails).map(&:strip).uniq.sort - allow_listed_emails
33
-
34
- File.write("config/disposable_email_domains.txt", result_emails.join("\n"))
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 eq true
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: 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: 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)
@@ -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
- # coding: utf-8
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 = %q{ActiveModel validation for email. Including MX lookup and disposable email deny list}
12
- spec.summary = %q{ActiveModel validation for email. Including MX lookup and disposable email deny list}
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.9
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-14 00:00:00.000000000 Z
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.3
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