email_address 0.1.13 → 0.1.14
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/README.md +4 -2
- data/lib/email_address/address.rb +87 -92
- data/lib/email_address/config.rb +47 -45
- data/lib/email_address/exchanger.rb +27 -28
- data/lib/email_address/host.rb +116 -116
- data/lib/email_address/version.rb +1 -1
- data/test/email_address/test_host.rb +29 -31
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f3decf66b46d08f8a8f3528ad331cfb009f7dfa302b8f887558bb535e189210
|
4
|
+
data.tar.gz: a5aac0f73f92ae103cada5c57b5f51a7efb968752dfe5ab8710b72a52705c98a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6acd7e0c108cadaa5be29dc0d843c3d12311d96791f79130b6c1ed2a1e26c3e3c2838f40034504d74bd2717dd35947e789c39c72fd0c7470aaefec23167983c8
|
7
|
+
data.tar.gz: bf7f061c707cac898ca51fd4bb8a697ad0c491be4a3119ded9c836e9ef8257ad3c513a2562c9803b415bbea8e747e5ae8c54805c00b03cfd4825a7b2754a5229
|
data/README.md
CHANGED
@@ -446,14 +446,16 @@ EmailAddress::Config.provider(:github,
|
|
446
446
|
You can override the default error messages as follows:
|
447
447
|
|
448
448
|
```ruby
|
449
|
-
EmailAddress::Config.error_messages(
|
449
|
+
EmailAddress::Config.error_messages({
|
450
450
|
invalid_address: "Invalid Email Address",
|
451
451
|
invalid_mailbox: "Invalid Recipient/Mailbox",
|
452
452
|
invalid_host: "Invalid Host/Domain Name",
|
453
453
|
exceeds_size: "Address too long",
|
454
454
|
not_allowed: "Address is not allowed",
|
455
|
-
incomplete_domain: "Domain name is incomplete")
|
455
|
+
incomplete_domain: "Domain name is incomplete"}, 'en')
|
456
456
|
```
|
457
|
+
Note: Release 0.1.14 fixed setting error messages by locale.
|
458
|
+
Also, it will accept a ruby "collected" hash as before,
|
457
459
|
|
458
460
|
Full translation support would be ideal though.
|
459
461
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "digest/sha1"
|
4
|
+
require "digest/md5"
|
5
5
|
|
6
6
|
module EmailAddress
|
7
7
|
# Implements the Email Address container, which hold the Local
|
@@ -14,33 +14,33 @@ module EmailAddress
|
|
14
14
|
|
15
15
|
CONVENTIONAL_REGEX = /\A#{::EmailAddress::Local::CONVENTIONAL_MAILBOX_WITHIN}
|
16
16
|
@#{::EmailAddress::Host::DNS_HOST_REGEX}\z/x
|
17
|
-
STANDARD_REGEX
|
17
|
+
STANDARD_REGEX = /\A#{::EmailAddress::Local::STANDARD_LOCAL_WITHIN}
|
18
18
|
@#{::EmailAddress::Host::DNS_HOST_REGEX}\z/x
|
19
|
-
RELAXED_REGEX
|
19
|
+
RELAXED_REGEX = /\A#{::EmailAddress::Local::RELAXED_MAILBOX_WITHIN}
|
20
20
|
@#{::EmailAddress::Host::DNS_HOST_REGEX}\z/x
|
21
21
|
|
22
22
|
# Given an email address of the form "local@hostname", this sets up the
|
23
23
|
# instance, and initializes the address to the "normalized" format of the
|
24
24
|
# address. The original string is available in the #original method.
|
25
|
-
def initialize(email_address, config={})
|
26
|
-
@config
|
27
|
-
@original
|
28
|
-
email_address
|
29
|
-
email_address
|
30
|
-
local, host
|
31
|
-
|
32
|
-
@host
|
33
|
-
@config
|
34
|
-
@local
|
35
|
-
@error
|
25
|
+
def initialize(email_address, config = {})
|
26
|
+
@config = config # This needs refactoring!
|
27
|
+
@original = email_address
|
28
|
+
email_address = (email_address || "").strip
|
29
|
+
email_address = parse_rewritten(email_address) unless config[:skip_rewrite]
|
30
|
+
local, host = EmailAddress::Address.split_local_host(email_address)
|
31
|
+
|
32
|
+
@host = EmailAddress::Host.new(host, config)
|
33
|
+
@config = @host.config
|
34
|
+
@local = EmailAddress::Local.new(local, @config, @host)
|
35
|
+
@error = @error_message = nil
|
36
36
|
end
|
37
37
|
|
38
38
|
# Given an email address, this returns an array of [local, host] parts
|
39
39
|
def self.split_local_host(email)
|
40
|
-
if lh = email.match(/(.+)@(.+)/)
|
41
|
-
lh.to_a[1,2]
|
40
|
+
if (lh = email.match(/(.+)@(.+)/))
|
41
|
+
lh.to_a[1, 2]
|
42
42
|
else
|
43
|
-
[email,
|
43
|
+
[email, ""]
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -54,26 +54,26 @@ module EmailAddress
|
|
54
54
|
|
55
55
|
# Everything to the left of the @ in the address, called the local part.
|
56
56
|
def left
|
57
|
-
|
57
|
+
local.to_s
|
58
58
|
end
|
59
59
|
|
60
60
|
# Returns the mailbox portion of the local port, with no tags. Usually, this
|
61
61
|
# can be considered the user account or role account names. Some systems
|
62
62
|
# employ dynamic email addresses which don't have the same meaning.
|
63
63
|
def mailbox
|
64
|
-
|
64
|
+
local.mailbox
|
65
65
|
end
|
66
66
|
|
67
67
|
# Returns the tag part of the local address, or nil if not given.
|
68
68
|
def tag
|
69
|
-
|
69
|
+
local.tag
|
70
70
|
end
|
71
71
|
|
72
72
|
# Retuns any comments parsed from the local part of the email address.
|
73
73
|
# This is retained for inspection after construction, even if it is
|
74
74
|
# removed from the normalized email address.
|
75
75
|
def comment
|
76
|
-
|
76
|
+
local.comment
|
77
77
|
end
|
78
78
|
|
79
79
|
############################################################################
|
@@ -87,8 +87,8 @@ module EmailAddress
|
|
87
87
|
def host_name
|
88
88
|
@host.host_name
|
89
89
|
end
|
90
|
-
alias
|
91
|
-
alias
|
90
|
+
alias right host_name
|
91
|
+
alias hostname host_name
|
92
92
|
|
93
93
|
# Returns the ESP (Email Service Provider) or ISP name derived
|
94
94
|
# using the provider configuration rules.
|
@@ -104,18 +104,18 @@ module EmailAddress
|
|
104
104
|
def normal
|
105
105
|
if !@original
|
106
106
|
@original
|
107
|
-
elsif
|
107
|
+
elsif local.to_s.size == 0
|
108
108
|
""
|
109
|
-
elsif
|
110
|
-
|
109
|
+
elsif host.to_s.size == 0
|
110
|
+
local.to_s
|
111
111
|
else
|
112
|
-
"#{
|
112
|
+
"#{local}@#{host}"
|
113
113
|
end
|
114
114
|
end
|
115
|
-
alias
|
115
|
+
alias to_s normal
|
116
116
|
|
117
117
|
def inspect
|
118
|
-
"#<EmailAddress::Address:0x#{
|
118
|
+
"#<EmailAddress::Address:0x#{object_id.to_s(16)} address=\"#{self}\">"
|
119
119
|
end
|
120
120
|
|
121
121
|
# Returns the canonical email address according to the provider
|
@@ -123,56 +123,56 @@ module EmailAddress
|
|
123
123
|
# spaves and comments and tags, and any extraneous part of the address
|
124
124
|
# not considered a unique account by the provider.
|
125
125
|
def canonical
|
126
|
-
c =
|
127
|
-
c += "@" +
|
126
|
+
c = local.canonical
|
127
|
+
c += "@" + host.canonical if host.canonical && host.canonical > " "
|
128
128
|
c
|
129
129
|
end
|
130
130
|
|
131
131
|
# True if the given address is already in it's canonical form.
|
132
132
|
def canonical?
|
133
|
-
|
133
|
+
canonical == to_s
|
134
134
|
end
|
135
135
|
|
136
136
|
# The base address is the mailbox, without tags, and host.
|
137
137
|
def base
|
138
|
-
|
138
|
+
mailbox + "@" + hostname
|
139
139
|
end
|
140
140
|
|
141
141
|
# Returns the redacted form of the address
|
142
142
|
# This format is defined by this libaray, and may change as usage increases.
|
143
143
|
# Takes either :sha1 (default) or :md5 as the argument
|
144
|
-
def redact(digest
|
145
|
-
raise "Unknown Digest type: #{digest}" unless %i
|
146
|
-
return
|
147
|
-
r = %
|
148
|
-
r += "@" +
|
144
|
+
def redact(digest = :sha1)
|
145
|
+
raise "Unknown Digest type: #{digest}" unless %i[sha1 md5].include?(digest)
|
146
|
+
return to_s if local.redacted?
|
147
|
+
r = %({#{send(digest)}})
|
148
|
+
r += "@" + host.to_s if host.to_s && host.to_s > " "
|
149
149
|
r
|
150
150
|
end
|
151
151
|
|
152
152
|
# True if the address is already in the redacted state.
|
153
153
|
def redacted?
|
154
|
-
|
154
|
+
local.redacted?
|
155
155
|
end
|
156
156
|
|
157
157
|
# Returns the munged form of the address, by default "mailbox@domain.tld"
|
158
158
|
# returns "ma*****@do*****".
|
159
159
|
def munge
|
160
|
-
[
|
160
|
+
[local.munge, host.munge].join("@")
|
161
161
|
end
|
162
162
|
|
163
163
|
# Returns and MD5 of the canonical address form. Some cross-system systems
|
164
164
|
# use the email address MD5 instead of the actual address to refer to the
|
165
165
|
# same shared user identity without exposing the actual address when it
|
166
166
|
# is not known in common.
|
167
|
-
def reference(form
|
168
|
-
Digest::MD5.hexdigest(
|
167
|
+
def reference(form = :base)
|
168
|
+
Digest::MD5.hexdigest(send(form))
|
169
169
|
end
|
170
|
-
alias
|
170
|
+
alias md5 reference
|
171
171
|
|
172
172
|
# This returns the SHA1 digest (in a hex string) of the canonical email
|
173
173
|
# address. See #md5 for more background.
|
174
|
-
def sha1(form
|
175
|
-
Digest::SHA1.hexdigest((
|
174
|
+
def sha1(form = :base)
|
175
|
+
Digest::SHA1.hexdigest((send(form) || "") + (@config[:sha1_secret] || ""))
|
176
176
|
end
|
177
177
|
|
178
178
|
#---------------------------------------------------------------------------
|
@@ -182,10 +182,10 @@ module EmailAddress
|
|
182
182
|
# Equal matches the normalized version of each address. Use the Threequal to check
|
183
183
|
# for match on canonical or redacted versions of addresses
|
184
184
|
def ==(other_email)
|
185
|
-
|
185
|
+
to_s == other_email.to_s
|
186
186
|
end
|
187
|
-
alias
|
188
|
-
alias
|
187
|
+
alias eql? ==
|
188
|
+
alias equal? ==
|
189
189
|
|
190
190
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
191
191
|
# of this addres with another, using the canonical or redacted forms.
|
@@ -194,29 +194,29 @@ module EmailAddress
|
|
194
194
|
other_email = EmailAddress::Address.new(other_email)
|
195
195
|
end
|
196
196
|
|
197
|
-
|
198
|
-
|
199
|
-
|
197
|
+
canonical == other_email.canonical ||
|
198
|
+
redact == other_email.canonical ||
|
199
|
+
canonical == other_email.redact
|
200
200
|
end
|
201
|
-
alias
|
201
|
+
alias include? same_as?
|
202
202
|
|
203
203
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
204
204
|
# of this addres with another, using the normalized form.
|
205
205
|
def <=>(other_email)
|
206
|
-
|
206
|
+
to_s <=> other_email.to_s
|
207
207
|
end
|
208
208
|
|
209
209
|
# Address matches one of these Matcher rule patterns
|
210
210
|
def matches?(*rules)
|
211
211
|
rules.flatten!
|
212
|
-
match
|
213
|
-
match ||=
|
212
|
+
match = local.matches?(rules)
|
213
|
+
match ||= host.matches?(rules)
|
214
214
|
return match if match
|
215
215
|
|
216
216
|
# Does "root@*.com" match "root@example.com" domain name
|
217
217
|
rules.each do |r|
|
218
|
-
if r
|
219
|
-
return r if File.fnmatch?(r,
|
218
|
+
if /.+@.+/.match?(r)
|
219
|
+
return r if File.fnmatch?(r, to_s)
|
220
220
|
end
|
221
221
|
end
|
222
222
|
false
|
@@ -228,24 +228,24 @@ module EmailAddress
|
|
228
228
|
|
229
229
|
# Returns true if this address is considered valid according to the format
|
230
230
|
# configured for its provider, It test the normalized form.
|
231
|
-
def valid?(options={})
|
231
|
+
def valid?(options = {})
|
232
232
|
@error = nil
|
233
|
-
unless
|
234
|
-
return set_error
|
233
|
+
unless local.valid?
|
234
|
+
return set_error local.error
|
235
235
|
end
|
236
|
-
unless
|
237
|
-
return set_error
|
236
|
+
unless host.valid?
|
237
|
+
return set_error host.error_message
|
238
238
|
end
|
239
|
-
if @config[:address_size] && !@config[:address_size].include?(
|
239
|
+
if @config[:address_size] && !@config[:address_size].include?(to_s.size)
|
240
240
|
return set_error :exceeds_size
|
241
241
|
end
|
242
242
|
if @config[:address_validation].is_a?(Proc)
|
243
|
-
unless @config[:address_validation].call(
|
243
|
+
unless @config[:address_validation].call(to_s)
|
244
244
|
return set_error :not_allowed
|
245
245
|
end
|
246
246
|
else
|
247
|
-
return false unless
|
248
|
-
return false unless
|
247
|
+
return false unless local.valid?
|
248
|
+
return false unless host.valid?
|
249
249
|
end
|
250
250
|
true
|
251
251
|
end
|
@@ -254,42 +254,37 @@ module EmailAddress
|
|
254
254
|
# as an email address check, but is provided to assist in problem resolution.
|
255
255
|
# If you abuse this, you *could* be blocked by the ESP.
|
256
256
|
def connect
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
257
|
+
smtp = Net::SMTP.new(host_name || ip_address)
|
258
|
+
smtp.start(@config[:smtp_helo_name] || "localhost")
|
259
|
+
smtp.mailfrom @config[:smtp_mail_from] || "postmaster@localhost"
|
260
|
+
smtp.rcptto to_s
|
261
|
+
# p [:connect]
|
262
|
+
smtp.finish
|
263
|
+
true
|
264
|
+
rescue Net::SMTPUnknownError => e
|
265
|
+
set_error(:address_unknown, e.to_s)
|
266
|
+
rescue Net::SMTPFatalError => e
|
267
|
+
set_error(:address_unknown, e.to_s)
|
268
|
+
rescue SocketError => e
|
269
|
+
set_error(:address_unknown, e.to_s)
|
270
|
+
ensure
|
271
|
+
if smtp&.started?
|
263
272
|
smtp.finish
|
264
|
-
true
|
265
|
-
rescue Net::SMTPUnknownError => e
|
266
|
-
set_error(:address_unknown, e.to_s)
|
267
|
-
rescue Net::SMTPFatalError => e
|
268
|
-
set_error(:address_unknown, e.to_s)
|
269
|
-
rescue SocketError => e
|
270
|
-
set_error(:address_unknown, e.to_s)
|
271
|
-
ensure
|
272
|
-
if smtp && smtp.started?
|
273
|
-
smtp.finish
|
274
|
-
end
|
275
|
-
!!@error
|
276
273
|
end
|
274
|
+
!!@error
|
277
275
|
end
|
278
276
|
|
279
|
-
def set_error(err, reason=nil)
|
277
|
+
def set_error(err, reason = nil)
|
280
278
|
@error = err
|
281
|
-
@reason= reason
|
279
|
+
@reason = reason
|
282
280
|
@error_message = EmailAddress::Config.error_message(err)
|
283
281
|
false
|
284
282
|
end
|
285
283
|
|
286
|
-
|
287
|
-
@error_message
|
288
|
-
end
|
284
|
+
attr_reader :error_message
|
289
285
|
|
290
286
|
def error
|
291
|
-
|
287
|
+
valid? ? nil : @error_message
|
292
288
|
end
|
293
|
-
|
294
289
|
end
|
295
290
|
end
|
data/lib/email_address/config.rb
CHANGED
@@ -97,67 +97,66 @@ module EmailAddress
|
|
97
97
|
# * exchanger_match: %w(google.com 127.0.0.1 10.9.8.0/24 ::1/64)
|
98
98
|
#
|
99
99
|
|
100
|
-
require
|
100
|
+
require "yaml"
|
101
101
|
|
102
102
|
class Config
|
103
103
|
@config = {
|
104
|
-
dns_lookup:
|
105
|
-
dns_timeout:
|
106
|
-
sha1_secret:
|
107
|
-
munge_string:
|
108
|
-
|
109
|
-
local_downcase:
|
110
|
-
local_fix:
|
111
|
-
local_encoding:
|
112
|
-
local_parse:
|
113
|
-
local_format:
|
114
|
-
local_size:
|
115
|
-
tag_separator:
|
116
|
-
mailbox_size:
|
117
|
-
mailbox_canonical:
|
118
|
-
mailbox_validator:
|
119
|
-
|
120
|
-
host_encoding:
|
121
|
-
host_validation:
|
122
|
-
host_size:
|
123
|
-
host_allow_ip:
|
104
|
+
dns_lookup: :mx, # :mx, :a, :off
|
105
|
+
dns_timeout: nil,
|
106
|
+
sha1_secret: "",
|
107
|
+
munge_string: "*****",
|
108
|
+
|
109
|
+
local_downcase: true,
|
110
|
+
local_fix: false,
|
111
|
+
local_encoding: :ascii, # :ascii, :unicode,
|
112
|
+
local_parse: nil, # nil, Proc
|
113
|
+
local_format: :conventional, # :conventional, :relaxed, :redacted, :standard, Proc
|
114
|
+
local_size: 1..64,
|
115
|
+
tag_separator: "+", # nil, character
|
116
|
+
mailbox_size: 1..64, # without tag
|
117
|
+
mailbox_canonical: nil, # nil, Proc
|
118
|
+
mailbox_validator: nil, # nil, Proc
|
119
|
+
|
120
|
+
host_encoding: :punycode || :unicode,
|
121
|
+
host_validation: :mx || :a || :connect || :syntax,
|
122
|
+
host_size: 1..253,
|
123
|
+
host_allow_ip: false,
|
124
124
|
host_remove_spaces: false,
|
125
|
-
host_local:
|
125
|
+
host_local: false,
|
126
126
|
|
127
127
|
address_validation: :parts, # :parts, :smtp, Proc
|
128
|
-
address_size:
|
129
|
-
address_fqdn_domain: nil
|
128
|
+
address_size: 3..254,
|
129
|
+
address_fqdn_domain: nil # Fully Qualified Domain Name = [host].[domain.tld]
|
130
130
|
}
|
131
131
|
|
132
132
|
# 2018-04: AOL and Yahoo now under "oath.com", owned by Verizon. Keeping separate for now
|
133
133
|
@providers = {
|
134
134
|
aol: {
|
135
|
-
host_match:
|
135
|
+
host_match: %w[aol. compuserve. netscape. aim. cs.]
|
136
136
|
},
|
137
137
|
google: {
|
138
|
-
host_match:
|
139
|
-
exchanger_match:
|
140
|
-
local_size:
|
138
|
+
host_match: %w[gmail.com googlemail.com],
|
139
|
+
exchanger_match: %w[google.com googlemail.com],
|
140
|
+
local_size: 5..64,
|
141
141
|
local_private_size: 1..64, # When hostname not in host_match (private label)
|
142
|
-
mailbox_canonical: ->(m) {m.
|
142
|
+
mailbox_canonical: ->(m) { m.delete(".") }
|
143
143
|
},
|
144
144
|
msn: {
|
145
|
-
host_match:
|
146
|
-
mailbox_validator: ->(m,t) { m =~ /\A\w[\-\w]*(?:\.[\-\w]+)*\z/i}
|
145
|
+
host_match: %w[msn. hotmail. outlook. live.],
|
146
|
+
mailbox_validator: ->(m, t) { m =~ /\A\w[\-\w]*(?:\.[\-\w]+)*\z/i }
|
147
147
|
},
|
148
148
|
yahoo: {
|
149
|
-
host_match:
|
150
|
-
exchanger_match:
|
151
|
-
}
|
149
|
+
host_match: %w[yahoo. ymail. rocketmail.],
|
150
|
+
exchanger_match: %w[yahoodns yahoo-inc]
|
151
|
+
}
|
152
152
|
}
|
153
153
|
|
154
|
-
|
155
154
|
# Loads messages: {"en"=>{"email_address"=>{"invalid_address"=>"Invalid Email Address",...}}}
|
156
155
|
# Rails/I18n gem: t(email_address.error, scope: "email_address")
|
157
|
-
@errors = YAML.load_file(File.dirname(__FILE__)+"/messages.yaml")
|
156
|
+
@errors = YAML.load_file(File.dirname(__FILE__) + "/messages.yaml")
|
158
157
|
|
159
158
|
# Set multiple default configuration settings
|
160
|
-
def self.configure(config={})
|
159
|
+
def self.configure(config = {})
|
161
160
|
@config.merge!(config)
|
162
161
|
end
|
163
162
|
|
@@ -168,12 +167,12 @@ module EmailAddress
|
|
168
167
|
end
|
169
168
|
|
170
169
|
# Returns the hash of Provider rules
|
171
|
-
|
172
|
-
|
170
|
+
class << self
|
171
|
+
attr_reader :providers
|
173
172
|
end
|
174
173
|
|
175
174
|
# Configure or lookup a provider by name.
|
176
|
-
def self.provider(name, config={})
|
175
|
+
def self.provider(name, config = {})
|
177
176
|
name = name.to_sym
|
178
177
|
if config.size > 0
|
179
178
|
@providers[name] ||= @config.clone
|
@@ -182,19 +181,22 @@ module EmailAddress
|
|
182
181
|
@providers[name]
|
183
182
|
end
|
184
183
|
|
185
|
-
def self.error_message(name, locale="en")
|
184
|
+
def self.error_message(name, locale = "en")
|
186
185
|
@errors[locale]["email_address"][name.to_s] || name.to_s
|
187
186
|
end
|
188
187
|
|
189
188
|
# Customize your own error message text.
|
190
|
-
def self.error_messages(hash=
|
191
|
-
|
192
|
-
|
189
|
+
def self.error_messages(hash = {}, locale = "en", *extra)
|
190
|
+
hash = extra.first if extra.first.is_a? Hash
|
191
|
+
unless hash.empty?
|
192
|
+
@errors[locale]["email_address"] = @errors[locale]["email_address"].merge(hash)
|
193
|
+
end
|
194
|
+
@errors[locale]["email_address"]
|
193
195
|
end
|
194
196
|
|
195
197
|
def self.all_settings(*configs)
|
196
198
|
config = @config.clone
|
197
|
-
configs.each {|c| config.merge!(c) }
|
199
|
+
configs.each { |c| config.merge!(c) }
|
198
200
|
config
|
199
201
|
end
|
200
202
|
end
|