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