valid_email2 3.2.4 → 3.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f0eea64eb523ab5e3993b7e8c8abff01c0de45535ec72e81c94177c70442d59
4
- data.tar.gz: 771e67404527aa129be31a94c2eef0738f54b3e653f394964fa215dabe786b21
3
+ metadata.gz: 85903e3f9f5d0e8222dcc7768e15632eecf4a6008e8a43c2d88549cfa9a140ae
4
+ data.tar.gz: 2519e54f15012418caa00608301ff19f91640ecf63e9cf88c586afa2d1a0d2d2
5
5
  SHA512:
6
- metadata.gz: 4d491d4b61f26a9155cee37e78e5a78fd60579418f17bfbe76713b3d9ab7d6a0e371f919c36bddf2843c69392c2cb6e3ed4f32d2b392b7642d5008b02b0ae02a
7
- data.tar.gz: 5ad7f4f013df6a23662c12e70113bdc9f74e98f31b680fad0de7730f17b4bd15b8761b26d1fb2d21f3b58dc89abcf049f225bfc5a05c5edc5ca9b0f50a81bd70
6
+ metadata.gz: 811d82babbd7ad076fc077d4dea52074ecf6591926ed6b37b4f6595b614bc46eeacfd944db95ebc577a6cfb36d858009a051eed67e87fa5ce3f06b63c576c8b6
7
+ data.tar.gz: 653fe9f637840019997480853e5784e4fba055f4d579bce8af3b30e631da8fe86a5074e00564d72444f7b4a1cd0f4d7d829d9928e7c3962d1b612b595cc16545
@@ -1,3 +1,23 @@
1
+ ## Version 3.5.0
2
+ * Disallow emails starting with a dot https://github.com/micke/valid_email2/pull/170
3
+ * Add option to whitelist domains from MX check https://github.com/micke/valid_email2/pull/167
4
+ * Remove false positives
5
+
6
+ ## Version 3.4.0
7
+ * Disallow consecutive dots https://github.com/micke/valid_email2/pull/163
8
+ * Add andyes.net https://github.com/micke/valid_email2/pull/162
9
+
10
+ ## Version 3.3.1
11
+ * Fix some performance regressions (https://github.com/micke/valid_email2/pull/150)
12
+
13
+ ## Version 3.3.0
14
+ * Allow multiple addresses separated by comma (https://github.com/micke/valid_email2/pull/156)
15
+ * Make prohibited_domain_characters_regex changeable (https://github.com/micke/valid_email2/pull/157)
16
+
17
+ ## Version 3.2.5
18
+ * Remove false positives
19
+ * Pull new domains
20
+
1
21
  ## Version 3.2.4
2
22
  * Remove false positives
3
23
 
data/README.md CHANGED
@@ -58,11 +58,17 @@ To validate that the domain is not a disposable email (checks domain only, does
58
58
  validates :email, 'valid_email_2/email': { disposable_domain: true }
59
59
  ```
60
60
 
61
- To validate that the domain is not a disposable email or a disposable email but whitelisted (under config/whitelisted_email_domains.yml):
61
+ To validate that the domain is not a disposable email or a disposable email (checks domain and MX server) but whitelisted (under config/whitelisted_email_domains.yml):
62
62
  ```ruby
63
63
  validates :email, 'valid_email_2/email': { disposable_with_whitelist: true }
64
64
  ```
65
65
 
66
+ To validate that the domain is not a disposable email or a disposable email (checks domain only, does not check MX server) but whitelisted (under config/whitelisted_email_domains.yml):
67
+
68
+ ```ruby
69
+ validates :email, 'valid_email_2/email': { disposable_domain_with_whitelist: true }
70
+ ```
71
+
66
72
  To validate that the domain is not blacklisted (under config/blacklisted_email_domains.yml):
67
73
  ```ruby
68
74
  validates :email, 'valid_email_2/email': { blacklist: true }
@@ -83,6 +89,11 @@ To validate create your own custom message:
83
89
  validates :email, 'valid_email_2/email': { message: "is not a valid email" }
84
90
  ```
85
91
 
92
+ To allow multiple addresses separated by comma:
93
+ ```ruby
94
+ validates :email, 'valid_email_2/email': { multiple: true }
95
+ ```
96
+
86
97
  All together:
87
98
  ```ruby
88
99
  validates :email, 'valid_email_2/email': { mx: true, disposable: true, disallow_subaddressing: true}
@@ -3398,6 +3398,7 @@ androidsapps.co
3398
3398
  androidworld.tw
3399
3399
  andthen.us
3400
3400
  andy1mail.host
3401
+ andyes.net
3401
3402
  andynugraha.net
3402
3403
  andyyxc45.biz
3403
3404
  aneaproducciones.com
@@ -7866,6 +7867,7 @@ cungmuachungnhom.com
7866
7867
  cungsuyngam.com
7867
7868
  cungtam.com
7868
7869
  cuoiz.com
7870
+ cuoly.com
7869
7871
  cuongvumarketingseo.com
7870
7872
  cupf6mdhtujxytdcoxh.cf
7871
7873
  cupf6mdhtujxytdcoxh.ga
@@ -9772,7 +9774,6 @@ edu.auction
9772
9774
  edu.dmtc.dev
9773
9775
  edu.hstu.eu.org
9774
9776
  edu.my
9775
- edu.sg
9776
9777
  eduanswer.ru
9777
9778
  education.eu
9778
9779
  educationleaders-ksa.com
@@ -10352,6 +10353,7 @@ eo-z.com
10352
10353
  eoffice.top
10353
10354
  eomail.com
10354
10355
  eonmech.com
10356
+ eoopy.com
10355
10357
  eorbs.com
10356
10358
  eos2mail.com
10357
10359
  eotoplenie.ru
@@ -11401,6 +11403,8 @@ fitnessjockey.org
11401
11403
  fitnessmojo.org
11402
11404
  fitnessreviewsonline.com
11403
11405
  fitnesszbyszko.pl
11406
+ fitschool.be
11407
+ fitschool.space
11404
11408
  fittinggeeks.pl
11405
11409
  fitzgeraldforjudge.com
11406
11410
  five-club.com
@@ -12320,7 +12324,6 @@ gd6ubc0xilchpozgpg.gq
12320
12324
  gd6ubc0xilchpozgpg.ml
12321
12325
  gd6ubc0xilchpozgpg.tk
12322
12326
  gdb.armageddon.org
12323
- gdf.it
12324
12327
  gdfretertwer.com
12325
12328
  gdmail.top
12326
12329
  gdradr.com
@@ -28483,6 +28486,7 @@ tempemailaddress.com
28483
28486
  tempemails.io
28484
28487
  tempinbox.co.uk
28485
28488
  tempinbox.com
28489
+ tempinbox.xyz
28486
28490
  tempm.cf
28487
28491
  tempm.com
28488
28492
  tempm.ga
@@ -31022,7 +31026,6 @@ voyagebirmanie.net
31022
31026
  voyancegratuite10min.com
31023
31027
  voyeurseite.info
31024
31028
  vozmivtop.ru
31025
- vp.com
31026
31029
  vp.ycare.de
31027
31030
  vpanel.ru
31028
31031
  vpc608a0.pl
@@ -7,23 +7,35 @@ module ValidEmail2
7
7
  WHITELIST_FILE = "config/whitelisted_email_domains.yml"
8
8
  DISPOSABLE_FILE = File.expand_path('../config/disposable_email_domains.txt', __dir__)
9
9
 
10
- def self.disposable_emails
11
- @disposable_emails ||= File.open(DISPOSABLE_FILE){ |f| f.read }.split("\n")
12
- end
10
+ class << self
11
+ def disposable_emails
12
+ @disposable_emails ||= load_file(DISPOSABLE_FILE)
13
+ end
13
14
 
14
- def self.blacklist
15
- @blacklist ||= if File.exist?(BLACKLIST_FILE)
16
- YAML.load_file(File.expand_path(BLACKLIST_FILE))
17
- else
18
- []
19
- end
20
- end
15
+ def blacklist
16
+ @blacklist ||= load_if_exists(BLACKLIST_FILE)
17
+ end
18
+
19
+ def whitelist
20
+ @whitelist ||= load_if_exists(WHITELIST_FILE)
21
+ end
22
+
23
+ private
24
+
25
+ def load_if_exists(path)
26
+ File.exist?(path) ? load_file(path) : Set.new
27
+ end
21
28
 
22
- def self.whitelist
23
- @whitelist ||= if File.exist?(WHITELIST_FILE)
24
- YAML.load_file(File.expand_path(WHITELIST_FILE))
25
- else
26
- []
27
- end
29
+ def load_file(path)
30
+ # This method MUST return a Set, otherwise the
31
+ # performance will suffer!
32
+ if path.end_with?(".yml")
33
+ Set.new(YAML.load_file(path))
34
+ else
35
+ File.open(path, "r").each_line.each_with_object(Set.new) do |domain, set|
36
+ set << domain.tap(&:chomp!)
37
+ end
38
+ end
39
+ end
28
40
  end
29
41
  end
@@ -10,6 +10,14 @@ module ValidEmail2
10
10
  DEFAULT_RECIPIENT_DELIMITER = '+'.freeze
11
11
  DOT_DELIMITER = '.'.freeze
12
12
 
13
+ def self.prohibited_domain_characters_regex
14
+ @prohibited_domain_characters_regex ||= PROHIBITED_DOMAIN_CHARACTERS_REGEX
15
+ end
16
+
17
+ def self.prohibited_domain_characters_regex=(val)
18
+ @prohibited_domain_characters_regex = val
19
+ end
20
+
13
21
  def initialize(address)
14
22
  @parse_error = false
15
23
  @raw_address = address
@@ -24,25 +32,22 @@ module ValidEmail2
24
32
  end
25
33
 
26
34
  def valid?
27
- @valid ||= begin
28
- return false if @parse_error
35
+ return @valid unless @valid.nil?
36
+ return false if @parse_error
29
37
 
38
+ @valid = begin
30
39
  if address.domain && address.address == @raw_address
31
40
  domain = address.domain
32
41
 
33
- domain !~ PROHIBITED_DOMAIN_CHARACTERS_REGEX &&
34
- # Domain needs to have at least one dot
35
- domain =~ /\./ &&
36
- # Domain may not have two consecutive dots
37
- domain !~ /\.{2,}/ &&
38
- # Domain may not start with a dot
39
- domain !~ /^\./ &&
40
- # Domain may not start with a dash
41
- domain !~ /^-/ &&
42
- # Domain name may not end with a dash
43
- domain !~ /-\./ &&
44
- # Address may not contain a dot directly before @
45
- address.address !~ /\.@/
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.include?('..') &&
49
+ !address.local.end_with?('.') &&
50
+ !address.local.start_with?('.')
46
51
  else
47
52
  false
48
53
  end
@@ -62,7 +67,7 @@ module ValidEmail2
62
67
  end
63
68
 
64
69
  def disposable_domain?
65
- valid? && domain_is_in?(ValidEmail2.disposable_emails)
70
+ domain_is_in?(ValidEmail2.disposable_emails)
66
71
  end
67
72
 
68
73
  def disposable_mx_server?
@@ -5,43 +5,48 @@ 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 }
8
+ { regex: true, disposable: false, 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
- address = ValidEmail2::Address.new(value)
15
+ value_spitted = options[:multiple] ? value.split(',').map(&:strip) : [value]
16
+ addresses = value_spitted.map { |v| ValidEmail2::Address.new(v) }
16
17
 
17
- error(record, attribute) && return unless address.valid?
18
+ error(record, attribute) && return unless addresses.all?(&:valid?)
18
19
 
19
20
  if options[:disallow_dotted]
20
- error(record, attribute) && return if address.dotted?
21
+ error(record, attribute) && return if addresses.any?(&:dotted?)
21
22
  end
22
23
 
23
24
  if options[:disallow_subaddressing]
24
- error(record, attribute) && return if address.subaddressed?
25
+ error(record, attribute) && return if addresses.any?(&:subaddressed?)
25
26
  end
26
27
 
27
28
  if options[:disposable]
28
- error(record, attribute) && return if address.disposable?
29
+ error(record, attribute) && return if addresses.any?(&:disposable?)
29
30
  end
30
31
 
31
32
  if options[:disposable_domain]
32
- error(record, attribute) && return if address.disposable_domain?
33
+ error(record, attribute) && return if addresses.any?(&:disposable_domain?)
33
34
  end
34
35
 
35
36
  if options[:disposable_with_whitelist]
36
- error(record, attribute) && return if address.disposable? && !address.whitelisted?
37
+ error(record, attribute) && return if addresses.any? { |address| address.disposable? && !address.whitelisted? }
38
+ end
39
+
40
+ if options[:disposable_domain_with_whitelist]
41
+ error(record, attribute) && return if addresses.any? { |address| address.disposable_domain? && !address.whitelisted? }
37
42
  end
38
43
 
39
44
  if options[:blacklist]
40
- error(record, attribute) && return if address.blacklisted?
45
+ error(record, attribute) && return if addresses.any?(&:blacklisted?)
41
46
  end
42
47
 
43
48
  if options[:mx]
44
- error(record, attribute) && return unless address.valid_mx?
49
+ error(record, attribute) && return unless addresses.all?(&:valid_mx?)
45
50
  end
46
51
  end
47
52
 
@@ -1,3 +1,3 @@
1
1
  module ValidEmail2
2
- VERSION = "3.2.4"
2
+ VERSION = "3.5.0"
3
3
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe "Performance testing" do
6
+ let(:disposable_domain) { ValidEmail2.disposable_emails.first }
7
+
8
+ it "has acceptable lookup performance" do
9
+ address = ValidEmail2::Address.new("test@example.com")
10
+
11
+ # preload list and check size
12
+ expect(ValidEmail2.disposable_emails).to be_a(Set)
13
+ expect(ValidEmail2.disposable_emails.count).to be > 30000
14
+
15
+ # check lookup timing
16
+ expect { address.disposable_domain? }.to perform_under(0.0001).sample(10).times
17
+ end
18
+ end
@@ -1,6 +1,15 @@
1
1
  $:.unshift File.expand_path("../lib",__FILE__)
2
2
  require "valid_email2"
3
3
 
4
+ # Include and configure benchmark
5
+ require 'rspec-benchmark'
6
+ RSpec.configure do |config|
7
+ config.include RSpec::Benchmark::Matchers
8
+ end
9
+ RSpec::Benchmark.configure do |config|
10
+ config.disable_gc = true
11
+ end
12
+
4
13
  class TestModel
5
14
  include ActiveModel::Validations
6
15
 
@@ -31,6 +31,10 @@ class TestUserDisallowDisposableWithWhitelist < TestModel
31
31
  validates :email, 'valid_email_2/email': { disposable_with_whitelist: true }
32
32
  end
33
33
 
34
+ class TestUserDisallowDisposableDomainWithWhitelist < TestModel
35
+ validates :email, 'valid_email_2/email': { disposable_domain_with_whitelist: true }
36
+ end
37
+
34
38
  class TestUserDisallowBlacklisted < TestModel
35
39
  validates :email, 'valid_email_2/email': { blacklist: true }
36
40
  end
@@ -39,6 +43,10 @@ class TestUserMessage < TestModel
39
43
  validates :email, 'valid_email_2/email': { message: "custom message" }
40
44
  end
41
45
 
46
+ class TestUserMultiple < TestModel
47
+ validates :email, 'valid_email_2/email': { multiple: true }
48
+ end
49
+
42
50
  describe ValidEmail2 do
43
51
 
44
52
  let(:disposable_domain) { ValidEmail2.disposable_emails.first }
@@ -83,13 +91,23 @@ describe ValidEmail2 do
83
91
  expect(user.valid?).to be_falsey
84
92
  end
85
93
 
86
- it "is invalid if the domain contains emoticons" do
87
- user = TestUser.new(email: "foo🙈@gmail.com")
88
- expect(user.valid?).to be_falsy
94
+ it "is invalid if the address contains consecutive dots" do
95
+ user = TestUser.new(email: "foo..bar@gmail.com")
96
+ expect(user.valid?).to be_falsey
97
+ end
98
+
99
+ it "is invalid if the address starts with a dot" do
100
+ user = TestUser.new(email: ".foo@bar.com")
101
+ expect(user.valid?).to be_falsey
102
+ end
103
+
104
+ it "is invalid if the local part of the address ends with a dot" do
105
+ user = TestUser.new(email: "foo.@bar.com")
106
+ expect(user.valid?).to be_falsey
89
107
  end
90
108
 
91
- it "is invalid if the domain contains .@ consecutively" do
92
- user = TestUser.new(email: "foo.@gmail.com")
109
+ it "is invalid if the email contains emoticons" do
110
+ user = TestUser.new(email: "foo🙈@gmail.com")
93
111
  expect(user.valid?).to be_falsy
94
112
  end
95
113
 
@@ -166,8 +184,18 @@ describe ValidEmail2 do
166
184
  let(:whitelist_domain) { disposable_domain }
167
185
  let(:whitelist_file_path) { "config/whitelisted_email_domains.yml" }
168
186
 
187
+ # Some of the specs below need to explictly set the whitelist var or it
188
+ # may be cached to an empty set
189
+ def set_whitelist
190
+ ValidEmail2.instance_variable_set(
191
+ :@whitelist,
192
+ ValidEmail2.send(:load_if_exists, ValidEmail2::WHITELIST_FILE)
193
+ )
194
+ end
195
+
169
196
  after do
170
197
  FileUtils.rm(whitelist_file_path, force: true)
198
+ set_whitelist
171
199
  end
172
200
 
173
201
  it "is invalid if the domain is disposable and not in the whitelist" do
@@ -177,9 +205,22 @@ describe ValidEmail2 do
177
205
 
178
206
  it "is valid if the domain is disposable but in the whitelist" do
179
207
  File.open(whitelist_file_path, "w") { |f| f.write [whitelist_domain].to_yaml }
208
+ set_whitelist
180
209
  user = TestUserDisallowDisposableWithWhitelist.new(email: "foo@#{whitelist_domain}")
210
+ expect(user.valid?).to be_truthy
211
+ end
212
+
213
+ it "is invalid if the domain is a disposable_domain and not in the whitelist" do
214
+ user = TestUserDisallowDisposableDomainWithWhitelist.new(email: "foo@#{whitelist_domain}")
181
215
  expect(user.valid?).to be_falsey
182
216
  end
217
+
218
+ it "is valid if the domain is a disposable_domain but in the whitelist" do
219
+ File.open(whitelist_file_path, "w") { |f| f.write [whitelist_domain].to_yaml }
220
+ set_whitelist
221
+ user = TestUserDisallowDisposableDomainWithWhitelist.new(email: "foo@#{whitelist_domain}")
222
+ expect(user.valid?).to be_truthy
223
+ end
183
224
  end
184
225
  end
185
226
 
@@ -244,6 +285,20 @@ describe ValidEmail2 do
244
285
  end
245
286
  end
246
287
 
288
+ describe "with multiple addresses" do
289
+ it "tests each address for it's own" do
290
+ user = TestUserMultiple.new(email: "foo@gmail.com, bar@gmail.com")
291
+ expect(user.valid?).to be_truthy
292
+ end
293
+
294
+ context 'when one address is invalid' do
295
+ it "fails for all" do
296
+ user = TestUserMultiple.new(email: "foo@gmail.com, bar@123")
297
+ expect(user.valid?).to be_falsey
298
+ end
299
+ end
300
+ end
301
+
247
302
  describe "#dotted?" do
248
303
  it "is true when address local part contains a dot delimiter ('.')" do
249
304
  email = ValidEmail2::Address.new("john.doe@gmail.com")
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "bundler", "~> 2.0"
24
24
  spec.add_development_dependency "rake", "~> 12.3.3"
25
25
  spec.add_development_dependency "rspec", "~> 3.5.0"
26
+ spec.add_development_dependency "rspec-benchmark", "~> 0.6"
26
27
  spec.add_development_dependency "pry"
27
28
  spec.add_runtime_dependency "mail", "~> 2.5"
28
29
  spec.add_runtime_dependency "activemodel", ">= 3.2"
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.2.4
4
+ version: 3.5.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-07-12 00:00:00.000000000 Z
11
+ date: 2020-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 3.5.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-benchmark
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.6'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: pry
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -120,6 +134,7 @@ files:
120
134
  - lib/valid_email2/email_validator.rb
121
135
  - lib/valid_email2/version.rb
122
136
  - pull_mailchecker_emails.rb
137
+ - spec/benchmark_spec.rb
123
138
  - spec/spec_helper.rb
124
139
  - spec/valid_email2_spec.rb
125
140
  - valid_email2.gemspec
@@ -142,11 +157,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
157
  - !ruby/object:Gem::Version
143
158
  version: '0'
144
159
  requirements: []
145
- rubygems_version: 3.1.4
160
+ rubygems_version: 3.1.2
146
161
  signing_key:
147
162
  specification_version: 4
148
163
  summary: ActiveModel validation for email. Including MX lookup and disposable email
149
164
  blacklist
150
165
  test_files:
166
+ - spec/benchmark_spec.rb
151
167
  - spec/spec_helper.rb
152
168
  - spec/valid_email2_spec.rb