validates_email_format_of 1.4.7 → 1.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.
- data/lib/validates_email_format_of.rb +84 -33
- data/test/validates_email_format_of_test.rb +42 -24
- metadata +21 -27
@@ -2,14 +2,11 @@
|
|
2
2
|
module ValidatesEmailFormatOf
|
3
3
|
require 'resolv'
|
4
4
|
|
5
|
-
VERSION = '1.
|
5
|
+
VERSION = '1.5.0'
|
6
6
|
|
7
7
|
MessageScope = defined?(ActiveModel) ? :activemodel : :activerecord
|
8
8
|
|
9
|
-
LocalPartSpecialChars =
|
10
|
-
LocalPartUnquoted = '([[:alnum:]' + LocalPartSpecialChars + ']+[\.]+)*[[:alnum:]' + LocalPartSpecialChars + '+]+'
|
11
|
-
LocalPartQuoted = '\"([[:alnum:]' + LocalPartSpecialChars + '\.]|\\\\[\x00-\xFF])*\"'
|
12
|
-
Regex = Regexp.new('\A(' + LocalPartUnquoted + '|' + LocalPartQuoted + '+)@(((\w+\-+[^_])|(\w+\.[a-z0-9-]*))*([a-z0-9\-\.]{1,63})\.[a-z]{2,6}(?:\.[a-z]{2,6})?\Z)', Regexp::EXTENDED | Regexp::IGNORECASE, 'n')
|
9
|
+
LocalPartSpecialChars = /[\!\#\$\%\&\'\*\-\/\=\?\+\-\^\_\`\{\|\}\~]/
|
13
10
|
|
14
11
|
def self.validate_email_domain(email)
|
15
12
|
domain = email.match(/\@(.+)/)[1]
|
@@ -26,58 +23,112 @@ module ValidatesEmailFormatOf
|
|
26
23
|
# * <tt>message</tt> - A custom error message (default is: "does not appear to be valid")
|
27
24
|
# * <tt>check_mx</tt> - Check for MX records (default is false)
|
28
25
|
# * <tt>mx_message</tt> - A custom error message when an MX record validation fails (default is: "is not routable.")
|
29
|
-
# * <tt>with</tt> The regex to use for validating the format of the email address (
|
26
|
+
# * <tt>with</tt> The regex to use for validating the format of the email address (deprecated)
|
30
27
|
# * <tt>local_length</tt> Maximum number of characters allowed in the local part (default is 64)
|
31
28
|
# * <tt>domain_length</tt> Maximum number of characters allowed in the domain part (default is 255)
|
32
29
|
def self.validate_email_format(email, options={})
|
33
30
|
default_options = { :message => I18n.t(:invalid_email_address, :scope => [MessageScope, :errors, :messages], :default => 'does not appear to be valid'),
|
34
31
|
:check_mx => false,
|
35
32
|
:mx_message => I18n.t(:email_address_not_routable, :scope => [MessageScope, :errors, :messages], :default => 'is not routable'),
|
36
|
-
:with => ValidatesEmailFormatOf::Regex ,
|
37
33
|
:domain_length => 255,
|
38
34
|
:local_length => 64
|
39
35
|
}
|
40
36
|
opts = options.merge(default_options) {|key, old, new| old} # merge the default options into the specified options, retaining all specified options
|
41
37
|
|
42
|
-
email.strip
|
38
|
+
email = email.strip if email
|
43
39
|
|
44
|
-
# local part max is 64 chars, domain part max is 255 chars
|
45
|
-
# TODO: should this decode escaped entities before counting?
|
46
40
|
begin
|
47
41
|
domain, local = email.reverse.split('@', 2)
|
48
42
|
rescue
|
49
43
|
return [ opts[:message] ]
|
50
44
|
end
|
51
45
|
|
52
|
-
|
53
|
-
|
46
|
+
# need local and domain parts
|
47
|
+
return [ opts[:message] ] unless local and domain
|
48
|
+
|
49
|
+
# check lengths
|
50
|
+
return [ opts[:message] ] unless domain.length <= opts[:domain_length] and local.length <= opts[:local_length]
|
51
|
+
|
52
|
+
local.reverse!
|
53
|
+
domain.reverse!
|
54
|
+
|
55
|
+
if opts.has_key?(:with) # holdover from versions <= 1.4.7
|
56
|
+
return [ opts[:message] ] unless email =~ opts[:with]
|
57
|
+
else
|
58
|
+
return [ opts[:message] ] unless self.validate_local_part_syntax(local) and self.validate_domain_part_syntax(domain)
|
54
59
|
end
|
55
60
|
|
56
|
-
if opts[:check_mx] and !
|
61
|
+
if opts[:check_mx] and !self.validate_email_domain(email)
|
57
62
|
return [ opts[:mx_message] ]
|
58
63
|
end
|
59
64
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
65
|
+
return nil # represents no validation errors
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def self.validate_local_part_syntax(local)
|
70
|
+
in_quoted_pair = false
|
71
|
+
in_quoted_string = false
|
72
|
+
|
73
|
+
(0..local.length-1).each do |i|
|
74
|
+
ord = local[i].ord
|
75
|
+
|
76
|
+
# accept anything if it's got a backslash before it
|
77
|
+
if in_quoted_pair
|
78
|
+
in_quoted_pair = false
|
79
|
+
next
|
80
|
+
end
|
81
|
+
|
82
|
+
# backslash signifies the start of a quoted pair
|
83
|
+
if ord == 92 and i < local.length - 1
|
84
|
+
return false if not in_quoted_string # must be in quoted string per http://www.rfc-editor.org/errata_search.php?rfc=3696
|
85
|
+
in_quoted_pair = true
|
86
|
+
next
|
87
|
+
end
|
88
|
+
|
89
|
+
# double quote delimits quoted strings
|
90
|
+
if ord == 34
|
91
|
+
in_quoted_string = !in_quoted_string
|
92
|
+
next
|
93
|
+
end
|
94
|
+
|
95
|
+
next if local[i] =~ /[[:alnum:]]/
|
96
|
+
next if local[i] =~ LocalPartSpecialChars
|
97
|
+
|
98
|
+
# period must be followed by something
|
99
|
+
if ord == 46
|
100
|
+
return false if i == 0 or i == local.length - 1 # can't be first or last char
|
101
|
+
next unless local[i+1].ord == 46 # can't be followed by a period
|
78
102
|
end
|
79
103
|
|
80
|
-
return
|
104
|
+
return false
|
105
|
+
end
|
106
|
+
|
107
|
+
return false if in_quoted_string # unbalanced quotes
|
108
|
+
|
109
|
+
return true
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.validate_domain_part_syntax(domain)
|
113
|
+
parts = domain.downcase.split('.', -1)
|
114
|
+
|
115
|
+
return false if parts.length <= 1 # Only one domain part
|
116
|
+
|
117
|
+
# Empty parts (double period) or invalid chars
|
118
|
+
return false if parts.any? {
|
119
|
+
|part|
|
120
|
+
part.nil? or
|
121
|
+
part.empty? or
|
122
|
+
not part =~ /\A[[:alnum:]\-]+\Z/ or
|
123
|
+
part[0] == '-' or part[-1] == '-' # hyphen at beginning or end of part
|
124
|
+
}
|
125
|
+
|
126
|
+
# ipv4
|
127
|
+
return true if parts.length == 4 and parts.all? { |part| part =~ /\A[0-9]+\Z/ and part.to_i.between?(0, 255) }
|
128
|
+
|
129
|
+
return false if parts[-1].length < 2 or not parts[-1] =~ /[a-z\-]/ # TLD is too short or does not contain a char or hyphen
|
130
|
+
|
131
|
+
return true
|
81
132
|
end
|
82
133
|
|
83
134
|
module Validations
|
@@ -98,7 +149,7 @@ module ValidatesEmailFormatOf
|
|
98
149
|
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
99
150
|
# method, proc or string should return or evaluate to a true or false value.
|
100
151
|
# * <tt>unless</tt> - See <tt>:if</tt>
|
101
|
-
def validates_email_format_of(*attr_names)
|
152
|
+
def validates_email_format_of(*attr_names)
|
102
153
|
options = { :on => :save,
|
103
154
|
:allow_nil => false,
|
104
155
|
:allow_blank => false }
|
@@ -50,6 +50,10 @@ class ValidatesEmailFormatOfTest < TEST_CASE
|
|
50
50
|
'_somename@example.com',
|
51
51
|
# apostrophes
|
52
52
|
"test'test@example.com",
|
53
|
+
# international domain names
|
54
|
+
'test@xn--bcher-kva.ch',
|
55
|
+
'test@example.xn--0zwm56d',
|
56
|
+
'test@192.192.192.1'
|
53
57
|
].each do |email|
|
54
58
|
assert_valid(email)
|
55
59
|
end
|
@@ -64,18 +68,20 @@ class ValidatesEmailFormatOfTest < TEST_CASE
|
|
64
68
|
# period can not appear twice consecutively in local part
|
65
69
|
'invali..d@example.com',
|
66
70
|
# should not allow underscores in domain names
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
'invalid@ex_mple.com',
|
72
|
+
'invalid@e..example.com',
|
73
|
+
'invalid@p-t..example.com',
|
74
|
+
'invalid@example.com.',
|
75
|
+
'invalid@example.com_',
|
76
|
+
'invalid@example.com-',
|
77
|
+
'invalid-example.com',
|
78
|
+
'invalid@example.b#r.com',
|
79
|
+
'invalid@example.c',
|
80
|
+
'invali d@example.com',
|
81
|
+
# TLD can not be only numeric
|
82
|
+
'invalid@example.123',
|
77
83
|
# unclosed quote
|
78
|
-
"\"a-17180061943-10618354-1993365053",
|
84
|
+
"\"a-17180061943-10618354-1993365053@example.com",
|
79
85
|
# too many special chars used to cause the regexp to hang
|
80
86
|
"-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++@foo",
|
81
87
|
'invalidexample.com',
|
@@ -84,27 +90,30 @@ class ValidatesEmailFormatOfTest < TEST_CASE
|
|
84
90
|
'local@sub.#domain.com',
|
85
91
|
# one at a time
|
86
92
|
"foo@example.com\nexample@gmail.com",
|
87
|
-
'invalid@example.'
|
93
|
+
'invalid@example.',
|
94
|
+
"\"foo\\\\\"\"@bar.com",
|
95
|
+
"foo@mail.com\r\nfoo@mail.com"
|
96
|
+
].each do |email|
|
88
97
|
assert_invalid(email)
|
89
98
|
end
|
90
99
|
end
|
91
|
-
|
100
|
+
|
92
101
|
# from http://www.rfc-editor.org/errata_search.php?rfc=3696
|
93
102
|
def test_should_allow_quoted_characters
|
94
|
-
['"Abc\@def"@example.com',
|
103
|
+
['"Abc\@def"@example.com',
|
95
104
|
'"Fred\ Bloggs"@example.com',
|
96
105
|
'"Joe.\\Blow"@example.com',
|
97
106
|
].each do |email|
|
98
107
|
assert_valid(email)
|
99
108
|
end
|
100
109
|
end
|
101
|
-
|
110
|
+
|
102
111
|
def test_should_required_balanced_quoted_characters
|
103
112
|
assert_valid(%!"example\\\\\\""@example.com!)
|
104
113
|
assert_valid(%!"example\\\\"@example.com!)
|
105
114
|
assert_invalid(%!"example\\\\""example.com!)
|
106
115
|
end
|
107
|
-
|
116
|
+
|
108
117
|
# from http://tools.ietf.org/html/rfc3696, page 5
|
109
118
|
# corrected in http://www.rfc-editor.org/errata_search.php?rfc=3696
|
110
119
|
def test_should_not_allow_escaped_characters_without_quotes
|
@@ -123,21 +132,25 @@ class ValidatesEmailFormatOfTest < TEST_CASE
|
|
123
132
|
assert_invalid(email)
|
124
133
|
end
|
125
134
|
end
|
126
|
-
|
135
|
+
|
127
136
|
def test_overriding_length_checks
|
128
137
|
assert_not_nil ValidatesEmailFormatOf::validate_email_format('valid@example.com', :local_length => 1)
|
129
138
|
assert_not_nil ValidatesEmailFormatOf::validate_email_format('valid@example.com', :domain_length => 1)
|
130
139
|
end
|
131
140
|
|
141
|
+
def test_validating_with_custom_regexp
|
142
|
+
assert_nil ValidatesEmailFormatOf::validate_email_format('012345@789', :with => /[0-9]+\@[0-9]+/)
|
143
|
+
end
|
144
|
+
|
132
145
|
def test_should_respect_validate_on_option
|
133
146
|
p = create_person(:email => @valid_email)
|
134
147
|
save_passes(p)
|
135
|
-
|
148
|
+
|
136
149
|
# we only asked to validate on :create so this should fail
|
137
150
|
assert p.update_attributes(:email => @invalid_email)
|
138
151
|
assert_equal @invalid_email, p.email
|
139
152
|
end
|
140
|
-
|
153
|
+
|
141
154
|
def test_should_allow_custom_error_message
|
142
155
|
p = create_person(:email => @invalid_email)
|
143
156
|
save_fails(p)
|
@@ -151,7 +164,7 @@ class ValidatesEmailFormatOfTest < TEST_CASE
|
|
151
164
|
def test_should_allow_nil
|
152
165
|
p = create_person(:email => nil)
|
153
166
|
save_passes(p)
|
154
|
-
|
167
|
+
|
155
168
|
p = PersonForbidNil.new(:email => nil)
|
156
169
|
save_fails(p)
|
157
170
|
end
|
@@ -170,7 +183,7 @@ class ValidatesEmailFormatOfTest < TEST_CASE
|
|
170
183
|
pmx = MxRecord.new(:email => 'test@code.dunae.ca')
|
171
184
|
save_passes(pmx)
|
172
185
|
end
|
173
|
-
|
186
|
+
|
174
187
|
def test_shorthand
|
175
188
|
if ActiveRecord::VERSION::MAJOR >= 3
|
176
189
|
s = Shorthand.new(:email => 'invalid')
|
@@ -184,15 +197,20 @@ class ValidatesEmailFormatOfTest < TEST_CASE
|
|
184
197
|
end
|
185
198
|
end
|
186
199
|
|
200
|
+
def test_frozen_string
|
201
|
+
assert_valid(" #{@valid_email} ".freeze)
|
202
|
+
assert_invalid(" #{@invalid_email} ".freeze)
|
203
|
+
end
|
204
|
+
|
187
205
|
protected
|
188
206
|
def create_person(params)
|
189
207
|
Person.new(params)
|
190
208
|
end
|
191
|
-
|
209
|
+
|
192
210
|
def assert_valid(email)
|
193
211
|
assert_nil ValidatesEmailFormatOf::validate_email_format(email)
|
194
212
|
end
|
195
|
-
|
213
|
+
|
196
214
|
def assert_invalid(email)
|
197
215
|
err = ValidatesEmailFormatOf::validate_email_format(email)
|
198
216
|
assert_equal 1, err.size
|
@@ -207,7 +225,7 @@ class ValidatesEmailFormatOfTest < TEST_CASE
|
|
207
225
|
assert_nil p.errors.on(:email)
|
208
226
|
end
|
209
227
|
end
|
210
|
-
|
228
|
+
|
211
229
|
def save_fails(p, email = '')
|
212
230
|
assert !p.valid?, " #{email} should fail"
|
213
231
|
assert !p.save
|
metadata
CHANGED
@@ -1,28 +1,24 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: validates_email_format_of
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.5.0
|
4
5
|
prerelease:
|
5
|
-
version: 1.4.7
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Alex Dunae
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
date: 2011-05-13 00:00:00 Z
|
12
|
+
date: 2011-09-07 00:00:00.000000000Z
|
14
13
|
dependencies: []
|
15
|
-
|
16
14
|
description: Validate e-mail addresses against RFC 2822 and RFC 3696.
|
17
15
|
email: code@dunae.ca
|
18
16
|
executables: []
|
19
|
-
|
20
17
|
extensions: []
|
21
|
-
|
22
|
-
extra_rdoc_files:
|
18
|
+
extra_rdoc_files:
|
23
19
|
- README.rdoc
|
24
20
|
- MIT-LICENSE
|
25
|
-
files:
|
21
|
+
files:
|
26
22
|
- MIT-LICENSE
|
27
23
|
- init.rb
|
28
24
|
- rakefile.rb
|
@@ -36,33 +32,31 @@ files:
|
|
36
32
|
- test/fixtures/people.yml
|
37
33
|
homepage: https://github.com/alexdunae/validates_email_format_of
|
38
34
|
licenses: []
|
39
|
-
|
40
35
|
post_install_message:
|
41
|
-
rdoc_options:
|
36
|
+
rdoc_options:
|
42
37
|
- --title
|
43
38
|
- validates_email_format_of
|
44
|
-
require_paths:
|
39
|
+
require_paths:
|
45
40
|
- lib
|
46
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
42
|
none: false
|
48
|
-
requirements:
|
49
|
-
- -
|
50
|
-
- !ruby/object:Gem::Version
|
51
|
-
version:
|
52
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
48
|
none: false
|
54
|
-
requirements:
|
55
|
-
- -
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
version:
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
58
53
|
requirements: []
|
59
|
-
|
60
54
|
rubyforge_project:
|
61
|
-
rubygems_version: 1.8.
|
55
|
+
rubygems_version: 1.8.5
|
62
56
|
signing_key:
|
63
57
|
specification_version: 3
|
64
58
|
summary: Validate e-mail addresses against RFC 2822 and RFC 3696.
|
65
|
-
test_files:
|
59
|
+
test_files:
|
66
60
|
- test/fixtures/person.rb
|
67
61
|
- test/schema.rb
|
68
62
|
- test/test_helper.rb
|