email_address 0.0.3 → 0.1.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 +10 -0
- data/Gemfile +0 -1
- data/README.md +451 -197
- data/Rakefile +4 -9
- data/email_address.gemspec +9 -5
- data/lib/email_address.rb +55 -24
- data/lib/email_address/active_record_validator.rb +5 -5
- data/lib/email_address/address.rb +152 -72
- data/lib/email_address/canonical_email_address_type.rb +46 -0
- data/lib/email_address/config.rb +148 -64
- data/lib/email_address/email_address_type.rb +15 -31
- data/lib/email_address/exchanger.rb +31 -34
- data/lib/email_address/host.rb +327 -51
- data/lib/email_address/local.rb +304 -52
- data/lib/email_address/version.rb +1 -1
- data/test/activerecord/test_ar.rb +22 -0
- data/test/activerecord/user.rb +71 -0
- data/test/email_address/test_address.rb +53 -27
- data/test/email_address/test_config.rb +23 -8
- data/test/email_address/test_exchanger.rb +22 -10
- data/test/email_address/test_host.rb +47 -6
- data/test/email_address/test_local.rb +80 -16
- data/test/test_email_address.rb +38 -4
- data/test/test_helper.rb +7 -5
- metadata +68 -34
- data/lib/email_address/domain_matcher.rb +0 -98
- data/lib/email_address/domain_parser.rb +0 -69
- data/lib/email_address/matcher.rb +0 -119
- data/lib/email_address/validator.rb +0 -141
- data/test/email_address/test_domain_matcher.rb +0 -21
- data/test/email_address/test_domain_parser.rb +0 -29
- data/test/email_address/test_matcher.rb +0 -44
- data/test/email_address/test_validator.rb +0 -16
data/Rakefile
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
|
3
|
-
task :default do
|
4
|
-
sh "ruby test/email_address/*"
|
5
|
-
end
|
6
3
|
require "bundler/gem_tasks"
|
7
4
|
require "bundler/setup"
|
8
5
|
require 'rake/testtask'
|
@@ -10,14 +7,12 @@ require 'rake/testtask'
|
|
10
7
|
task :default => :test
|
11
8
|
|
12
9
|
desc "Run the Test Suite, toot suite"
|
13
|
-
|
14
|
-
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << "test"
|
12
|
+
t.pattern = "test/**/test_*.rb"
|
15
13
|
end
|
16
14
|
|
17
15
|
desc "Open and IRB Console with the gem loaded"
|
18
16
|
task :console do
|
19
|
-
sh "bundle exec irb -Ilib -I . -r email_address"
|
20
|
-
#require 'irb'
|
21
|
-
#ARGV.clear
|
22
|
-
#IRB.start
|
17
|
+
sh "bundle exec irb -Ilib -I . -r active_record -r email_address"
|
23
18
|
end
|
data/email_address.gemspec
CHANGED
@@ -8,9 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = EmailAddress::VERSION
|
9
9
|
spec.authors = ["Allen Fair"]
|
10
10
|
spec.email = ["allen.fair@gmail.com"]
|
11
|
-
spec.description = %q{The EmailAddress
|
12
|
-
|
13
|
-
spec.summary = %q{EmailAddress checks on validates an acceptable set of email addresses.}
|
11
|
+
spec.description = %q{The EmailAddress Gem to work with and validate email addresses.}
|
12
|
+
spec.summary = %q{This gem provides a ruby language library for working with and validating email addresses. By default, it validates against conventional usage, the format preferred for user email addresses. It can be configured to validate against RFC “Standard” formats, common email service provider formats, and perform DNS validation.}
|
14
13
|
spec.homepage = "https://github.com/afair/email_address"
|
15
14
|
spec.license = "MIT"
|
16
15
|
|
@@ -19,9 +18,14 @@ validator.}
|
|
19
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
19
|
spec.require_paths = ["lib"]
|
21
20
|
|
22
|
-
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
-
spec.add_development_dependency "activemodel", "~> 4.2"
|
24
21
|
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "minitest", "~> 5.8.3"
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "activerecord", "~> 5.0.0.beta1" if RUBY_PLATFORM != 'java'
|
25
|
+
spec.add_development_dependency "activerecord", "~> 4.2.5" if RUBY_PLATFORM == 'java'
|
26
|
+
spec.add_development_dependency "sqlite3" if RUBY_PLATFORM != 'java'
|
27
|
+
spec.add_development_dependency "activerecord-jdbcsqlite3-adapter" if RUBY_PLATFORM == 'java'
|
28
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
25
29
|
spec.add_dependency "simpleidn"
|
26
30
|
spec.add_dependency "netaddr"
|
27
31
|
end
|
data/lib/email_address.rb
CHANGED
@@ -1,42 +1,73 @@
|
|
1
|
-
require "email_address/address"
|
2
|
-
require "email_address/config"
|
3
|
-
require "email_address/domain_matcher"
|
4
|
-
require "email_address/domain_parser"
|
5
|
-
require "email_address/exchanger"
|
6
|
-
require "email_address/host"
|
7
|
-
require "email_address/local"
|
8
|
-
require "email_address/matcher"
|
9
|
-
require "email_address/validator"
|
10
|
-
require "email_address/version"
|
11
|
-
require "email_address/active_record_validator" if defined?(ActiveModel)
|
12
1
|
|
13
2
|
module EmailAddress
|
14
3
|
|
4
|
+
require "email_address/config"
|
5
|
+
require "email_address/exchanger"
|
6
|
+
require "email_address/host"
|
7
|
+
require "email_address/local"
|
8
|
+
require "email_address/address"
|
9
|
+
require "email_address/version"
|
10
|
+
require "email_address/active_record_validator" if defined?(ActiveModel)
|
11
|
+
if defined?(ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5
|
12
|
+
require "email_address/email_address_type"
|
13
|
+
require "email_address/canonical_email_address_type"
|
14
|
+
end
|
15
|
+
|
15
16
|
# Creates an instance of this email address.
|
16
17
|
# This is a short-cut to Email::Address::Address.new
|
17
|
-
def self.new(email_address)
|
18
|
-
EmailAddress::Address.new(email_address)
|
18
|
+
def self.new(email_address, config={})
|
19
|
+
EmailAddress::Address.new(email_address, config)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Given an email address, this returns true if the email validates, false otherwise
|
23
|
+
def self.valid?(email_address, config={})
|
24
|
+
self.new(email_address, config).valid?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Given an email address, this returns nil if the email validates,
|
28
|
+
# or a string with a small error message otherwise
|
29
|
+
def self.error(email_address, config={})
|
30
|
+
self.new(email_address, config).error
|
19
31
|
end
|
20
32
|
|
21
|
-
#
|
22
|
-
def self.
|
23
|
-
|
33
|
+
# Shortcut to normalize the given email address in the given format
|
34
|
+
def self.normal(email_address, config={})
|
35
|
+
EmailAddress::Address.new(email_address, config).to_s
|
24
36
|
end
|
25
37
|
|
26
38
|
# Shortcut to normalize the given email address
|
27
|
-
def self.
|
28
|
-
EmailAddress::Address.new(email_address).
|
39
|
+
def self.redact(email_address, config={})
|
40
|
+
EmailAddress::Address.new(email_address, config).redact
|
41
|
+
end
|
42
|
+
|
43
|
+
# Shortcut to munge the given email address for web publishing
|
44
|
+
# returns ma_____@do_____.com
|
45
|
+
def self.munge(email_address, config={})
|
46
|
+
EmailAddress::Address.new(email_address, config).munge
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.new_redacted(email_address, config={})
|
50
|
+
EmailAddress::Address.new(EmailAddress::Address.new(email_address, config).redact)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the Canonical form of the email address. This form is what should
|
54
|
+
# be considered unique for an email account, lower case, and no address tags.
|
55
|
+
def self.canonical(email_address, config={})
|
56
|
+
EmailAddress::Address.new(email_address, config).canonical
|
29
57
|
end
|
30
58
|
|
31
|
-
def self.
|
32
|
-
EmailAddress::Address.new(EmailAddress::Address.new(email_address).
|
59
|
+
def self.new_canonical(email_address, config={})
|
60
|
+
EmailAddress::Address.new(EmailAddress::Address.new(email_address, config).canonical, config)
|
33
61
|
end
|
34
62
|
|
35
|
-
|
36
|
-
|
63
|
+
# Returns the Reference form of the email address, defined as the MD5
|
64
|
+
# digest of the Canonical form.
|
65
|
+
def self.reference(email_address, config={})
|
66
|
+
EmailAddress::Address.new(email_address, config).reference
|
37
67
|
end
|
38
68
|
|
39
|
-
|
40
|
-
|
69
|
+
# Does the email address match any of the given rules
|
70
|
+
def self.matches?(email_address, rules, config={})
|
71
|
+
EmailAddress::Address.new(email_address, config).matches?(rules)
|
41
72
|
end
|
42
73
|
end
|
@@ -8,10 +8,10 @@ module EmailAddress
|
|
8
8
|
# validates_with EmailAddress::ActiveRecordValidator, field: :name
|
9
9
|
#
|
10
10
|
# Options:
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
11
|
+
# * field: email,
|
12
|
+
# * fields: [:email1, :email2]
|
13
|
+
#
|
14
|
+
# Default field: :email or :email_address (first found)
|
15
15
|
#
|
16
16
|
class ActiveRecordValidator < ActiveModel::Validator
|
17
17
|
|
@@ -23,7 +23,7 @@ module EmailAddress
|
|
23
23
|
if @opt[:fields]
|
24
24
|
@opt[:fields].each {|f| validate_email(r, f) }
|
25
25
|
elsif @opt[:field]
|
26
|
-
validate_email(r, opt[:field])
|
26
|
+
validate_email(r, @opt[:field])
|
27
27
|
elsif r.respond_to? :email
|
28
28
|
validate_email(r, :email)
|
29
29
|
elsif r.respond_to? :email_address
|
@@ -2,116 +2,162 @@ require 'digest/sha1'
|
|
2
2
|
require 'digest/md5'
|
3
3
|
|
4
4
|
module EmailAddress
|
5
|
+
# Implements the Email Address container, which hold the Local
|
6
|
+
# (EmailAddress::Local) and Host (Email::AddressHost) parts.
|
5
7
|
class Address
|
6
8
|
include Comparable
|
9
|
+
attr_accessor :original, :local, :host, :config, :error
|
10
|
+
|
11
|
+
CONVENTIONAL_REGEX = /\A#{::EmailAddress::Local::CONVENTIONAL_MAILBOX_WITHIN}
|
12
|
+
@#{::EmailAddress::Host::DNS_HOST_REGEX}\z/x
|
13
|
+
STANDARD_REGEX = /\A#{::EmailAddress::Local::STANDARD_LOCAL_WITHIN}
|
14
|
+
@#{::EmailAddress::Host::DNS_HOST_REGEX}\z/x
|
7
15
|
|
8
16
|
# Given an email address of the form "local@hostname", this sets up the
|
9
17
|
# instance, and initializes the address to the "normalized" format of the
|
10
18
|
# address. The original string is available in the #original method.
|
11
|
-
def initialize(email_address)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@host
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
def initialize(email_address, config={})
|
20
|
+
email_address.strip! if email_address
|
21
|
+
@original = email_address
|
22
|
+
email_address||= ""
|
23
|
+
if lh = email_address.match(/(.+)@(.+)/)
|
24
|
+
(_, local, host) = lh.to_a
|
25
|
+
else
|
26
|
+
(local, host) = [email_address, '']
|
27
|
+
end
|
28
|
+
@host = EmailAddress::Host.new(host, config)
|
29
|
+
@config = @host.config
|
30
|
+
@local = EmailAddress::Local.new(local, @config)
|
31
|
+
end
|
32
|
+
|
33
|
+
############################################################################
|
34
|
+
# Local Part (left of @) access
|
35
|
+
# * local: Access full local part instance
|
36
|
+
# * left: everything on the left of @
|
37
|
+
# * mailbox: parsed mailbox or email account name
|
38
|
+
# * tag: address tag (mailbox+tag)
|
39
|
+
############################################################################
|
28
40
|
|
29
41
|
# Everything to the left of the @ in the address, called the local part.
|
30
42
|
def left
|
31
|
-
local.to_s
|
43
|
+
self.local.to_s
|
32
44
|
end
|
33
45
|
|
34
46
|
# Returns the mailbox portion of the local port, with no tags. Usually, this
|
35
47
|
# can be considered the user account or role account names. Some systems
|
36
48
|
# employ dynamic email addresses which don't have the same meaning.
|
37
49
|
def mailbox
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
# Returns the host name, the part to the right of the @ sign.
|
42
|
-
def host_name
|
43
|
-
@host.host_name
|
50
|
+
self.local.mailbox
|
44
51
|
end
|
45
|
-
alias :right :host_name
|
46
52
|
|
47
53
|
# Returns the tag part of the local address, or nil if not given.
|
48
54
|
def tag
|
49
|
-
|
55
|
+
self.local.tag
|
50
56
|
end
|
51
57
|
|
52
58
|
# Retuns any comments parsed from the local part of the email address.
|
53
59
|
# This is retained for inspection after construction, even if it is
|
54
60
|
# removed from the normalized email address.
|
55
61
|
def comment
|
56
|
-
|
62
|
+
self.local.comment
|
57
63
|
end
|
58
64
|
|
65
|
+
############################################################################
|
66
|
+
# Host Part (right of @) access
|
67
|
+
# * host: Access full local part instance (alias: right)
|
68
|
+
# * hostname: everything on the right of @
|
69
|
+
# * provider: determined email service provider
|
70
|
+
############################################################################
|
71
|
+
|
72
|
+
# Returns the host name, the part to the right of the @ sign.
|
73
|
+
def host_name
|
74
|
+
@host.host_name
|
75
|
+
end
|
76
|
+
alias :right :host_name
|
77
|
+
alias :hostname :host_name
|
78
|
+
|
59
79
|
# Returns the ESP (Email Service Provider) or ISP name derived
|
60
80
|
# using the provider configuration rules.
|
61
81
|
def provider
|
62
82
|
@host.provider
|
63
83
|
end
|
64
84
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
85
|
+
############################################################################
|
86
|
+
# Address methods
|
87
|
+
############################################################################
|
69
88
|
|
70
|
-
#
|
71
|
-
def
|
72
|
-
|
89
|
+
# Returns the string representation of the normalized email address.
|
90
|
+
def normal
|
91
|
+
if !@original
|
92
|
+
@original
|
93
|
+
elsif self.local.to_s.size == 0
|
94
|
+
""
|
95
|
+
elsif self.host.to_s.size == 0
|
96
|
+
self.local.to_s
|
97
|
+
else
|
98
|
+
"#{self.local.to_s}@#{self.host.to_s}"
|
99
|
+
end
|
73
100
|
end
|
101
|
+
alias :to_s :normal
|
74
102
|
|
75
|
-
|
76
|
-
|
77
|
-
# removes spaces and comments, but includes any tags.
|
78
|
-
def normal
|
79
|
-
[@local.normalize, @host.normalize].join('@')
|
103
|
+
def inspect
|
104
|
+
"#<EmailAddress::Address:0x#{self.object_id.to_s(16)} address=\"#{self.to_s}\">"
|
80
105
|
end
|
81
|
-
alias :normalize :normal
|
82
106
|
|
83
107
|
# Returns the canonical email address according to the provider
|
84
108
|
# uniqueness rules. Usually, this downcases the address, removes
|
85
109
|
# spaves and comments and tags, and any extraneous part of the address
|
86
110
|
# not considered a unique account by the provider.
|
87
111
|
def canonical
|
88
|
-
[
|
112
|
+
[self.local.canonical, @host.canonical].join('@')
|
113
|
+
end
|
114
|
+
|
115
|
+
# True if the given address is already in it's canonical form.
|
116
|
+
def canonical?
|
117
|
+
self.canonical == self.to_s
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns the redacted form of the address
|
121
|
+
# This format is defined by this libaray, and may change as usage increases.
|
122
|
+
def redact
|
123
|
+
return self.to_s if self.local.redacted?
|
124
|
+
%Q({#{self.sha1}}@#{self.host.to_s})
|
125
|
+
end
|
126
|
+
|
127
|
+
# True if the address is already in the redacted state.
|
128
|
+
def redacted?
|
129
|
+
self.local.redacted?
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the munged form of the address, by default "mailbox@domain.tld"
|
133
|
+
# returns "ma*****@do*****".
|
134
|
+
def munge
|
135
|
+
[self.local.munge, self.host.munge].join("@")
|
89
136
|
end
|
90
|
-
alias :uniq :canonical
|
91
|
-
alias :canonicalize :canonical
|
92
137
|
|
93
138
|
# Returns and MD5 of the canonical address form. Some cross-system systems
|
94
139
|
# use the email address MD5 instead of the actual address to refer to the
|
95
140
|
# same shared user identity without exposing the actual address when it
|
96
141
|
# is not known in common.
|
97
|
-
def
|
98
|
-
Digest::MD5.hexdigest(canonical)
|
99
|
-
end
|
100
|
-
|
101
|
-
def canonical_md5
|
142
|
+
def reference
|
102
143
|
Digest::MD5.hexdigest(self.canonical)
|
103
144
|
end
|
145
|
+
alias :md5 :reference
|
104
146
|
|
105
147
|
# This returns the SHA1 digest (in a hex string) of the canonical email
|
106
148
|
# address. See #md5 for more background.
|
107
149
|
def sha1
|
108
|
-
Digest::SHA1.hexdigest(canonical)
|
150
|
+
Digest::SHA1.hexdigest(canonical + @config[:sha1_secret])
|
109
151
|
end
|
110
152
|
|
153
|
+
#---------------------------------------------------------------------------
|
154
|
+
# Comparisons & Matching
|
155
|
+
#---------------------------------------------------------------------------
|
156
|
+
|
111
157
|
# Equal matches the normalized version of each address. Use the Threequal to check
|
112
158
|
# for match on canonical or redacted versions of addresses
|
113
159
|
def ==(other_email)
|
114
|
-
|
160
|
+
self.to_s == other_email.to_s
|
115
161
|
end
|
116
162
|
alias :eql? :==
|
117
163
|
alias :equal? :==
|
@@ -119,43 +165,77 @@ module EmailAddress
|
|
119
165
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
120
166
|
# of this addres with another, using the canonical or redacted forms.
|
121
167
|
def same_as?(other_email)
|
122
|
-
|
123
|
-
|
168
|
+
if other_email.is_a?(String)
|
169
|
+
other_email = EmailAddress::Address.new(other_email)
|
170
|
+
end
|
171
|
+
|
172
|
+
self.canonical == other_email.canonical ||
|
173
|
+
self.redact == other_email.canonical ||
|
174
|
+
self.canonical == other_email.redact
|
124
175
|
end
|
125
176
|
alias :include? :same_as?
|
126
177
|
|
127
178
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
128
179
|
# of this addres with another, using the normalized form.
|
129
180
|
def <=>(other_email)
|
130
|
-
|
181
|
+
self.to_s <=> other_email.to_s
|
131
182
|
end
|
132
183
|
|
133
|
-
#
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
def redact
|
140
|
-
[sha1, @host.canonical].join('@')
|
141
|
-
end
|
184
|
+
# Address matches one of these Matcher rule patterns
|
185
|
+
def matches?(*rules)
|
186
|
+
rules.flatten!
|
187
|
+
match = self.local.matches?(rules)
|
188
|
+
match ||= self.host.matches?(rules)
|
189
|
+
return match if match
|
142
190
|
|
143
|
-
|
144
|
-
|
191
|
+
# Does "root@*.com" match "root@example.com" domain name
|
192
|
+
rules.each do |r|
|
193
|
+
if r =~ /.+@.+/
|
194
|
+
return r if File.fnmatch?(r, self.to_s)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
false
|
145
198
|
end
|
146
199
|
|
200
|
+
#---------------------------------------------------------------------------
|
201
|
+
# Validation
|
202
|
+
#---------------------------------------------------------------------------
|
203
|
+
|
147
204
|
# Returns true if this address is considered valid according to the format
|
148
205
|
# configured for its provider, It test the normalized form.
|
149
206
|
def valid?(options={})
|
150
|
-
|
207
|
+
self.error = nil
|
208
|
+
unless self.local.valid?
|
209
|
+
self.error = "Invalid Mailbox"
|
210
|
+
return false
|
211
|
+
end
|
212
|
+
unless self.host.valid?
|
213
|
+
self.error = "Invalid Host"
|
214
|
+
return false
|
215
|
+
end
|
216
|
+
if @config[:address_size] && !@config[:address_size].include?(self.to_s.size)
|
217
|
+
self.error = "Exceeds size"
|
218
|
+
return false
|
219
|
+
end
|
220
|
+
if @config[:address_validation].is_a?(Proc)
|
221
|
+
unless @config[:address_validation].call(self.to_s)
|
222
|
+
self.error = "Not allowed"
|
223
|
+
return false
|
224
|
+
end
|
225
|
+
else
|
226
|
+
return false unless self.local.valid?
|
227
|
+
return false unless self.host.valid?
|
228
|
+
end
|
229
|
+
if !@config[:address_local] && !self.hostname.include?(".")
|
230
|
+
self.error = "Incomplete Domain"
|
231
|
+
return false
|
232
|
+
end
|
233
|
+
true
|
234
|
+
end
|
235
|
+
|
236
|
+
def error
|
237
|
+
self.valid? ? nil : @error
|
151
238
|
end
|
152
239
|
|
153
|
-
# Returns an array of error messages generated from the validation process via
|
154
|
-
# the #valid? method.
|
155
|
-
def errors(options={})
|
156
|
-
v = EmailAddress::Validator.new(self, options)
|
157
|
-
v.valid?
|
158
|
-
v.errors
|
159
|
-
end
|
160
240
|
end
|
161
241
|
end
|