valid_email2 3.2.4 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
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