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