email_validator 2.2.0 → 2.2.1

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: 0bd676f012ed7b109082842ed227bc1881aec6ac24f0e1ed4be24772b206b14f
4
- data.tar.gz: 6c2f92e0a494f751b690743b15d4d97fd8dd1f9213b8cb2640d6f8f729fca51f
3
+ metadata.gz: faae1443109b7d1c95aca6addd9d2be7c3cfaf58a771e003d80a1ea75dd0993f
4
+ data.tar.gz: c1094ac3f9d4887193eb2145c62f46cefe1d4ed0a734a6d7add1cf68222de1dd
5
5
  SHA512:
6
- metadata.gz: 8dd2ab72b087727acec6c7509d3ffa2a51e35074b99113bc8b105c16fc62e9c3288712516e3fc0f15be125d83e0d7db50d368da269672d7ecdddec9ba21f925e
7
- data.tar.gz: e35ab67f7e7337b1bb1cd04d27d0f47dee128b093cdd6015fdade7967b404441d7abc522dcec18bfda8e682e7ae5418a24ba3a400afa9292b6585e796b644c27
6
+ metadata.gz: e99e6549ebfa01f6fc43b0a8f8bfba80ce0037700f2672be89bb0936b71f929bde127d667fc634a38bc6ea9e26d08a61393c5f261db3581362f8497001305790
7
+ data.tar.gz: 7e74593c1bb75c71d8864c1db31d9a415ec6fbc4d56a7f190363224f3906b884f0bd39af9208969112f12978b733ae09271e3a94b42ed1fc84b88553cd71df9d
@@ -5,6 +5,13 @@ This file is used to list changes made in `email_validator`.
5
5
  All notable changes to this project will be documented in this file.
6
6
  This project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
+ ## 2.2.1 (2020-12-10)
9
+
10
+ * [karlwilbur] - Modify regexp to:
11
+ - allow numeric-only hosts [#68]
12
+ - allow mailbox-only addresses in `:rfc` mode
13
+ - enforce the 255-char domain limit (not in `:loose` mode unless using `:domain`)
14
+
8
15
  ## 2.2.0 (2020-12-09)
9
16
 
10
17
  * [karlwilbur] - Rename `:strict` -> `:rfc`; `:moderate` -> `:strict`
data/README.md CHANGED
@@ -45,7 +45,7 @@ Add the following to your model:
45
45
  validates :my_email_attribute, email: true
46
46
  ```
47
47
 
48
- You may wish to allow domains without a FDQN, like `user@somehost`. While this
48
+ You may wish to allow domains without a FQDN, like `user@somehost`. While this
49
49
  is technically a valid address, it is uncommon to consider such address valid.
50
50
  We will consider them valid by default with the `:loose` checking. Disallowed
51
51
  by setting `require_fqdn: true` or by enabling `:strict` checking:
@@ -30,15 +30,31 @@ class EmailValidator < ActiveModel::EachValidator
30
30
  # https://tools.ietf.org/html/rfc5321 : 4.1.2. Command Argument Syntax
31
31
  def regexp(options = {})
32
32
  options = parse_options(options)
33
- if options[:mode] == :loose
34
- return /\A[^\s]+@[^\s]+\z/ if options[:domain].nil?
35
- return /\A[^\s]+@#{domain_pattern(options)}\z/
33
+ case options[:mode]
34
+ when :loose
35
+ loose_regexp(options)
36
+ when :rfc
37
+ rfc_regexp(options)
38
+ else
39
+ strict_regexp(options)
36
40
  end
37
- /\A(?>#{local_part_pattern})@#{domain_pattern(options)}\z/i
38
41
  end
39
42
 
40
43
  protected
41
44
 
45
+ def loose_regexp(options = {})
46
+ return /\A[^\s]+@[^\s]+\z/ if options[:domain].nil?
47
+ /\A[^\s]+@#{domain_part_pattern(options)}\z/
48
+ end
49
+
50
+ def strict_regexp(options = {})
51
+ /\A(?>#{local_part_pattern})@#{domain_part_pattern(options)}\z/i
52
+ end
53
+
54
+ def rfc_regexp(options = {})
55
+ /\A(?>#{local_part_pattern})(?:@#{domain_part_pattern(options)})?\z/i
56
+ end
57
+
42
58
  def alpha
43
59
  '[[:alpha:]]'
44
60
  end
@@ -64,8 +80,32 @@ class EmailValidator < ActiveModel::EachValidator
64
80
  "\\[(?:#{ipv4}|#{ipv6})\\]"
65
81
  end
66
82
 
67
- def label_pattern
68
- "#{alpha}(?:#{alnumhy}{,62}#{alnum})?"
83
+ def host_label_pattern
84
+ "#{alnum}(?:#{alnumhy}{,61}#{alnum})?"
85
+ end
86
+
87
+ # splitting this up into separate regex pattern for performance; let's not
88
+ # try the "contains" pattern unless we have to
89
+ def domain_label_pattern
90
+ '(?=[^.]{1,63}(?:\.|$))' \
91
+ '(?:' \
92
+ "#{alpha}" \
93
+ "|#{domain_label_starts_with_a_letter_pattern}" \
94
+ "|#{domain_label_ends_with_a_letter_pattern}" \
95
+ "|#{domain_label_contains_a_letter_pattern}" \
96
+ ')'
97
+ end
98
+
99
+ def domain_label_starts_with_a_letter_pattern
100
+ "#{alpha}#{alnumhy}{,61}#{alnum}"
101
+ end
102
+
103
+ def domain_label_ends_with_a_letter_pattern
104
+ "#{alnum}#{alnumhy}{,61}#{alpha}"
105
+ end
106
+
107
+ def domain_label_contains_a_letter_pattern
108
+ "(?:[[:digit:]])(?:[[:digit:]]|-)*#{alpha}#{alnumhy}*#{alnum}"
69
109
  end
70
110
 
71
111
  def atom_char
@@ -79,10 +119,14 @@ class EmailValidator < ActiveModel::EachValidator
79
119
  "#{atom_char}(?:\\.?#{atom_char}){,63}"
80
120
  end
81
121
 
82
- def domain_pattern(options)
122
+ def domain_part_pattern(options)
83
123
  return options[:domain].sub(/\./, '\.') if options[:domain].present?
84
- return "(?:#{label_pattern}\\.)+#{label_pattern}" if options[:require_fqdn]
85
- "(?:#{address_literal}|(?:#{label_pattern}\\.)*#{label_pattern})"
124
+ return fqdn_pattern if options[:require_fqdn]
125
+ "(?=.{1,255}$)(?:#{address_literal}|(?:#{host_label_pattern}\\.)*#{domain_label_pattern})"
126
+ end
127
+
128
+ def fqdn_pattern
129
+ "(?=.{1,255}$)(?:#{host_label_pattern}\\.)*#{domain_label_pattern}\\.#{domain_label_pattern}"
86
130
  end
87
131
 
88
132
  private
@@ -140,7 +140,13 @@ RSpec.describe EmailValidator do
140
140
  'someuser@somehost.somedomain',
141
141
  'mixed-1234-in-{+^}-local@sld.dev',
142
142
  'partially."quoted"@sld.com',
143
- 'areallylongnameaasdfasdfasdfasdf@asdfasdfasdfasdfasdf.ab.cd.ef.gh.co.ca'
143
+ 'areallylongnameaasdfasdfasdfasdf@asdfasdfasdfasdfasdf.ab.cd.ef.gh.co.ca',
144
+ 'john.doe@2020.example.com',
145
+ 'john.doe@2a.com',
146
+ 'john.doe@a2.com',
147
+ 'john.doe@2020.a-z.com',
148
+ 'john.doe@2020.a2z.com',
149
+ 'john.doe@2020.12345a6789.com'
144
150
  ]).flatten.each do |email|
145
151
  context 'when using defaults' do
146
152
  it "'#{email}' should be valid" do
@@ -260,6 +266,132 @@ RSpec.describe EmailValidator do
260
266
  end
261
267
  end
262
268
 
269
+ context 'when given the numeric domain' do
270
+ [
271
+ 'only-numbers-in-domain-label@sub.123.custom'
272
+ ].each do |email|
273
+ context 'when using defaults' do
274
+ it "'#{email}' should be valid" do
275
+ expect(DefaultUser.new(:email => email)).to be_valid
276
+ end
277
+
278
+ it "'#{email}' should be valid using EmailValidator.valid?" do
279
+ expect(described_class).to be_valid(email)
280
+ end
281
+
282
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
283
+ expect(described_class).not_to be_invalid(email)
284
+ end
285
+
286
+ it "'#{email}' should match the regexp" do
287
+ expect(!!(email.strip =~ described_class.regexp)).to be(true)
288
+ end
289
+ end
290
+
291
+ context 'when in `:strict` mode' do
292
+ it "'#{email}' should not be valid" do
293
+ expect(StrictUser.new(:email => email)).not_to be_valid
294
+ end
295
+
296
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
297
+ expect(described_class).not_to be_valid(email, :mode => :strict)
298
+ end
299
+
300
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
301
+ expect(described_class).to be_invalid(email, :mode => :strict)
302
+ end
303
+
304
+ it "'#{email}' should not match the regexp" do
305
+ expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
306
+ end
307
+ end
308
+
309
+ context 'when in `:rfc` mode' do
310
+ it "'#{email}' should be valid" do
311
+ expect(RfcUser.new(:email => email)).to be_valid
312
+ end
313
+
314
+ it "'#{email}' should be valid using EmailValidator.valid?" do
315
+ expect(described_class).to be_valid(email, :mode => :rfc)
316
+ end
317
+
318
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
319
+ expect(described_class).not_to be_invalid(email, :mode => :rfc)
320
+ end
321
+
322
+ it "'#{email}' should match the regexp" do
323
+ expect(!!(email.strip =~ described_class.regexp(:mode => :rfc))).to be(true)
324
+ end
325
+ end
326
+ end
327
+ end
328
+
329
+ context 'when given the valid mailbox-only email' do
330
+ valid_includable.map { |k, v| [
331
+ "user-#{v}-#{k}-name"
332
+ ]}.concat(valid_beginable.map { |k, v| [
333
+ "#{v}-start-with-#{k}-user"
334
+ ]}).concat(valid_endable.map { |k, v| [
335
+ "end-with-#{k}-#{v}"
336
+ ]}).concat([
337
+ 'user'
338
+ ]).flatten.each do |email|
339
+ context 'when using defaults' do
340
+ it "'#{email}' should not be valid" do
341
+ expect(DefaultUser.new(:email => email)).not_to be_valid
342
+ end
343
+
344
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
345
+ expect(described_class).not_to be_valid(email)
346
+ end
347
+
348
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
349
+ expect(described_class).to be_invalid(email)
350
+ end
351
+
352
+ it "'#{email}' should not match the regexp" do
353
+ expect(!!(email.strip =~ described_class.regexp)).to be(false)
354
+ end
355
+ end
356
+
357
+ context 'when in `:strict` mode' do
358
+ it "'#{email}' should not be valid" do
359
+ expect(StrictUser.new(:email => email)).not_to be_valid
360
+ end
361
+
362
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
363
+ expect(described_class).not_to be_valid(email, :mode => :strict)
364
+ end
365
+
366
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
367
+ expect(described_class).to be_invalid(email, :mode => :strict)
368
+ end
369
+
370
+ it "'#{email}' should not match the regexp" do
371
+ expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
372
+ end
373
+ end
374
+
375
+ context 'when in `:rfc` mode' do
376
+ it "'#{email}' should be valid" do
377
+ expect(RfcUser.new(:email => email)).to be_valid
378
+ end
379
+
380
+ it "'#{email}' should be valid using EmailValidator.valid?" do
381
+ expect(described_class).to be_valid(email, :mode => :rfc)
382
+ end
383
+
384
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
385
+ expect(described_class).not_to be_invalid(email, :mode => :rfc)
386
+ end
387
+
388
+ it "'#{email}' should match the regexp" do
389
+ expect(!!(email.strip =~ described_class.regexp(:mode => :rfc))).to be(true)
390
+ end
391
+ end
392
+ end
393
+ end
394
+
263
395
  context 'when given the valid IP address email' do
264
396
  [
265
397
  'bracketed-IP@[127.0.0.1]',
@@ -334,8 +466,6 @@ RSpec.describe EmailValidator do
334
466
  'test@example.com@example.com',
335
467
  'missing-sld@.com',
336
468
  'missing-tld@sld.',
337
- 'only-numbers-in-domain-label@sub.123.com',
338
- 'only-numbers-in-domain-label@123.example.com',
339
469
  'unbracketed-IPv6@abcd:ef01:1234:5678:9abc:def0:1234:5678',
340
470
  'unbracketed-and-labled-IPv6@IPv6:abcd:ef01:1234:5678:9abc:def0:1234:5678',
341
471
  'bracketed-and-unlabeled-IPv6@[abcd:ef01:1234:5678:9abc:def0:1234:5678]',
@@ -347,6 +477,7 @@ RSpec.describe EmailValidator do
347
477
  'domain-beginning-with-dash@-example.com',
348
478
  'domain-ending-with-dash@example-.com',
349
479
  'the-local-part-is-invalid-if-it-is-longer-than-sixty-four-characters@sld.dev',
480
+ "domain-too-long@t#{".#{'o' * 63}" * 5}.long",
350
481
  "user@example.com<script>alert('hello')</script>"
351
482
  ]).flatten.each do |email|
352
483
  context 'when using defaults' do
@@ -504,8 +635,7 @@ RSpec.describe EmailValidator do
504
635
  '@bar.com',
505
636
  'test@',
506
637
  '@missing-local.dev',
507
- ' ',
508
- 'missing-at-sign.dev'
638
+ ' '
509
639
  ].each do |email|
510
640
  context 'when using defaults' do
511
641
  it "'#{email}' should not be valid" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: email_validator
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Alexander
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-12-09 00:00:00.000000000 Z
12
+ date: 2020-12-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel