valid_email2 3.3.1 → 3.7.0

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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal:true
2
+
1
3
  require "valid_email2"
2
4
  require "resolv"
3
5
  require "mail"
@@ -7,8 +9,8 @@ module ValidEmail2
7
9
  attr_accessor :address
8
10
 
9
11
  PROHIBITED_DOMAIN_CHARACTERS_REGEX = /[+!_\/\s'`]/
10
- DEFAULT_RECIPIENT_DELIMITER = '+'.freeze
11
- DOT_DELIMITER = '.'.freeze
12
+ DEFAULT_RECIPIENT_DELIMITER = '+'
13
+ DOT_DELIMITER = '.'
12
14
 
13
15
  def self.prohibited_domain_characters_regex
14
16
  @prohibited_domain_characters_regex ||= PROHIBITED_DOMAIN_CHARACTERS_REGEX
@@ -35,21 +37,27 @@ module ValidEmail2
35
37
  return @valid unless @valid.nil?
36
38
  return false if @parse_error
37
39
 
38
- @valid = begin
39
- if address.domain && address.address == @raw_address
40
- domain = address.domain
41
-
42
- domain !~ self.class.prohibited_domain_characters_regex &&
43
- domain.include?('.') &&
44
- !domain.include?('..') &&
45
- !domain.start_with?('.') &&
46
- !domain.start_with?('-') &&
47
- !domain.include?('-.') &&
48
- !address.local.end_with?('.')
49
- else
50
- false
51
- end
52
- end
40
+ @valid = address.domain &&
41
+ address.address == @raw_address &&
42
+ valid_domain? &&
43
+ valid_address?
44
+ end
45
+
46
+ def valid_domain?
47
+ domain = address.domain
48
+
49
+ domain !~ self.class.prohibited_domain_characters_regex &&
50
+ domain.include?('.') &&
51
+ !domain.include?('..') &&
52
+ !domain.start_with?('.') &&
53
+ !domain.start_with?('-') &&
54
+ !domain.include?('-.')
55
+ end
56
+
57
+ def valid_address?
58
+ !address.local.include?('..') &&
59
+ !address.local.end_with?('.') &&
60
+ !address.local.start_with?('.')
53
61
  end
54
62
 
55
63
  def dotted?
@@ -83,6 +91,12 @@ module ValidEmail2
83
91
  def valid_mx?
84
92
  return false unless valid?
85
93
 
94
+ mx_or_a_servers.any?
95
+ end
96
+
97
+ def valid_strict_mx?
98
+ return false unless valid?
99
+
86
100
  mx_servers.any?
87
101
  end
88
102
 
@@ -95,12 +109,13 @@ module ValidEmail2
95
109
  i = address_domain.index('.')
96
110
  return false unless i
97
111
 
98
- return domain_list.include?(address_domain[(i+1)..-1])
112
+ domain_list.include?(address_domain[(i + 1)..-1])
99
113
  end
100
114
 
101
115
  def mx_server_is_in?(domain_list)
102
116
  mx_servers.any? { |mx_server|
103
117
  return false unless mx_server.respond_to?(:exchange)
118
+
104
119
  mx_server = mx_server.exchange.to_s
105
120
 
106
121
  domain_list.any? { |domain|
@@ -117,7 +132,12 @@ module ValidEmail2
117
132
 
118
133
  def mx_servers
119
134
  @mx_servers ||= Resolv::DNS.open do |dns|
120
- mx_servers = dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
135
+ dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
136
+ end
137
+ end
138
+
139
+ def mx_or_a_servers
140
+ @mx_or_a_servers ||= Resolv::DNS.open do |dns|
121
141
  (mx_servers.any? && mx_servers) ||
122
142
  dns.getresources(address.domain, Resolv::DNS::Resource::IN::A)
123
143
  end
@@ -5,15 +5,14 @@ require "active_model/validations"
5
5
  module ValidEmail2
6
6
  class EmailValidator < ActiveModel::EachValidator
7
7
  def default_options
8
- { regex: true, disposable: false, mx: false, disallow_subaddressing: false, multiple: false }
8
+ { regex: true, disposable: false, mx: false, strict_mx: false, disallow_subaddressing: false, multiple: false }
9
9
  end
10
10
 
11
11
  def validate_each(record, attribute, value)
12
12
  return unless value.present?
13
13
  options = default_options.merge(self.options)
14
14
 
15
- value_spitted = options[:multiple] ? value.split(',').map(&:strip) : [value]
16
- addresses = value_spitted.map { |v| ValidEmail2::Address.new(v) }
15
+ addresses = sanitized_values(value).map { |v| ValidEmail2::Address.new(v) }
17
16
 
18
17
  error(record, attribute) && return unless addresses.all?(&:valid?)
19
18
 
@@ -37,6 +36,10 @@ module ValidEmail2
37
36
  error(record, attribute) && return if addresses.any? { |address| address.disposable? && !address.whitelisted? }
38
37
  end
39
38
 
39
+ if options[:disposable_domain_with_whitelist]
40
+ error(record, attribute) && return if addresses.any? { |address| address.disposable_domain? && !address.whitelisted? }
41
+ end
42
+
40
43
  if options[:blacklist]
41
44
  error(record, attribute) && return if addresses.any?(&:blacklisted?)
42
45
  end
@@ -44,6 +47,22 @@ module ValidEmail2
44
47
  if options[:mx]
45
48
  error(record, attribute) && return unless addresses.all?(&:valid_mx?)
46
49
  end
50
+
51
+ if options[:strict_mx]
52
+ error(record, attribute) && return unless addresses.all?(&:valid_strict_mx?)
53
+ end
54
+ end
55
+
56
+ def sanitized_values(input)
57
+ options = default_options.merge(self.options)
58
+
59
+ if options[:multiple]
60
+ email_list = input.is_a?(Array) ? input : input.split(',')
61
+ else
62
+ email_list = [input]
63
+ end
64
+
65
+ email_list.reject(&:empty?).map(&:strip)
47
66
  end
48
67
 
49
68
  def error(record, attribute)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal:true
2
+
1
3
  module ValidEmail2
2
- VERSION = "3.3.1"
4
+ VERSION = "3.7.0"
3
5
  end
@@ -8,7 +8,7 @@ require "net/http"
8
8
  whitelisted_emails = %w(
9
9
  onet.pl poczta.onet.pl fastmail.fm hushmail.com
10
10
  hush.ai hush.com hushmail.me naver.com qq.com example.com
11
- yandex.net gmx.com gmx.es
11
+ yandex.net gmx.com gmx.es webdesignspecialist.com.au vp.com
12
12
  )
13
13
 
14
14
  existing_emails = File.open("config/disposable_email_domains.txt") { |f| f.read.split("\n") }
@@ -19,6 +19,10 @@ class TestUserMX < TestModel
19
19
  validates :email, 'valid_email_2/email': { mx: true }
20
20
  end
21
21
 
22
+ class TestUserStrictMX < TestModel
23
+ validates :email, 'valid_email_2/email': { strict_mx: true }
24
+ end
25
+
22
26
  class TestUserDisallowDisposable < TestModel
23
27
  validates :email, 'valid_email_2/email': { disposable: true }
24
28
  end
@@ -31,6 +35,10 @@ class TestUserDisallowDisposableWithWhitelist < TestModel
31
35
  validates :email, 'valid_email_2/email': { disposable_with_whitelist: true }
32
36
  end
33
37
 
38
+ class TestUserDisallowDisposableDomainWithWhitelist < TestModel
39
+ validates :email, 'valid_email_2/email': { disposable_domain_with_whitelist: true }
40
+ end
41
+
34
42
  class TestUserDisallowBlacklisted < TestModel
35
43
  validates :email, 'valid_email_2/email': { blacklist: true }
36
44
  end
@@ -87,6 +95,21 @@ describe ValidEmail2 do
87
95
  expect(user.valid?).to be_falsey
88
96
  end
89
97
 
98
+ it "is invalid if the address contains consecutive dots" do
99
+ user = TestUser.new(email: "foo..bar@gmail.com")
100
+ expect(user.valid?).to be_falsey
101
+ end
102
+
103
+ it "is invalid if the address starts with a dot" do
104
+ user = TestUser.new(email: ".foo@bar.com")
105
+ expect(user.valid?).to be_falsey
106
+ end
107
+
108
+ it "is invalid if the local part of the address ends with a dot" do
109
+ user = TestUser.new(email: "foo.@bar.com")
110
+ expect(user.valid?).to be_falsey
111
+ end
112
+
90
113
  it "is invalid if the email contains emoticons" do
91
114
  user = TestUser.new(email: "foo🙈@gmail.com")
92
115
  expect(user.valid?).to be_falsy
@@ -165,8 +188,18 @@ describe ValidEmail2 do
165
188
  let(:whitelist_domain) { disposable_domain }
166
189
  let(:whitelist_file_path) { "config/whitelisted_email_domains.yml" }
167
190
 
191
+ # Some of the specs below need to explictly set the whitelist var or it
192
+ # may be cached to an empty set
193
+ def set_whitelist
194
+ ValidEmail2.instance_variable_set(
195
+ :@whitelist,
196
+ ValidEmail2.send(:load_if_exists, ValidEmail2::WHITELIST_FILE)
197
+ )
198
+ end
199
+
168
200
  after do
169
201
  FileUtils.rm(whitelist_file_path, force: true)
202
+ set_whitelist
170
203
  end
171
204
 
172
205
  it "is invalid if the domain is disposable and not in the whitelist" do
@@ -176,9 +209,22 @@ describe ValidEmail2 do
176
209
 
177
210
  it "is valid if the domain is disposable but in the whitelist" do
178
211
  File.open(whitelist_file_path, "w") { |f| f.write [whitelist_domain].to_yaml }
212
+ set_whitelist
179
213
  user = TestUserDisallowDisposableWithWhitelist.new(email: "foo@#{whitelist_domain}")
214
+ expect(user.valid?).to be_truthy
215
+ end
216
+
217
+ it "is invalid if the domain is a disposable_domain and not in the whitelist" do
218
+ user = TestUserDisallowDisposableDomainWithWhitelist.new(email: "foo@#{whitelist_domain}")
180
219
  expect(user.valid?).to be_falsey
181
220
  end
221
+
222
+ it "is valid if the domain is a disposable_domain but in the whitelist" do
223
+ File.open(whitelist_file_path, "w") { |f| f.write [whitelist_domain].to_yaml }
224
+ set_whitelist
225
+ user = TestUserDisallowDisposableDomainWithWhitelist.new(email: "foo@#{whitelist_domain}")
226
+ expect(user.valid?).to be_truthy
227
+ end
182
228
  end
183
229
  end
184
230
 
@@ -211,6 +257,23 @@ describe ValidEmail2 do
211
257
  end
212
258
  end
213
259
 
260
+ describe "with strict mx validation" do
261
+ it "is valid if mx records are found" do
262
+ user = TestUserStrictMX.new(email: "foo@gmail.com")
263
+ expect(user.valid?).to be_truthy
264
+ end
265
+
266
+ it "is invalid if A records are found but no mx records are found" do
267
+ user = TestUserStrictMX.new(email: "foo@ghs.google.com")
268
+ expect(user.valid?).to be_falsey
269
+ end
270
+
271
+ it "is invalid if no mx records are found" do
272
+ user = TestUserStrictMX.new(email: "foo@subdomain.gmail.com")
273
+ expect(user.valid?).to be_falsey
274
+ end
275
+ end
276
+
214
277
  describe "with dotted validation" do
215
278
  it "is valid when address does not contain dots" do
216
279
  user = TestUserDotted.new(email: "johndoe@gmail.com")
@@ -249,6 +312,11 @@ describe ValidEmail2 do
249
312
  expect(user.valid?).to be_truthy
250
313
  end
251
314
 
315
+ it "tests each address from an array" do
316
+ user = TestUserMultiple.new(email: %w[foo@gmail.com bar@gmail.com])
317
+ expect(user.valid?).to be_truthy
318
+ end
319
+
252
320
  context 'when one address is invalid' do
253
321
  it "fails for all" do
254
322
  user = TestUserMultiple.new(email: "foo@gmail.com, bar@123")
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: 3.3.1
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micke Lisinge
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-10 00:00:00.000000000 Z
11
+ date: 2021-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -157,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
159
  requirements: []
160
- rubygems_version: 3.1.2
160
+ rubygems_version: 3.2.3
161
161
  signing_key:
162
162
  specification_version: 4
163
163
  summary: ActiveModel validation for email. Including MX lookup and disposable email