email_address 0.1.13 → 0.1.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/README.md +42 -3
- data/email_address.gemspec +1 -0
- data/lib/email_address.rb +17 -18
- data/lib/email_address/active_record_validator.rb +2 -2
- data/lib/email_address/address.rb +94 -100
- data/lib/email_address/canonical_email_address_type.rb +14 -12
- data/lib/email_address/config.rb +69 -47
- data/lib/email_address/email_address_type.rb +15 -13
- data/lib/email_address/exchanger.rb +29 -30
- data/lib/email_address/host.rb +123 -125
- data/lib/email_address/local.rb +7 -7
- data/lib/email_address/version.rb +1 -1
- data/test/email_address/test_address.rb +5 -0
- data/test/email_address/test_host.rb +29 -31
- data/test/test_aliasing.rb +54 -0
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a5ff67ec79e4de2d356293664296391fc8296ad6a6ba08f1d1256e93f7c729c
|
4
|
+
data.tar.gz: 5485014c6f886d9a382c046f9025948eeed6c114accde2957424f1f613d1b51b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 336f61eb9e81ccf8fe859b0d05e5e4d1aaf734e06328169ce919a01fdf30caaebdb2f44e673b52cdde99e3790c413c854e486fd97e4c610b5ee6349c1e3dbe40
|
7
|
+
data.tar.gz: c08f83be388d6fdb646ea46439c0a65543091e401b9c9d38e312a7d2dc34313c3269f9c16916340d090f48ae224a1cd18631153d6478f87e32073f38e319ff28
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -50,7 +50,7 @@ It does not check:
|
|
50
50
|
By default, MX records are required in DNS. MX or "mail exchanger" records
|
51
51
|
tell where to deliver email for the domain. Many domains run their
|
52
52
|
website on one provider (ISP, Heroku, etc.), and email on a different
|
53
|
-
provider (such as
|
53
|
+
provider (such as G Suite). Note that `example.com`, while
|
54
54
|
a valid domain name, does not have MX records.
|
55
55
|
|
56
56
|
```ruby
|
@@ -248,6 +248,16 @@ EmailAddress.normal("HIRO@こんにちは世界.com")
|
|
248
248
|
EmailAddress.normal("hiro@xn--28j2a3ar1pp75ovm7c.com", host_encoding: :unicode)
|
249
249
|
#=> "hiro@こんにちは世界.com"
|
250
250
|
```
|
251
|
+
As of release 0.1.17, exchanger_match is no longer used for host provider
|
252
|
+
determination, which designated the set of rules for that domain.
|
253
|
+
Sometimes, as in Google-hosted domains, the address
|
254
|
+
rules are different, notably the optional dots in mailboxes for gmail.com
|
255
|
+
accounts do not apply to other private domains hosted at google.
|
256
|
+
|
257
|
+
To access the provider service, you can now call:
|
258
|
+
|
259
|
+
EmailAddress.new("user@hosteddomain.com").host.hosted_provider
|
260
|
+
|
251
261
|
|
252
262
|
#### Rails Validator
|
253
263
|
|
@@ -446,14 +456,16 @@ EmailAddress::Config.provider(:github,
|
|
446
456
|
You can override the default error messages as follows:
|
447
457
|
|
448
458
|
```ruby
|
449
|
-
EmailAddress::Config.error_messages(
|
459
|
+
EmailAddress::Config.error_messages({
|
450
460
|
invalid_address: "Invalid Email Address",
|
451
461
|
invalid_mailbox: "Invalid Recipient/Mailbox",
|
452
462
|
invalid_host: "Invalid Host/Domain Name",
|
453
463
|
exceeds_size: "Address too long",
|
454
464
|
not_allowed: "Address is not allowed",
|
455
|
-
incomplete_domain: "Domain name is incomplete")
|
465
|
+
incomplete_domain: "Domain name is incomplete"}, 'en')
|
456
466
|
```
|
467
|
+
Note: Release 0.1.14 fixed setting error messages by locale.
|
468
|
+
Also, it will accept a ruby "collected" hash as before,
|
457
469
|
|
458
470
|
Full translation support would be ideal though.
|
459
471
|
|
@@ -540,6 +552,33 @@ The value is an array of match tokens.
|
|
540
552
|
* host_match: %w(.org example.com hotmail. user*@ sub.*.com)
|
541
553
|
* exchanger_match: %w(google.com 127.0.0.1 10.9.8.0/24 ::1/64)
|
542
554
|
|
555
|
+
### Namespace conflict resolution
|
556
|
+
|
557
|
+
If your application already uses the `EmailAddress` class name,
|
558
|
+
it's possible to create an alias prior to loading your code:
|
559
|
+
|
560
|
+
For a Rails application, you can do this in `config/application.rb`
|
561
|
+
after the `Bundler.require` line, usually:
|
562
|
+
|
563
|
+
```ruby
|
564
|
+
Bundler.require(*Rails.groups)
|
565
|
+
```
|
566
|
+
|
567
|
+
Add these lines immediately after that point:
|
568
|
+
|
569
|
+
```ruby
|
570
|
+
EmailAddressValidator = EmailAddress
|
571
|
+
Object.send(:remove_const, :EmailAddress)
|
572
|
+
```
|
573
|
+
|
574
|
+
Then your application loads with your EmailAddress class. You may
|
575
|
+
then use this gem with `EmailAddressValidator` or whatever name you
|
576
|
+
gave it above:
|
577
|
+
|
578
|
+
```ruby
|
579
|
+
EmailAddressValidator.valid?("clark.kent@gmail.com") # => true
|
580
|
+
```
|
581
|
+
|
543
582
|
## Notes
|
544
583
|
|
545
584
|
#### Internationalization
|
data/email_address.gemspec
CHANGED
@@ -13,6 +13,7 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = "https://github.com/afair/email_address"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
+
#spec.required_ruby_version = ">= 2.3.0"
|
16
17
|
spec.files = `git ls-files`.split($/)
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
data/lib/email_address.rb
CHANGED
@@ -40,31 +40,30 @@ module EmailAddress
|
|
40
40
|
# secret to use in options[:secret]
|
41
41
|
class << self
|
42
42
|
(%i[valid? error normal redact munge canonical reference base srs] &
|
43
|
-
|
43
|
+
Address.public_instance_methods
|
44
44
|
).each do |proxy_method|
|
45
45
|
define_method(proxy_method) do |*args, &block|
|
46
|
-
|
46
|
+
Address.new(*args).public_send(proxy_method, &block)
|
47
47
|
end
|
48
48
|
end
|
49
|
-
end
|
50
|
-
|
51
49
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
50
|
+
# Creates an instance of this email address.
|
51
|
+
# This is a short-cut to EmailAddress::Address.new
|
52
|
+
def new(email_address, config={})
|
53
|
+
Address.new(email_address, config)
|
54
|
+
end
|
57
55
|
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
def new_redacted(email_address, config={})
|
57
|
+
Address.new(Address.new(email_address, config).redact)
|
58
|
+
end
|
61
59
|
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
def new_canonical(email_address, config={})
|
61
|
+
Address.new(Address.new(email_address, config).canonical, config)
|
62
|
+
end
|
65
63
|
|
66
|
-
|
67
|
-
|
68
|
-
|
64
|
+
# Does the email address match any of the given rules
|
65
|
+
def matches?(email_address, rules, config={})
|
66
|
+
Address.new(email_address, config).matches?(rules)
|
67
|
+
end
|
69
68
|
end
|
70
69
|
end
|
@@ -35,10 +35,10 @@ module EmailAddress
|
|
35
35
|
|
36
36
|
def validate_email(r,f)
|
37
37
|
return if r[f].nil?
|
38
|
-
e =
|
38
|
+
e = Address.new(r[f])
|
39
39
|
unless e.valid?
|
40
40
|
r.errors[f] << (@opt[:message] ||
|
41
|
-
|
41
|
+
Config.error_messages[:invalid_address] ||
|
42
42
|
"Invalid Email Address")
|
43
43
|
end
|
44
44
|
end
|
@@ -1,46 +1,45 @@
|
|
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
|
8
|
-
# (EmailAddress::Local) and Host (
|
8
|
+
# (EmailAddress::Local) and Host (EmailAddress::Host) parts.
|
9
9
|
class Address
|
10
10
|
include Comparable
|
11
|
-
include
|
11
|
+
include Rewriter
|
12
12
|
|
13
13
|
attr_accessor :original, :local, :host, :config, :reason
|
14
14
|
|
15
|
-
CONVENTIONAL_REGEX = /\A#{
|
16
|
-
@#{
|
17
|
-
STANDARD_REGEX
|
18
|
-
@#{
|
19
|
-
RELAXED_REGEX
|
20
|
-
@#{
|
15
|
+
CONVENTIONAL_REGEX = /\A#{Local::CONVENTIONAL_MAILBOX_WITHIN}
|
16
|
+
@#{Host::DNS_HOST_REGEX}\z/x
|
17
|
+
STANDARD_REGEX = /\A#{Local::STANDARD_LOCAL_WITHIN}
|
18
|
+
@#{Host::DNS_HOST_REGEX}\z/x
|
19
|
+
RELAXED_REGEX = /\A#{Local::RELAXED_MAILBOX_WITHIN}
|
20
|
+
@#{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
|
-
@
|
34
|
-
@
|
35
|
-
@error = @error_message = nil
|
25
|
+
def initialize(email_address, config = {})
|
26
|
+
@config = Config.new(config)
|
27
|
+
@original = email_address
|
28
|
+
email_address = (email_address || "").strip
|
29
|
+
email_address = parse_rewritten(email_address) unless config[:skip_rewrite]
|
30
|
+
local, host = Address.split_local_host(email_address)
|
31
|
+
|
32
|
+
@host = Host.new(host, @config)
|
33
|
+
@local = Local.new(local, @config, @host)
|
34
|
+
@error = @error_message = nil
|
36
35
|
end
|
37
36
|
|
38
37
|
# Given an email address, this returns an array of [local, host] parts
|
39
38
|
def self.split_local_host(email)
|
40
|
-
if lh = email.match(/(.+)@(.+)/)
|
41
|
-
lh.to_a[1,2]
|
39
|
+
if (lh = email.match(/(.+)@(.+)/))
|
40
|
+
lh.to_a[1, 2]
|
42
41
|
else
|
43
|
-
[email,
|
42
|
+
[email, ""]
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
@@ -54,26 +53,26 @@ module EmailAddress
|
|
54
53
|
|
55
54
|
# Everything to the left of the @ in the address, called the local part.
|
56
55
|
def left
|
57
|
-
|
56
|
+
local.to_s
|
58
57
|
end
|
59
58
|
|
60
59
|
# Returns the mailbox portion of the local port, with no tags. Usually, this
|
61
60
|
# can be considered the user account or role account names. Some systems
|
62
61
|
# employ dynamic email addresses which don't have the same meaning.
|
63
62
|
def mailbox
|
64
|
-
|
63
|
+
local.mailbox
|
65
64
|
end
|
66
65
|
|
67
66
|
# Returns the tag part of the local address, or nil if not given.
|
68
67
|
def tag
|
69
|
-
|
68
|
+
local.tag
|
70
69
|
end
|
71
70
|
|
72
71
|
# Retuns any comments parsed from the local part of the email address.
|
73
72
|
# This is retained for inspection after construction, even if it is
|
74
73
|
# removed from the normalized email address.
|
75
74
|
def comment
|
76
|
-
|
75
|
+
local.comment
|
77
76
|
end
|
78
77
|
|
79
78
|
############################################################################
|
@@ -87,8 +86,8 @@ module EmailAddress
|
|
87
86
|
def host_name
|
88
87
|
@host.host_name
|
89
88
|
end
|
90
|
-
alias
|
91
|
-
alias
|
89
|
+
alias right host_name
|
90
|
+
alias hostname host_name
|
92
91
|
|
93
92
|
# Returns the ESP (Email Service Provider) or ISP name derived
|
94
93
|
# using the provider configuration rules.
|
@@ -104,18 +103,18 @@ module EmailAddress
|
|
104
103
|
def normal
|
105
104
|
if !@original
|
106
105
|
@original
|
107
|
-
elsif
|
106
|
+
elsif local.to_s.size == 0
|
108
107
|
""
|
109
|
-
elsif
|
110
|
-
|
108
|
+
elsif host.to_s.size == 0
|
109
|
+
local.to_s
|
111
110
|
else
|
112
|
-
"#{
|
111
|
+
"#{local}@#{host}"
|
113
112
|
end
|
114
113
|
end
|
115
|
-
alias
|
114
|
+
alias to_s normal
|
116
115
|
|
117
116
|
def inspect
|
118
|
-
"
|
117
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} address=\"#{self}\">"
|
119
118
|
end
|
120
119
|
|
121
120
|
# Returns the canonical email address according to the provider
|
@@ -123,56 +122,56 @@ module EmailAddress
|
|
123
122
|
# spaves and comments and tags, and any extraneous part of the address
|
124
123
|
# not considered a unique account by the provider.
|
125
124
|
def canonical
|
126
|
-
c =
|
127
|
-
c += "@" +
|
125
|
+
c = local.canonical
|
126
|
+
c += "@" + host.canonical if host.canonical && host.canonical > " "
|
128
127
|
c
|
129
128
|
end
|
130
129
|
|
131
130
|
# True if the given address is already in it's canonical form.
|
132
131
|
def canonical?
|
133
|
-
|
132
|
+
canonical == to_s
|
134
133
|
end
|
135
134
|
|
136
135
|
# The base address is the mailbox, without tags, and host.
|
137
136
|
def base
|
138
|
-
|
137
|
+
mailbox + "@" + hostname
|
139
138
|
end
|
140
139
|
|
141
140
|
# Returns the redacted form of the address
|
142
141
|
# This format is defined by this libaray, and may change as usage increases.
|
143
142
|
# 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 += "@" +
|
143
|
+
def redact(digest = :sha1)
|
144
|
+
raise "Unknown Digest type: #{digest}" unless %i[sha1 md5].include?(digest)
|
145
|
+
return to_s if local.redacted?
|
146
|
+
r = %({#{send(digest)}})
|
147
|
+
r += "@" + host.to_s if host.to_s && host.to_s > " "
|
149
148
|
r
|
150
149
|
end
|
151
150
|
|
152
151
|
# True if the address is already in the redacted state.
|
153
152
|
def redacted?
|
154
|
-
|
153
|
+
local.redacted?
|
155
154
|
end
|
156
155
|
|
157
156
|
# Returns the munged form of the address, by default "mailbox@domain.tld"
|
158
157
|
# returns "ma*****@do*****".
|
159
158
|
def munge
|
160
|
-
[
|
159
|
+
[local.munge, host.munge].join("@")
|
161
160
|
end
|
162
161
|
|
163
162
|
# Returns and MD5 of the canonical address form. Some cross-system systems
|
164
163
|
# use the email address MD5 instead of the actual address to refer to the
|
165
164
|
# same shared user identity without exposing the actual address when it
|
166
165
|
# is not known in common.
|
167
|
-
def reference(form
|
168
|
-
Digest::MD5.hexdigest(
|
166
|
+
def reference(form = :base)
|
167
|
+
Digest::MD5.hexdigest(send(form))
|
169
168
|
end
|
170
|
-
alias
|
169
|
+
alias md5 reference
|
171
170
|
|
172
171
|
# This returns the SHA1 digest (in a hex string) of the canonical email
|
173
172
|
# address. See #md5 for more background.
|
174
|
-
def sha1(form
|
175
|
-
Digest::SHA1.hexdigest((
|
173
|
+
def sha1(form = :base)
|
174
|
+
Digest::SHA1.hexdigest((send(form) || "") + (@config[:sha1_secret] || ""))
|
176
175
|
end
|
177
176
|
|
178
177
|
#---------------------------------------------------------------------------
|
@@ -182,41 +181,41 @@ module EmailAddress
|
|
182
181
|
# Equal matches the normalized version of each address. Use the Threequal to check
|
183
182
|
# for match on canonical or redacted versions of addresses
|
184
183
|
def ==(other_email)
|
185
|
-
|
184
|
+
to_s == other_email.to_s
|
186
185
|
end
|
187
|
-
alias
|
188
|
-
alias
|
186
|
+
alias eql? ==
|
187
|
+
alias equal? ==
|
189
188
|
|
190
189
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
191
190
|
# of this addres with another, using the canonical or redacted forms.
|
192
191
|
def same_as?(other_email)
|
193
192
|
if other_email.is_a?(String)
|
194
|
-
other_email =
|
193
|
+
other_email = Address.new(other_email)
|
195
194
|
end
|
196
195
|
|
197
|
-
|
198
|
-
|
199
|
-
|
196
|
+
canonical == other_email.canonical ||
|
197
|
+
redact == other_email.canonical ||
|
198
|
+
canonical == other_email.redact
|
200
199
|
end
|
201
|
-
alias
|
200
|
+
alias include? same_as?
|
202
201
|
|
203
202
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
204
203
|
# of this addres with another, using the normalized form.
|
205
204
|
def <=>(other_email)
|
206
|
-
|
205
|
+
to_s <=> other_email.to_s
|
207
206
|
end
|
208
207
|
|
209
208
|
# Address matches one of these Matcher rule patterns
|
210
209
|
def matches?(*rules)
|
211
210
|
rules.flatten!
|
212
|
-
match
|
213
|
-
match ||=
|
211
|
+
match = local.matches?(rules)
|
212
|
+
match ||= host.matches?(rules)
|
214
213
|
return match if match
|
215
214
|
|
216
215
|
# Does "root@*.com" match "root@example.com" domain name
|
217
216
|
rules.each do |r|
|
218
|
-
if r
|
219
|
-
return r if File.fnmatch?(r,
|
217
|
+
if r.match(/.+@.+/)
|
218
|
+
return r if File.fnmatch?(r, to_s)
|
220
219
|
end
|
221
220
|
end
|
222
221
|
false
|
@@ -228,24 +227,24 @@ module EmailAddress
|
|
228
227
|
|
229
228
|
# Returns true if this address is considered valid according to the format
|
230
229
|
# configured for its provider, It test the normalized form.
|
231
|
-
def valid?(options={})
|
230
|
+
def valid?(options = {})
|
232
231
|
@error = nil
|
233
|
-
unless
|
234
|
-
return set_error
|
232
|
+
unless local.valid?
|
233
|
+
return set_error local.error
|
235
234
|
end
|
236
|
-
unless
|
237
|
-
return set_error
|
235
|
+
unless host.valid?
|
236
|
+
return set_error host.error_message
|
238
237
|
end
|
239
|
-
if @config[:address_size] && !@config[:address_size].include?(
|
238
|
+
if @config[:address_size] && !@config[:address_size].include?(to_s.size)
|
240
239
|
return set_error :exceeds_size
|
241
240
|
end
|
242
241
|
if @config[:address_validation].is_a?(Proc)
|
243
|
-
unless @config[:address_validation].call(
|
242
|
+
unless @config[:address_validation].call(to_s)
|
244
243
|
return set_error :not_allowed
|
245
244
|
end
|
246
245
|
else
|
247
|
-
return false unless
|
248
|
-
return false unless
|
246
|
+
return false unless local.valid?
|
247
|
+
return false unless host.valid?
|
249
248
|
end
|
250
249
|
true
|
251
250
|
end
|
@@ -254,42 +253,37 @@ module EmailAddress
|
|
254
253
|
# as an email address check, but is provided to assist in problem resolution.
|
255
254
|
# If you abuse this, you *could* be blocked by the ESP.
|
256
255
|
def connect
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
256
|
+
smtp = Net::SMTP.new(host_name || ip_address)
|
257
|
+
smtp.start(@config[:smtp_helo_name] || "localhost")
|
258
|
+
smtp.mailfrom @config[:smtp_mail_from] || "postmaster@localhost"
|
259
|
+
smtp.rcptto to_s
|
260
|
+
# p [:connect]
|
261
|
+
smtp.finish
|
262
|
+
true
|
263
|
+
rescue Net::SMTPUnknownError => e
|
264
|
+
set_error(:address_unknown, e.to_s)
|
265
|
+
rescue Net::SMTPFatalError => e
|
266
|
+
set_error(:address_unknown, e.to_s)
|
267
|
+
rescue SocketError => e
|
268
|
+
set_error(:address_unknown, e.to_s)
|
269
|
+
ensure
|
270
|
+
if smtp&.started?
|
263
271
|
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
272
|
end
|
273
|
+
!!@error
|
277
274
|
end
|
278
275
|
|
279
|
-
def set_error(err, reason=nil)
|
276
|
+
def set_error(err, reason = nil)
|
280
277
|
@error = err
|
281
|
-
@reason= reason
|
282
|
-
@error_message =
|
278
|
+
@reason = reason
|
279
|
+
@error_message = Config.error_message(err)
|
283
280
|
false
|
284
281
|
end
|
285
282
|
|
286
|
-
|
287
|
-
@error_message
|
288
|
-
end
|
283
|
+
attr_reader :error_message
|
289
284
|
|
290
285
|
def error
|
291
|
-
|
286
|
+
valid? ? nil : @error_message
|
292
287
|
end
|
293
|
-
|
294
288
|
end
|
295
289
|
end
|