email_address 0.1.20 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +18 -0
- data/Gemfile +1 -1
- data/README.md +27 -2
- data/Rakefile +2 -2
- data/email_address.gemspec +22 -23
- data/lib/email_address.rb +8 -9
- data/lib/email_address/active_record_validator.rb +5 -9
- data/lib/email_address/address.rb +25 -19
- data/lib/email_address/config.rb +12 -1
- data/lib/email_address/exchanger.rb +5 -19
- data/lib/email_address/host.rb +21 -38
- data/lib/email_address/local.rb +102 -105
- data/lib/email_address/rewriter.rb +28 -31
- data/lib/email_address/version.rb +1 -1
- data/test/activerecord/test_ar.rb +17 -13
- data/test/activerecord/user.rb +31 -30
- data/test/email_address/test_address.rb +44 -25
- data/test/email_address/test_config.rb +8 -8
- data/test/email_address/test_exchanger.rb +6 -7
- data/test/email_address/test_local.rb +32 -36
- data/test/email_address/test_rewriter.rb +2 -5
- data/test/test_aliasing.rb +1 -2
- data/test/test_email_address.rb +14 -18
- data/test/test_helper.rb +9 -8
- metadata +29 -18
- data/.travis.yml +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9667acc31d6ce84fd3acb2e8e2f3ea04e496df5ff9dc5f89815fa148df1113e
|
4
|
+
data.tar.gz: e17aa2b229cb28ca80dcbf37226a5411052758db1e15cee0ddc2038d5244de42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 908fa1ffaa5692b07fa4e586a66a8fdf6395c1a51013ee389e0437df175d6712ecc57831b6616cfabd28463a87c7a60df9b6ba8382cba0c58d8449e4ada51c6d
|
7
|
+
data.tar.gz: a9f2a9bb8280118af5b21b08e8ca2273b16f3d5f700b91cb7c36b22444a7d5878e54a45292a2080243aff6fa4d31cd2ca3af4bd664f182b49d52ab42b864c711
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: CI Build
|
2
|
+
on: [push, pull_request]
|
3
|
+
jobs:
|
4
|
+
build:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
strategy:
|
7
|
+
matrix:
|
8
|
+
ruby-version: [2.6, 2.7, 3.0, jruby]
|
9
|
+
steps:
|
10
|
+
- uses: actions/checkout@v2
|
11
|
+
- uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: ${{ matrix.ruby-version }}
|
14
|
+
bundler-cache: true # runs `bundle install` and caches installed gems automatically
|
15
|
+
- name: Install dependencies
|
16
|
+
run: bundle install
|
17
|
+
- name: Run tests
|
18
|
+
run: bundle exec rake
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Email Address
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/email_address.svg)](http://rubygems.org/gems/email_address)
|
4
|
-
[![Build
|
4
|
+
[![CI Build](https://github.com/afair/email_address/actions/workflows/ci.yml/badge.svg)](https://github.com/afair/email_address/actions/workflows/ci.yml)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/afair/email_address/badges/gpa.svg)](https://codeclimate.com/github/afair/email_address)
|
6
6
|
|
7
7
|
The `email_address` gem provides a ruby language library for working
|
@@ -98,7 +98,7 @@ introduces terms to distinguish types of email addresses.
|
|
98
98
|
madness!."()<>[]:,;@\\\"!#$%&'*+-/=?^_`{}| ~.a(comment )"@example.org
|
99
99
|
|
100
100
|
* *Base* - A unique mailbox without tags. For gmail, is uses the incoming
|
101
|
-
punctation, essential when building an MD5 or
|
101
|
+
punctation, essential when building an MD5, SHA1, or SHA256 to match services
|
102
102
|
like Gravatar, and email address digest interchange.
|
103
103
|
|
104
104
|
* *Canonical* - An unique account address, lower-cased, without the
|
@@ -234,6 +234,7 @@ Here are some other methods that are available.
|
|
234
234
|
```ruby
|
235
235
|
email.redact #=> "{bea3f3560a757f8142d38d212a931237b218eb5e}@gmail.com"
|
236
236
|
email.sha1 #=> "bea3f3560a757f8142d38d212a931237b218eb5e"
|
237
|
+
email.sha256 #=> "9e2a0270f2d6778e5f647fc9eaf6992705ca183c23d1ed1166586fd54e859f75"
|
237
238
|
email.md5 #=> "c5be3597c391169a5ad2870f9ca51901"
|
238
239
|
email.host_name #=> "gmail.com"
|
239
240
|
email.provider #=> :google
|
@@ -272,6 +273,25 @@ class User < ActiveRecord::Base
|
|
272
273
|
end
|
273
274
|
```
|
274
275
|
|
276
|
+
#### Rails I18n
|
277
|
+
|
278
|
+
Copy and adapt `lib/email_address/messages.yaml` into your locales and
|
279
|
+
create an after initialization callback:
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
# config/initializers/email_address.rb
|
283
|
+
|
284
|
+
Rails.application.config.after_initialize do
|
285
|
+
I18n.available_locales.each do |locale|
|
286
|
+
translations = I18n.t(:email_address, locale: locale)
|
287
|
+
|
288
|
+
next unless translations.is_a? Hash
|
289
|
+
|
290
|
+
EmailAddress::Config.error_messages translations.transform_keys(&:to_s), locale.to_s
|
291
|
+
end
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
275
295
|
#### Rails Email Address Type Attribute
|
276
296
|
|
277
297
|
Initial support is provided for Active Record 5.0 attributes API.
|
@@ -476,6 +496,11 @@ Full translation support would be ideal though.
|
|
476
496
|
the SHA1 Digest, making it unique to your application so it can't easily be
|
477
497
|
discovered by comparing against a known list of email/sha1 pairs.
|
478
498
|
|
499
|
+
* sha256_secret -
|
500
|
+
This application-level secret is appended to the email_address to compute
|
501
|
+
the SHA256 Digest, making it unique to your application so it can't easily be
|
502
|
+
discovered by comparing against a known list of email/sha256 pairs.
|
503
|
+
|
479
504
|
* munge_string - "*****", the string to replace into munged addresses.
|
480
505
|
|
481
506
|
For local part configuration:
|
data/Rakefile
CHANGED
data/email_address.gemspec
CHANGED
@@ -1,37 +1,36 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require "email_address/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
11
|
-
spec.description
|
12
|
-
spec.summary
|
13
|
-
spec.homepage
|
14
|
-
spec.license
|
6
|
+
spec.name = "email_address"
|
7
|
+
spec.version = EmailAddress::VERSION
|
8
|
+
spec.authors = ["Allen Fair"]
|
9
|
+
spec.email = ["allen.fair@gmail.com"]
|
10
|
+
spec.description = "The EmailAddress Gem to work with and validate email addresses."
|
11
|
+
spec.summary = "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."
|
12
|
+
spec.homepage = "https://github.com/afair/email_address"
|
13
|
+
spec.license = "MIT"
|
15
14
|
|
16
|
-
|
17
|
-
spec.files
|
18
|
-
spec.executables
|
19
|
-
spec.test_files
|
15
|
+
spec.required_ruby_version = ">= 2.5", "< 4"
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
19
|
spec.require_paths = ["lib"]
|
21
20
|
|
22
21
|
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency "minitest",
|
24
|
-
spec.add_development_dependency "bundler"
|
25
|
-
if RUBY_PLATFORM ==
|
26
|
-
spec.add_development_dependency "activerecord", "
|
27
|
-
spec.add_development_dependency "activerecord-jdbcsqlite3-adapter",
|
22
|
+
spec.add_development_dependency "minitest", "~> 5.11"
|
23
|
+
spec.add_development_dependency "bundler" # , "~> 1.16.0"
|
24
|
+
if RUBY_PLATFORM == "java"
|
25
|
+
spec.add_development_dependency "activerecord", "~> 5.2.6"
|
26
|
+
spec.add_development_dependency "activerecord-jdbcsqlite3-adapter", "~> 52.7"
|
28
27
|
else
|
29
|
-
spec.add_development_dependency "activerecord", "~>
|
28
|
+
spec.add_development_dependency "activerecord", "~> 6.1.4"
|
30
29
|
spec.add_development_dependency "sqlite3"
|
31
30
|
end
|
32
|
-
#spec.add_development_dependency "codeclimate-test-reporter"
|
33
31
|
spec.add_development_dependency "simplecov"
|
32
|
+
spec.add_development_dependency "pry"
|
33
|
+
spec.add_development_dependency "standard", "~> 1.1.1"
|
34
34
|
|
35
35
|
spec.add_dependency "simpleidn"
|
36
|
-
spec.add_dependency "netaddr", '>= 2.0.4', '< 3'
|
37
36
|
end
|
data/lib/email_address.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
# EmailAddress parses and validates email addresses against RFC standard,
|
4
4
|
# conventional, canonical, formats and other special uses.
|
5
5
|
module EmailAddress
|
6
|
-
|
7
6
|
require "email_address/config"
|
8
7
|
require "email_address/exchanger"
|
9
8
|
require "email_address/host"
|
@@ -49,21 +48,21 @@ module EmailAddress
|
|
49
48
|
|
50
49
|
# Creates an instance of this email address.
|
51
50
|
# This is a short-cut to EmailAddress::Address.new
|
52
|
-
def new(email_address, config={})
|
53
|
-
Address.new(email_address, config)
|
51
|
+
def new(email_address, config = {}, locale = "en")
|
52
|
+
Address.new(email_address, config, locale)
|
54
53
|
end
|
55
54
|
|
56
|
-
def new_redacted(email_address, config={})
|
57
|
-
Address.new(Address.new(email_address, config).redact)
|
55
|
+
def new_redacted(email_address, config = {}, locale = "en")
|
56
|
+
Address.new(Address.new(email_address, config, locale).redact)
|
58
57
|
end
|
59
58
|
|
60
|
-
def new_canonical(email_address, config={})
|
61
|
-
Address.new(Address.new(email_address, config).canonical, config)
|
59
|
+
def new_canonical(email_address, config = {}, locale = "en")
|
60
|
+
Address.new(Address.new(email_address, config, locale).canonical, config)
|
62
61
|
end
|
63
62
|
|
64
63
|
# 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)
|
64
|
+
def matches?(email_address, rules, config = {}, locale = "en")
|
65
|
+
Address.new(email_address, config, locale).matches?(rules)
|
67
66
|
end
|
68
67
|
end
|
69
68
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module EmailAddress
|
4
|
-
|
5
4
|
# ActiveRecord validator class for validating an email
|
6
5
|
# address with this library.
|
7
6
|
# Note the initialization happens once per process.
|
@@ -16,14 +15,13 @@ module EmailAddress
|
|
16
15
|
# Default field: :email or :email_address (first found)
|
17
16
|
#
|
18
17
|
class ActiveRecordValidator < ActiveModel::Validator
|
19
|
-
|
20
|
-
def initialize(options={})
|
18
|
+
def initialize(options = {})
|
21
19
|
@opt = options
|
22
20
|
end
|
23
21
|
|
24
22
|
def validate(r)
|
25
23
|
if @opt[:fields]
|
26
|
-
@opt[:fields].each {|f| validate_email(r, f) }
|
24
|
+
@opt[:fields].each { |f| validate_email(r, f) }
|
27
25
|
elsif @opt[:field]
|
28
26
|
validate_email(r, @opt[:field])
|
29
27
|
elsif r.respond_to? :email
|
@@ -33,17 +31,15 @@ module EmailAddress
|
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
|
-
def validate_email(r,f)
|
34
|
+
def validate_email(r, f)
|
37
35
|
return if r[f].nil?
|
38
36
|
e = Address.new(r[f])
|
39
37
|
unless e.valid?
|
40
38
|
error_message = @opt[:message] ||
|
41
|
-
|
42
|
-
|
39
|
+
Config.error_message(:invalid_address, I18n.locale.to_s) ||
|
40
|
+
"Invalid Email Address"
|
43
41
|
r.errors.add(f, error_message)
|
44
42
|
end
|
45
43
|
end
|
46
|
-
|
47
44
|
end
|
48
|
-
|
49
45
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "digest/sha1"
|
4
|
+
require "digest/sha2"
|
4
5
|
require "digest/md5"
|
5
6
|
|
6
7
|
module EmailAddress
|
@@ -10,7 +11,7 @@ module EmailAddress
|
|
10
11
|
include Comparable
|
11
12
|
include Rewriter
|
12
13
|
|
13
|
-
attr_accessor :original, :local, :host, :config, :reason
|
14
|
+
attr_accessor :original, :local, :host, :config, :reason, :locale
|
14
15
|
|
15
16
|
CONVENTIONAL_REGEX = /\A#{Local::CONVENTIONAL_MAILBOX_WITHIN}
|
16
17
|
@#{Host::DNS_HOST_REGEX}\z/x
|
@@ -22,15 +23,16 @@ module EmailAddress
|
|
22
23
|
# Given an email address of the form "local@hostname", this sets up the
|
23
24
|
# instance, and initializes the address to the "normalized" format of the
|
24
25
|
# address. The original string is available in the #original method.
|
25
|
-
def initialize(email_address, config = {})
|
26
|
+
def initialize(email_address, config = {}, locale = "en")
|
26
27
|
@config = Config.new(config)
|
27
28
|
@original = email_address
|
29
|
+
@locale = locale
|
28
30
|
email_address = (email_address || "").strip
|
29
31
|
email_address = parse_rewritten(email_address) unless config[:skip_rewrite]
|
30
32
|
local, host = Address.split_local_host(email_address)
|
31
33
|
|
32
|
-
@host = Host.new(host, @config)
|
33
|
-
@local = Local.new(local, @config, @host)
|
34
|
+
@host = Host.new(host, @config, locale)
|
35
|
+
@local = Local.new(local, @config, @host, locale)
|
34
36
|
@error = @error_message = nil
|
35
37
|
end
|
36
38
|
|
@@ -86,8 +88,8 @@ module EmailAddress
|
|
86
88
|
def host_name
|
87
89
|
@host.host_name
|
88
90
|
end
|
89
|
-
|
90
|
-
|
91
|
+
alias_method :right, :host_name
|
92
|
+
alias_method :hostname, :host_name
|
91
93
|
|
92
94
|
# Returns the ESP (Email Service Provider) or ISP name derived
|
93
95
|
# using the provider configuration rules.
|
@@ -111,7 +113,7 @@ module EmailAddress
|
|
111
113
|
"#{local}@#{host}"
|
112
114
|
end
|
113
115
|
end
|
114
|
-
|
116
|
+
alias_method :to_s, :normal
|
115
117
|
|
116
118
|
def inspect
|
117
119
|
"#<#{self.class}:0x#{object_id.to_s(16)} address=\"#{self}\">"
|
@@ -159,32 +161,36 @@ module EmailAddress
|
|
159
161
|
[local.munge, host.munge].join("@")
|
160
162
|
end
|
161
163
|
|
162
|
-
# Returns and MD5 of the
|
164
|
+
# Returns and MD5 of the base address form. Some cross-system systems
|
163
165
|
# use the email address MD5 instead of the actual address to refer to the
|
164
166
|
# same shared user identity without exposing the actual address when it
|
165
167
|
# is not known in common.
|
166
168
|
def reference(form = :base)
|
167
169
|
Digest::MD5.hexdigest(send(form))
|
168
170
|
end
|
169
|
-
|
171
|
+
alias_method :md5, :reference
|
170
172
|
|
171
|
-
# 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
|
172
174
|
# address. See #md5 for more background.
|
173
175
|
def sha1(form = :base)
|
174
176
|
Digest::SHA1.hexdigest((send(form) || "") + (@config[:sha1_secret] || ""))
|
175
177
|
end
|
176
178
|
|
179
|
+
def sha256(form = :base)
|
180
|
+
Digest::SHA256.hexdigest((send(form) || "") + (@config[:sha256_secret] || ""))
|
181
|
+
end
|
182
|
+
|
177
183
|
#---------------------------------------------------------------------------
|
178
184
|
# Comparisons & Matching
|
179
185
|
#---------------------------------------------------------------------------
|
180
186
|
|
181
187
|
# Equal matches the normalized version of each address. Use the Threequal to check
|
182
188
|
# for match on canonical or redacted versions of addresses
|
183
|
-
def ==(
|
184
|
-
to_s ==
|
189
|
+
def ==(other)
|
190
|
+
to_s == other.to_s
|
185
191
|
end
|
186
|
-
|
187
|
-
|
192
|
+
alias_method :eql?, :==
|
193
|
+
alias_method :equal?, :==
|
188
194
|
|
189
195
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
190
196
|
# of this addres with another, using the canonical or redacted forms.
|
@@ -197,12 +203,12 @@ module EmailAddress
|
|
197
203
|
redact == other_email.canonical ||
|
198
204
|
canonical == other_email.redact
|
199
205
|
end
|
200
|
-
|
206
|
+
alias_method :include?, :same_as?
|
201
207
|
|
202
208
|
# Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
|
203
209
|
# of this addres with another, using the normalized form.
|
204
|
-
def <=>(
|
205
|
-
to_s <=>
|
210
|
+
def <=>(other)
|
211
|
+
to_s <=> other.to_s
|
206
212
|
end
|
207
213
|
|
208
214
|
# Address matches one of these Matcher rule patterns
|
@@ -214,7 +220,7 @@ module EmailAddress
|
|
214
220
|
|
215
221
|
# Does "root@*.com" match "root@example.com" domain name
|
216
222
|
rules.each do |r|
|
217
|
-
if
|
223
|
+
if /.+@.+/.match?(r)
|
218
224
|
return r if File.fnmatch?(r, to_s)
|
219
225
|
end
|
220
226
|
end
|
@@ -276,7 +282,7 @@ module EmailAddress
|
|
276
282
|
def set_error(err, reason = nil)
|
277
283
|
@error = err
|
278
284
|
@reason = reason
|
279
|
-
@error_message = Config.error_message(err)
|
285
|
+
@error_message = Config.error_message(err, locale)
|
280
286
|
false
|
281
287
|
end
|
282
288
|
|
data/lib/email_address/config.rb
CHANGED
@@ -17,6 +17,11 @@ module EmailAddress
|
|
17
17
|
# the SHA1 Digest, making it unique to your application so it can't easily be
|
18
18
|
# discovered by comparing against a known list of email/sha1 pairs.
|
19
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
|
+
#
|
20
25
|
# For local part configuration:
|
21
26
|
# * local_downcase: true
|
22
27
|
# Downcase the local part. You probably want this for uniqueness.
|
@@ -104,6 +109,7 @@ module EmailAddress
|
|
104
109
|
dns_lookup: :mx, # :mx, :a, :off
|
105
110
|
dns_timeout: nil,
|
106
111
|
sha1_secret: "",
|
112
|
+
sha256_secret: "",
|
107
113
|
munge_string: "*****",
|
108
114
|
|
109
115
|
local_downcase: true,
|
@@ -188,9 +194,14 @@ module EmailAddress
|
|
188
194
|
# Customize your own error message text.
|
189
195
|
def self.error_messages(hash = {}, locale = "en", *extra)
|
190
196
|
hash = extra.first if extra.first.is_a? Hash
|
191
|
-
|
197
|
+
|
198
|
+
@errors[locale] ||= {}
|
199
|
+
@errors[locale]["email_address"] ||= {}
|
200
|
+
|
201
|
+
unless hash.nil? || hash.empty?
|
192
202
|
@errors[locale]["email_address"] = @errors[locale]["email_address"].merge(hash)
|
193
203
|
end
|
204
|
+
|
194
205
|
@errors[locale]["email_address"]
|
195
206
|
end
|
196
207
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "resolv"
|
4
|
-
require "netaddr"
|
5
4
|
require "socket"
|
6
5
|
|
7
6
|
module EmailAddress
|
@@ -58,8 +57,8 @@ module EmailAddress
|
|
58
57
|
|
59
58
|
ress = begin
|
60
59
|
dns.getresources(@host, Resolv::DNS::Resource::IN::MX)
|
61
|
-
|
62
|
-
|
60
|
+
rescue Resolv::ResolvTimeout
|
61
|
+
[]
|
63
62
|
end
|
64
63
|
|
65
64
|
records = ress.map { |r|
|
@@ -105,22 +104,9 @@ module EmailAddress
|
|
105
104
|
|
106
105
|
# Given a cidr (ip/bits) and ip address, returns true on match. Caches cidr object.
|
107
106
|
def in_cidr?(cidr)
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
next unless ip.include?(":")
|
112
|
-
rel = c.rel NetAddr::IPv6Net.parse(ip)
|
113
|
-
!rel.nil? && rel >= 0
|
114
|
-
end
|
115
|
-
elsif cidr.include?(".")
|
116
|
-
c = NetAddr::IPv4Net.parse(cidr)
|
117
|
-
return true if mx_ips.find do |ip|
|
118
|
-
next if ip.include?(":")
|
119
|
-
rel = c.rel NetAddr::IPv4Net.parse(ip)
|
120
|
-
!rel.nil? && rel >= 0
|
121
|
-
end
|
122
|
-
end
|
123
|
-
false
|
107
|
+
net = IPAddr.new(cidr)
|
108
|
+
found = mx_ips.detect { |ip| net.include?(IPAddr.new(ip)) }
|
109
|
+
!!found
|
124
110
|
end
|
125
111
|
end
|
126
112
|
end
|