email_address 0.1.2 → 0.2.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 +5 -5
- data/.github/workflows/ci.yml +18 -0
- data/Gemfile +1 -1
- data/README.md +255 -123
- data/Rakefile +2 -3
- data/email_address.gemspec +27 -22
- data/lib/email_address/active_record_validator.rb +10 -11
- data/lib/email_address/address.rb +126 -80
- data/lib/email_address/canonical_email_address_type.rb +16 -12
- data/lib/email_address/config.rb +102 -51
- data/lib/email_address/email_address_type.rb +17 -13
- data/lib/email_address/exchanger.rb +44 -33
- data/lib/email_address/host.rb +217 -105
- data/lib/email_address/local.rb +127 -87
- data/lib/email_address/messages.yaml +21 -0
- data/lib/email_address/rewriter.rb +144 -0
- data/lib/email_address/version.rb +1 -1
- data/lib/email_address.rb +48 -53
- data/test/activerecord/test_ar.rb +17 -13
- data/test/activerecord/user.rb +31 -30
- data/test/email_address/test_address.rb +84 -21
- data/test/email_address/test_config.rb +10 -10
- data/test/email_address/test_exchanger.rb +6 -7
- data/test/email_address/test_host.rb +59 -21
- data/test/email_address/test_local.rb +49 -36
- data/test/email_address/test_rewriter.rb +11 -0
- data/test/test_aliasing.rb +53 -0
- data/test/test_email_address.rb +15 -19
- data/test/test_helper.rb +9 -8
- metadata +43 -21
- data/.travis.yml +0 -10
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module EmailAddress
|
3
4
|
# ActiveRecord validator class for validating an email
|
4
5
|
# address with this library.
|
5
6
|
# Note the initialization happens once per process.
|
@@ -14,14 +15,13 @@ module EmailAddress
|
|
14
15
|
# Default field: :email or :email_address (first found)
|
15
16
|
#
|
16
17
|
class ActiveRecordValidator < ActiveModel::Validator
|
17
|
-
|
18
|
-
def initialize(options={})
|
18
|
+
def initialize(options = {})
|
19
19
|
@opt = options
|
20
20
|
end
|
21
21
|
|
22
22
|
def validate(r)
|
23
23
|
if @opt[:fields]
|
24
|
-
@opt[:fields].each {|f| validate_email(r, f) }
|
24
|
+
@opt[:fields].each { |f| validate_email(r, f) }
|
25
25
|
elsif @opt[:field]
|
26
26
|
validate_email(r, @opt[:field])
|
27
27
|
elsif r.respond_to? :email
|
@@ -31,16 +31,15 @@ module EmailAddress
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
def validate_email(r,f)
|
34
|
+
def validate_email(r, f)
|
35
35
|
return if r[f].nil?
|
36
|
-
e =
|
36
|
+
e = Address.new(r[f])
|
37
37
|
unless e.valid?
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
error_message = @opt[:message] ||
|
39
|
+
Config.error_message(:invalid_address, I18n.locale.to_s) ||
|
40
|
+
"Invalid Email Address"
|
41
|
+
r.errors.add(f, error_message)
|
41
42
|
end
|
42
43
|
end
|
43
|
-
|
44
44
|
end
|
45
|
-
|
46
45
|
end
|
@@ -1,35 +1,48 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha1"
|
4
|
+
require "digest/sha2"
|
5
|
+
require "digest/md5"
|
3
6
|
|
4
7
|
module EmailAddress
|
5
8
|
# Implements the Email Address container, which hold the Local
|
6
|
-
# (EmailAddress::Local) and Host (
|
9
|
+
# (EmailAddress::Local) and Host (EmailAddress::Host) parts.
|
7
10
|
class Address
|
8
11
|
include Comparable
|
9
|
-
|
12
|
+
include Rewriter
|
13
|
+
|
14
|
+
attr_accessor :original, :local, :host, :config, :reason, :locale
|
10
15
|
|
11
|
-
CONVENTIONAL_REGEX = /\A#{
|
12
|
-
@#{
|
13
|
-
STANDARD_REGEX
|
14
|
-
@#{
|
15
|
-
RELAXED_REGEX
|
16
|
-
@#{
|
16
|
+
CONVENTIONAL_REGEX = /\A#{Local::CONVENTIONAL_MAILBOX_WITHIN}
|
17
|
+
@#{Host::DNS_HOST_REGEX}\z/x
|
18
|
+
STANDARD_REGEX = /\A#{Local::STANDARD_LOCAL_WITHIN}
|
19
|
+
@#{Host::DNS_HOST_REGEX}\z/x
|
20
|
+
RELAXED_REGEX = /\A#{Local::RELAXED_MAILBOX_WITHIN}
|
21
|
+
@#{Host::DNS_HOST_REGEX}\z/x
|
17
22
|
|
18
23
|
# Given an email address of the form "local@hostname", this sets up the
|
19
24
|
# instance, and initializes the address to the "normalized" format of the
|
20
25
|
# address. The original string is available in the #original method.
|
21
|
-
def initialize(email_address, config={})
|
22
|
-
|
23
|
-
@original
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
def initialize(email_address, config = {}, locale = "en")
|
27
|
+
@config = Config.new(config)
|
28
|
+
@original = email_address
|
29
|
+
@locale = locale
|
30
|
+
email_address = (email_address || "").strip
|
31
|
+
email_address = parse_rewritten(email_address) unless config[:skip_rewrite]
|
32
|
+
local, host = Address.split_local_host(email_address)
|
33
|
+
|
34
|
+
@host = Host.new(host, @config, locale)
|
35
|
+
@local = Local.new(local, @config, @host, locale)
|
36
|
+
@error = @error_message = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Given an email address, this returns an array of [local, host] parts
|
40
|
+
def self.split_local_host(email)
|
41
|
+
if (lh = email.match(/(.+)@(.+)/))
|
42
|
+
lh.to_a[1, 2]
|
27
43
|
else
|
28
|
-
|
44
|
+
[email, ""]
|
29
45
|
end
|
30
|
-
@host = EmailAddress::Host.new(host, config)
|
31
|
-
@config = @host.config
|
32
|
-
@local = EmailAddress::Local.new(local, @config)
|
33
46
|
end
|
34
47
|
|
35
48
|
############################################################################
|
@@ -42,26 +55,26 @@ module EmailAddress
|
|
42
55
|
|
43
56
|
# Everything to the left of the @ in the address, called the local part.
|
44
57
|
def left
|
45
|
-
|
58
|
+
local.to_s
|
46
59
|
end
|
47
60
|
|
48
61
|
# Returns the mailbox portion of the local port, with no tags. Usually, this
|
49
62
|
# can be considered the user account or role account names. Some systems
|
50
63
|
# employ dynamic email addresses which don't have the same meaning.
|
51
64
|
def mailbox
|
52
|
-
|
65
|
+
local.mailbox
|
53
66
|
end
|
54
67
|
|
55
68
|
# Returns the tag part of the local address, or nil if not given.
|
56
69
|
def tag
|
57
|
-
|
70
|
+
local.tag
|
58
71
|
end
|
59
72
|
|
60
73
|
# Retuns any comments parsed from the local part of the email address.
|
61
74
|
# This is retained for inspection after construction, even if it is
|
62
75
|
# removed from the normalized email address.
|
63
76
|
def comment
|
64
|
-
|
77
|
+
local.comment
|
65
78
|
end
|
66
79
|
|
67
80
|
############################################################################
|
@@ -75,8 +88,8 @@ module EmailAddress
|
|
75
88
|
def host_name
|
76
89
|
@host.host_name
|
77
90
|
end
|
78
|
-
|
79
|
-
|
91
|
+
alias_method :right, :host_name
|
92
|
+
alias_method :hostname, :host_name
|
80
93
|
|
81
94
|
# Returns the ESP (Email Service Provider) or ISP name derived
|
82
95
|
# using the provider configuration rules.
|
@@ -92,18 +105,18 @@ module EmailAddress
|
|
92
105
|
def normal
|
93
106
|
if !@original
|
94
107
|
@original
|
95
|
-
elsif
|
108
|
+
elsif local.to_s.size == 0
|
96
109
|
""
|
97
|
-
elsif
|
98
|
-
|
110
|
+
elsif host.to_s.size == 0
|
111
|
+
local.to_s
|
99
112
|
else
|
100
|
-
"#{
|
113
|
+
"#{local}@#{host}"
|
101
114
|
end
|
102
115
|
end
|
103
|
-
|
116
|
+
alias_method :to_s, :normal
|
104
117
|
|
105
118
|
def inspect
|
106
|
-
"
|
119
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} address=\"#{self}\">"
|
107
120
|
end
|
108
121
|
|
109
122
|
# Returns the canonical email address according to the provider
|
@@ -111,51 +124,60 @@ module EmailAddress
|
|
111
124
|
# spaves and comments and tags, and any extraneous part of the address
|
112
125
|
# not considered a unique account by the provider.
|
113
126
|
def canonical
|
114
|
-
c =
|
115
|
-
c += "@" +
|
127
|
+
c = local.canonical
|
128
|
+
c += "@" + host.canonical if host.canonical && host.canonical > " "
|
116
129
|
c
|
117
130
|
end
|
118
131
|
|
119
132
|
# True if the given address is already in it's canonical form.
|
120
133
|
def canonical?
|
121
|
-
|
134
|
+
canonical == to_s
|
135
|
+
end
|
136
|
+
|
137
|
+
# The base address is the mailbox, without tags, and host.
|
138
|
+
def base
|
139
|
+
mailbox + "@" + hostname
|
122
140
|
end
|
123
141
|
|
124
142
|
# Returns the redacted form of the address
|
125
143
|
# This format is defined by this libaray, and may change as usage increases.
|
126
144
|
# Takes either :sha1 (default) or :md5 as the argument
|
127
|
-
def redact(digest
|
128
|
-
raise "Unknown Digest type: #{digest}" unless %i
|
129
|
-
return
|
130
|
-
r = %
|
131
|
-
r += "@" +
|
145
|
+
def redact(digest = :sha1)
|
146
|
+
raise "Unknown Digest type: #{digest}" unless %i[sha1 md5].include?(digest)
|
147
|
+
return to_s if local.redacted?
|
148
|
+
r = %({#{send(digest)}})
|
149
|
+
r += "@" + host.to_s if host.to_s && host.to_s > " "
|
132
150
|
r
|
133
151
|
end
|
134
152
|
|
135
153
|
# True if the address is already in the redacted state.
|
136
154
|
def redacted?
|
137
|
-
|
155
|
+
local.redacted?
|
138
156
|
end
|
139
157
|
|
140
158
|
# Returns the munged form of the address, by default "mailbox@domain.tld"
|
141
159
|
# returns "ma*****@do*****".
|
142
160
|
def munge
|
143
|
-
[
|
161
|
+
[local.munge, host.munge].join("@")
|
144
162
|
end
|
145
163
|
|
146
|
-
# Returns and MD5 of the
|
164
|
+
# Returns and MD5 of the base address form. Some cross-system systems
|
147
165
|
# use the email address MD5 instead of the actual address to refer to the
|
148
166
|
# same shared user identity without exposing the actual address when it
|
149
167
|
# is not known in common.
|
150
|
-
def reference
|
151
|
-
Digest::MD5.hexdigest(
|
168
|
+
def reference(form = :base)
|
169
|
+
Digest::MD5.hexdigest(send(form))
|
152
170
|
end
|
153
|
-
|
171
|
+
alias_method :md5, :reference
|
154
172
|
|
155
|
-
# This returns the SHA1 digest (in a hex string) of the
|
173
|
+
# This returns the SHA1 digest (in a hex string) of the base email
|
156
174
|
# address. See #md5 for more background.
|
157
|
-
def sha1
|
158
|
-
Digest::SHA1.hexdigest((
|
175
|
+
def sha1(form = :base)
|
176
|
+
Digest::SHA1.hexdigest((send(form) || "") + (@config[:sha1_secret] || ""))
|
177
|
+
end
|
178
|
+
|
179
|
+
def sha256(form = :base)
|
180
|
+
Digest::SHA256.hexdigest((send(form) || "") + (@config[:sha256_secret] || ""))
|
159
181
|
end
|
160
182
|
|
161
183
|
#---------------------------------------------------------------------------
|
@@ -164,42 +186,42 @@ module EmailAddress
|
|
164
186
|
|
165
187
|
# Equal matches the normalized version of each address. Use the Threequal to check
|
166
188
|
# for match on canonical or redacted versions of addresses
|
167
|
-
def ==(
|
168
|
-
|
189
|
+
def ==(other)
|
190
|
+
to_s == other.to_s
|
169
191
|
end
|
170
|
-
|
171
|
-
|
192
|
+
alias_method :eql?, :==
|
193
|
+
alias_method :equal?, :==
|
172
194
|
|
173
195
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
174
196
|
# of this addres with another, using the canonical or redacted forms.
|
175
197
|
def same_as?(other_email)
|
176
198
|
if other_email.is_a?(String)
|
177
|
-
other_email =
|
199
|
+
other_email = Address.new(other_email)
|
178
200
|
end
|
179
201
|
|
180
|
-
|
181
|
-
|
182
|
-
|
202
|
+
canonical == other_email.canonical ||
|
203
|
+
redact == other_email.canonical ||
|
204
|
+
canonical == other_email.redact
|
183
205
|
end
|
184
|
-
|
206
|
+
alias_method :include?, :same_as?
|
185
207
|
|
186
208
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
187
209
|
# of this addres with another, using the normalized form.
|
188
|
-
def <=>(
|
189
|
-
|
210
|
+
def <=>(other)
|
211
|
+
to_s <=> other.to_s
|
190
212
|
end
|
191
213
|
|
192
214
|
# Address matches one of these Matcher rule patterns
|
193
215
|
def matches?(*rules)
|
194
216
|
rules.flatten!
|
195
|
-
match
|
196
|
-
match ||=
|
217
|
+
match = local.matches?(rules)
|
218
|
+
match ||= host.matches?(rules)
|
197
219
|
return match if match
|
198
220
|
|
199
221
|
# Does "root@*.com" match "root@example.com" domain name
|
200
222
|
rules.each do |r|
|
201
|
-
if r
|
202
|
-
return r if File.fnmatch?(r,
|
223
|
+
if /.+@.+/.match?(r)
|
224
|
+
return r if File.fnmatch?(r, to_s)
|
203
225
|
end
|
204
226
|
end
|
205
227
|
false
|
@@ -211,39 +233,63 @@ module EmailAddress
|
|
211
233
|
|
212
234
|
# Returns true if this address is considered valid according to the format
|
213
235
|
# configured for its provider, It test the normalized form.
|
214
|
-
def valid?(options={})
|
236
|
+
def valid?(options = {})
|
215
237
|
@error = nil
|
216
|
-
unless
|
217
|
-
return set_error
|
238
|
+
unless local.valid?
|
239
|
+
return set_error local.error
|
218
240
|
end
|
219
|
-
unless
|
220
|
-
return set_error
|
241
|
+
unless host.valid?
|
242
|
+
return set_error host.error_message
|
221
243
|
end
|
222
|
-
if @config[:address_size] && !@config[:address_size].include?(
|
244
|
+
if @config[:address_size] && !@config[:address_size].include?(to_s.size)
|
223
245
|
return set_error :exceeds_size
|
224
246
|
end
|
225
247
|
if @config[:address_validation].is_a?(Proc)
|
226
|
-
unless @config[:address_validation].call(
|
248
|
+
unless @config[:address_validation].call(to_s)
|
227
249
|
return set_error :not_allowed
|
228
250
|
end
|
229
251
|
else
|
230
|
-
return false unless
|
231
|
-
return false unless
|
232
|
-
end
|
233
|
-
if !@config[:address_local] && !self.hostname.include?(".")
|
234
|
-
return set_error :incomplete_domain
|
252
|
+
return false unless local.valid?
|
253
|
+
return false unless host.valid?
|
235
254
|
end
|
236
255
|
true
|
237
256
|
end
|
238
257
|
|
239
|
-
|
240
|
-
|
258
|
+
# Connects to host to test if user can receive email. This should NOT be performed
|
259
|
+
# as an email address check, but is provided to assist in problem resolution.
|
260
|
+
# If you abuse this, you *could* be blocked by the ESP.
|
261
|
+
def connect
|
262
|
+
smtp = Net::SMTP.new(host_name || ip_address)
|
263
|
+
smtp.start(@config[:smtp_helo_name] || "localhost")
|
264
|
+
smtp.mailfrom @config[:smtp_mail_from] || "postmaster@localhost"
|
265
|
+
smtp.rcptto to_s
|
266
|
+
# p [:connect]
|
267
|
+
smtp.finish
|
268
|
+
true
|
269
|
+
rescue Net::SMTPUnknownError => e
|
270
|
+
set_error(:address_unknown, e.to_s)
|
271
|
+
rescue Net::SMTPFatalError => e
|
272
|
+
set_error(:address_unknown, e.to_s)
|
273
|
+
rescue SocketError => e
|
274
|
+
set_error(:address_unknown, e.to_s)
|
275
|
+
ensure
|
276
|
+
if smtp&.started?
|
277
|
+
smtp.finish
|
278
|
+
end
|
279
|
+
!!@error
|
280
|
+
end
|
281
|
+
|
282
|
+
def set_error(err, reason = nil)
|
283
|
+
@error = err
|
284
|
+
@reason = reason
|
285
|
+
@error_message = Config.error_message(err, locale)
|
241
286
|
false
|
242
287
|
end
|
243
288
|
|
289
|
+
attr_reader :error_message
|
290
|
+
|
244
291
|
def error
|
245
|
-
|
292
|
+
valid? ? nil : @error_message
|
246
293
|
end
|
247
|
-
|
248
294
|
end
|
249
295
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
################################################################################
|
2
4
|
# ActiveRecord v5.0 Custom Type
|
3
5
|
#
|
@@ -27,20 +29,22 @@
|
|
27
29
|
# user.canonical_email #=> "patsmith@gmail.com"
|
28
30
|
################################################################################
|
29
31
|
|
30
|
-
|
32
|
+
module EmailAddress
|
33
|
+
class CanonicalEmailAddressType < ActiveRecord::Type::Value
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
# From user input, setter
|
36
|
+
def cast(value)
|
37
|
+
super(Address.new(value).canonical)
|
38
|
+
end
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
40
|
+
# From a database value
|
41
|
+
def deserialize(value)
|
42
|
+
value && Address.new(value).normal
|
43
|
+
end
|
41
44
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
# To a database value (string)
|
46
|
+
def serialize(value)
|
47
|
+
value && Address.new(value).normal
|
48
|
+
end
|
45
49
|
end
|
46
50
|
end
|
data/lib/email_address/config.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module EmailAddress
|
2
4
|
# Global configurations and for default/unknown providers. Settings are:
|
3
5
|
#
|
@@ -7,11 +9,19 @@ module EmailAddress
|
|
7
9
|
# :a - DNS A Record lookup (as some domains don't specify an MX incorrectly)
|
8
10
|
# :off - Do not perform DNS lookup (Test mode, network unavailable)
|
9
11
|
#
|
12
|
+
# * dns_timeout: nil
|
13
|
+
# False, or a timeout in seconds. Timeout on the DNS lookup, after which it will fail.
|
14
|
+
#
|
10
15
|
# * sha1_secret ""
|
11
16
|
# This application-level secret is appended to the email_address to compute
|
12
17
|
# the SHA1 Digest, making it unique to your application so it can't easily be
|
13
18
|
# discovered by comparing against a known list of email/sha1 pairs.
|
14
19
|
#
|
20
|
+
# * sha256_secret ""
|
21
|
+
# This application-level secret is appended to the email_address to compute
|
22
|
+
# the SHA256 Digest, making it unique to your application so it can't easily be
|
23
|
+
# discovered by comparing against a known list of email/sha256 pairs.
|
24
|
+
#
|
15
25
|
# For local part configuration:
|
16
26
|
# * local_downcase: true
|
17
27
|
# Downcase the local part. You probably want this for uniqueness.
|
@@ -68,6 +78,9 @@ module EmailAddress
|
|
68
78
|
# * host_allow_ip: false,
|
69
79
|
# Allow IP address format in host: [127.0.0.1], [IPv6:::1]
|
70
80
|
#
|
81
|
+
# * host_local: false,
|
82
|
+
# Allow localhost, no domain, or local subdomains.
|
83
|
+
#
|
71
84
|
# * address_validation: :parts, :smtp, ->(address) { true }
|
72
85
|
# Address validation policy
|
73
86
|
# :parts Validate local and host.
|
@@ -77,8 +90,11 @@ module EmailAddress
|
|
77
90
|
# * address_size: 3..254,
|
78
91
|
# A range specifying the size limit of the complete address
|
79
92
|
#
|
80
|
-
# *
|
81
|
-
#
|
93
|
+
# * address_fqdn_domain: nil || "domain.tld"
|
94
|
+
# Configure to complete the FQDN (Fully Qualified Domain Name)
|
95
|
+
# When host is blank, this value is used
|
96
|
+
# When host is computer name only, a dot and this is appended to get the FQDN
|
97
|
+
# You probably don't want this unless you have host-local email accounts
|
82
98
|
#
|
83
99
|
# For provider rules to match to domain names and Exchanger hosts
|
84
100
|
# The value is an array of match tokens.
|
@@ -86,64 +102,68 @@ module EmailAddress
|
|
86
102
|
# * exchanger_match: %w(google.com 127.0.0.1 10.9.8.0/24 ::1/64)
|
87
103
|
#
|
88
104
|
|
105
|
+
require "yaml"
|
106
|
+
|
89
107
|
class Config
|
90
108
|
@config = {
|
91
|
-
dns_lookup:
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
109
|
+
dns_lookup: :mx, # :mx, :a, :off
|
110
|
+
dns_timeout: nil,
|
111
|
+
sha1_secret: "",
|
112
|
+
sha256_secret: "",
|
113
|
+
munge_string: "*****",
|
114
|
+
|
115
|
+
local_downcase: true,
|
116
|
+
local_fix: false,
|
117
|
+
local_encoding: :ascii, # :ascii, :unicode,
|
118
|
+
local_parse: nil, # nil, Proc
|
119
|
+
local_format: :conventional, # :conventional, :relaxed, :redacted, :standard, Proc
|
120
|
+
local_size: 1..64,
|
121
|
+
tag_separator: "+", # nil, character
|
122
|
+
mailbox_size: 1..64, # without tag
|
123
|
+
mailbox_canonical: nil, # nil, Proc
|
124
|
+
mailbox_validator: nil, # nil, Proc
|
125
|
+
|
126
|
+
host_encoding: :punycode || :unicode,
|
127
|
+
host_validation: :mx || :a || :connect || :syntax,
|
128
|
+
host_size: 1..253,
|
129
|
+
host_allow_ip: false,
|
130
|
+
host_remove_spaces: false,
|
131
|
+
host_local: false,
|
110
132
|
|
111
133
|
address_validation: :parts, # :parts, :smtp, Proc
|
112
|
-
address_size:
|
113
|
-
|
134
|
+
address_size: 3..254,
|
135
|
+
address_fqdn_domain: nil # Fully Qualified Domain Name = [host].[domain.tld]
|
114
136
|
}
|
115
137
|
|
138
|
+
# 2018-04: AOL and Yahoo now under "oath.com", owned by Verizon. Keeping separate for now
|
116
139
|
@providers = {
|
117
140
|
aol: {
|
118
|
-
host_match:
|
141
|
+
host_match: %w[aol. compuserve. netscape. aim. cs.]
|
119
142
|
},
|
120
143
|
google: {
|
121
|
-
host_match:
|
122
|
-
exchanger_match:
|
123
|
-
local_size:
|
124
|
-
|
144
|
+
host_match: %w[gmail.com googlemail.com],
|
145
|
+
exchanger_match: %w[google.com googlemail.com],
|
146
|
+
local_size: 5..64,
|
147
|
+
local_private_size: 1..64, # When hostname not in host_match (private label)
|
148
|
+
mailbox_canonical: ->(m) { m.delete(".") }
|
125
149
|
},
|
126
150
|
msn: {
|
127
|
-
host_match:
|
128
|
-
|
151
|
+
host_match: %w[msn. hotmail. outlook. live.],
|
152
|
+
exchanger_match: %w[outlook.com],
|
153
|
+
mailbox_validator: ->(m, t) { m =~ /\A\w[\-\w]*(?:\.[\-\w]+)*\z/i }
|
129
154
|
},
|
130
155
|
yahoo: {
|
131
|
-
host_match:
|
132
|
-
exchanger_match:
|
133
|
-
}
|
156
|
+
host_match: %w[yahoo. ymail. rocketmail.],
|
157
|
+
exchanger_match: %w[yahoodns yahoo-inc]
|
158
|
+
}
|
134
159
|
}
|
135
160
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
invalid_host: "Invalid Host/Domain Name",
|
140
|
-
exceeds_size: "Address too long",
|
141
|
-
not_allowed: "Address is not allowed",
|
142
|
-
incomplete_domain: "Domain name is incomplete",
|
143
|
-
}
|
161
|
+
# Loads messages: {"en"=>{"email_address"=>{"invalid_address"=>"Invalid Email Address",...}}}
|
162
|
+
# Rails/I18n gem: t(email_address.error, scope: "email_address")
|
163
|
+
@errors = YAML.load_file(File.dirname(__FILE__) + "/messages.yaml")
|
144
164
|
|
145
165
|
# Set multiple default configuration settings
|
146
|
-
def self.configure(config={})
|
166
|
+
def self.configure(config = {})
|
147
167
|
@config.merge!(config)
|
148
168
|
end
|
149
169
|
|
@@ -154,30 +174,61 @@ module EmailAddress
|
|
154
174
|
end
|
155
175
|
|
156
176
|
# Returns the hash of Provider rules
|
157
|
-
|
158
|
-
|
177
|
+
class << self
|
178
|
+
attr_reader :providers
|
159
179
|
end
|
160
180
|
|
161
181
|
# Configure or lookup a provider by name.
|
162
|
-
def self.provider(name, config={})
|
182
|
+
def self.provider(name, config = {})
|
163
183
|
name = name.to_sym
|
164
184
|
if config.size > 0
|
165
|
-
@providers[name]
|
166
|
-
@providers[name].merge!(config)
|
185
|
+
@providers[name.to_sym] = config
|
167
186
|
end
|
168
187
|
@providers[name]
|
169
188
|
end
|
170
189
|
|
190
|
+
def self.error_message(name, locale = "en")
|
191
|
+
@errors[locale]["email_address"][name.to_s] || name.to_s
|
192
|
+
end
|
193
|
+
|
171
194
|
# Customize your own error message text.
|
172
|
-
def self.error_messages(hash=
|
173
|
-
|
174
|
-
|
195
|
+
def self.error_messages(hash = {}, locale = "en", *extra)
|
196
|
+
hash = extra.first if extra.first.is_a? Hash
|
197
|
+
|
198
|
+
@errors[locale] ||= {}
|
199
|
+
@errors[locale]["email_address"] ||= {}
|
200
|
+
|
201
|
+
unless hash.nil? || hash.empty?
|
202
|
+
@errors[locale]["email_address"] = @errors[locale]["email_address"].merge(hash)
|
203
|
+
end
|
204
|
+
|
205
|
+
@errors[locale]["email_address"]
|
175
206
|
end
|
176
207
|
|
177
208
|
def self.all_settings(*configs)
|
178
209
|
config = @config.clone
|
179
|
-
configs.each {|c| config.merge!(c) }
|
210
|
+
configs.each { |c| config.merge!(c) }
|
180
211
|
config
|
181
212
|
end
|
213
|
+
|
214
|
+
def initialize(overrides = {})
|
215
|
+
@config = Config.all_settings(overrides)
|
216
|
+
end
|
217
|
+
|
218
|
+
def []=(setting, value)
|
219
|
+
@config[setting.to_sym] = value
|
220
|
+
end
|
221
|
+
|
222
|
+
def [](setting)
|
223
|
+
@config[setting.to_sym]
|
224
|
+
end
|
225
|
+
|
226
|
+
def configure(settings)
|
227
|
+
@config = @config.merge(settings)
|
228
|
+
end
|
229
|
+
|
230
|
+
def to_h
|
231
|
+
@config
|
232
|
+
end
|
182
233
|
end
|
183
234
|
end
|