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.
- checksums.yaml +4 -4
- data/.travis.yml +3 -5
- data/CHANGELOG.md +21 -0
- data/README.md +16 -4
- data/config/disposable_email_domains.txt +160 -2
- data/lib/valid_email2/address.rb +39 -19
- data/lib/valid_email2/email_validator.rb +22 -3
- data/lib/valid_email2/version.rb +3 -1
- data/pull_mailchecker_emails.rb +1 -1
- data/spec/valid_email2_spec.rb +68 -0
- metadata +3 -3
data/lib/valid_email2/address.rb
CHANGED
|
@@ -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 = '+'
|
|
11
|
-
DOT_DELIMITER = '.'
|
|
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 =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
data/lib/valid_email2/version.rb
CHANGED
data/pull_mailchecker_emails.rb
CHANGED
|
@@ -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") }
|
data/spec/valid_email2_spec.rb
CHANGED
|
@@ -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.
|
|
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:
|
|
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.
|
|
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
|