email_address 0.1.19 → 0.2.2
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 +23 -23
- data/lib/email_address/active_record_validator.rb +5 -9
- data/lib/email_address/address.rb +29 -19
- data/lib/email_address/config.rb +13 -2
- data/lib/email_address/exchanger.rb +5 -19
- data/lib/email_address/host.rb +29 -45
- data/lib/email_address/local.rb +104 -105
- data/lib/email_address/rewriter.rb +28 -31
- data/lib/email_address/version.rb +1 -1
- data/lib/email_address.rb +8 -9
- data/test/activerecord/test_ar.rb +17 -13
- data/test/activerecord/user.rb +31 -30
- data/test/email_address/test_address.rb +46 -25
- data/test/email_address/test_config.rb +8 -8
- data/test/email_address/test_exchanger.rb +6 -7
- data/test/email_address/test_host.rb +2 -1
- data/test/email_address/test_local.rb +39 -35
- 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: c6784502377cd86fe2b6bb53c53ef7700673ed0317d2c96850ba0e9faad32c72
|
4
|
+
data.tar.gz: 5a558e95e390bf608225149fa2cb0948dcd9455607592e0c92156f34dced0807
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65cd1a6acccd37f3580762ba5d97e2956833c979a9fa639369385427bfe0ebf3cca693014d442ab530a005349e6fd36bce3d0b7372f432230bfcc4e0d3d15d79
|
7
|
+
data.tar.gz: 6543b62b3d0650717c81d2b25de2008fe6b898707a82c8779646364cb351654322958d38dfbf6b9420728a1eb7d6047d2d4205f0e11254e9d868930f6db74243
|
@@ -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, 3.1, 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,37 @@
|
|
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"
|
30
|
+
spec.add_development_dependency "standard", "~> 1.5.0"
|
31
31
|
end
|
32
|
-
#spec.add_development_dependency "
|
32
|
+
# spec.add_development_dependency "net-smtp"
|
33
33
|
spec.add_development_dependency "simplecov"
|
34
|
+
spec.add_development_dependency "pry"
|
34
35
|
|
35
36
|
spec.add_dependency "simpleidn"
|
36
|
-
spec.add_dependency "netaddr", '>= 2.0.4', '< 3'
|
37
37
|
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
|
@@ -252,6 +258,10 @@ module EmailAddress
|
|
252
258
|
# Connects to host to test if user can receive email. This should NOT be performed
|
253
259
|
# as an email address check, but is provided to assist in problem resolution.
|
254
260
|
# If you abuse this, you *could* be blocked by the ESP.
|
261
|
+
#
|
262
|
+
# NOTE: As of Ruby 3.1, Net::SMTP was moved from the standard library to the
|
263
|
+
# 'net-smtp' gem. In order to avoid adding that dependency for this experimental
|
264
|
+
# feature, please add the gem to your Gemfile and require it to use this feature.
|
255
265
|
def connect
|
256
266
|
smtp = Net::SMTP.new(host_name || ip_address)
|
257
267
|
smtp.start(@config[:smtp_helo_name] || "localhost")
|
@@ -276,7 +286,7 @@ module EmailAddress
|
|
276
286
|
def set_error(err, reason = nil)
|
277
287
|
@error = err
|
278
288
|
@reason = reason
|
279
|
-
@error_message = Config.error_message(err)
|
289
|
+
@error_message = Config.error_message(err, locale)
|
280
290
|
false
|
281
291
|
end
|
282
292
|
|
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,
|
@@ -182,15 +188,20 @@ module EmailAddress
|
|
182
188
|
end
|
183
189
|
|
184
190
|
def self.error_message(name, locale = "en")
|
185
|
-
@errors
|
191
|
+
@errors.dig(locale, "email_address", name.to_s) || name.to_s
|
186
192
|
end
|
187
193
|
|
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
|
data/lib/email_address/host.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require "simpleidn"
|
2
2
|
require "resolv"
|
3
|
-
require "netaddr"
|
4
|
-
require "net/smtp"
|
5
3
|
|
6
4
|
module EmailAddress
|
7
5
|
##############################################################################
|
@@ -34,15 +32,15 @@ module EmailAddress
|
|
34
32
|
attr_reader :host_name
|
35
33
|
attr_accessor :dns_name, :domain_name, :registration_name,
|
36
34
|
:tld, :tld2, :subdomains, :ip_address, :config, :provider,
|
37
|
-
:comment, :error_message, :reason
|
35
|
+
:comment, :error_message, :reason, :locale
|
38
36
|
MAX_HOST_LENGTH = 255
|
39
37
|
|
40
38
|
# Sometimes, you just need a Regexp...
|
41
|
-
DNS_HOST_REGEX = / [\p{L}\p{N}]+ (?: (?:
|
39
|
+
DNS_HOST_REGEX = / [\p{L}\p{N}]+ (?: (?: -{1,2} | \.) [\p{L}\p{N}]+ )*/x
|
42
40
|
|
43
41
|
# The IPv4 and IPv6 were lifted from Resolv::IPv?::Regex and tweaked to not
|
44
42
|
# \A...\z anchor at the edges.
|
45
|
-
|
43
|
+
IPV6_HOST_REGEX = /\[IPv6:
|
46
44
|
(?: (?:(?x-mi:
|
47
45
|
(?:[0-9A-Fa-f]{1,4}:){7}
|
48
46
|
[0-9A-Fa-f]{1,4}
|
@@ -61,7 +59,7 @@ module EmailAddress
|
|
61
59
|
(?: \d+)\.(?: \d+)\.(?: \d+)\.(?: \d+)
|
62
60
|
)))\]/ix
|
63
61
|
|
64
|
-
|
62
|
+
IPV4_HOST_REGEX = /\[((?x-mi:0
|
65
63
|
|1(?:[0-9][0-9]?)?
|
66
64
|
|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
|
67
65
|
|[3-9][0-9]?))\.((?x-mi:0
|
@@ -80,12 +78,13 @@ module EmailAddress
|
|
80
78
|
|
81
79
|
# Matches Host forms: DNS name, IPv4, or IPv6 formats
|
82
80
|
STANDARD_HOST_REGEX = /\A (?: #{DNS_HOST_REGEX}
|
83
|
-
| #{
|
81
|
+
| #{IPV4_HOST_REGEX} | #{IPV6_HOST_REGEX}) \z/ix
|
84
82
|
|
85
83
|
# host name -
|
86
84
|
# * host type - :email for an email host, :mx for exchanger host
|
87
|
-
def initialize(host_name, config = {})
|
85
|
+
def initialize(host_name, config = {}, locale = "en")
|
88
86
|
@original = host_name ||= ""
|
87
|
+
@locale = locale
|
89
88
|
config[:host_type] ||= :email
|
90
89
|
@config = config.is_a?(Hash) ? Config.new(config) : config
|
91
90
|
@error = @error_message = nil
|
@@ -104,7 +103,7 @@ module EmailAddress
|
|
104
103
|
dns_name
|
105
104
|
end
|
106
105
|
end
|
107
|
-
|
106
|
+
alias_method :to_s, :name
|
108
107
|
|
109
108
|
# The canonical host name is the simplified, DNS host name
|
110
109
|
def canonical
|
@@ -149,7 +148,7 @@ module EmailAddress
|
|
149
148
|
if @config[:host_remove_spaces]
|
150
149
|
@host_name = @host_name.delete(" ")
|
151
150
|
end
|
152
|
-
@dns_name = if /[^[:ascii:]]/.match(host_name)
|
151
|
+
@dns_name = if /[^[:ascii:]]/.match?(host_name)
|
153
152
|
::SimpleIDN.to_ascii(host_name)
|
154
153
|
else
|
155
154
|
host_name
|
@@ -229,7 +228,7 @@ module EmailAddress
|
|
229
228
|
def parts
|
230
229
|
{host_name: host_name, dns_name: dns_name, subdomain: subdomains,
|
231
230
|
registration_name: registration_name, domain_name: domain_name,
|
232
|
-
tld2: tld2, tld: tld, ip_address: ip_address
|
231
|
+
tld2: tld2, tld: tld, ip_address: ip_address}
|
233
232
|
end
|
234
233
|
|
235
234
|
def hosted_provider
|
@@ -246,7 +245,7 @@ module EmailAddress
|
|
246
245
|
end
|
247
246
|
|
248
247
|
def ip?
|
249
|
-
ip_address
|
248
|
+
!!ip_address
|
250
249
|
end
|
251
250
|
|
252
251
|
def ipv4?
|
@@ -290,7 +289,7 @@ module EmailAddress
|
|
290
289
|
# Does "sub.example.com" match ".com" and ".example.com" top level names?
|
291
290
|
# Matches TLD (uk) or TLD2 (co.uk)
|
292
291
|
def tld_matches?(rule)
|
293
|
-
rule.match(/\A\.(.+)\z/) && ($1 == tld || $1 == tld2) ? true : false
|
292
|
+
rule.match(/\A\.(.+)\z/) && ($1 == tld || $1 == tld2) # ? true : false
|
294
293
|
end
|
295
294
|
|
296
295
|
def provider_matches?(rule)
|
@@ -310,13 +309,8 @@ module EmailAddress
|
|
310
309
|
# the passed CIDR string ("10.9.8.0/24" or "2001:..../64")
|
311
310
|
def ip_matches?(cidr)
|
312
311
|
return false unless ip_address
|
313
|
-
|
314
|
-
|
315
|
-
return cidr if NetAddr::IPv6Net.parse(cidr).contains(NetAddr::IPv6.parse(ip_address))
|
316
|
-
elsif cidr.include?(".") && ip_address.include?(".")
|
317
|
-
return cidr if NetAddr::IPv4Net.parse(cidr).contains(NetAddr::IPv4.parse(ip_address))
|
318
|
-
end
|
319
|
-
false
|
312
|
+
net = IPAddr.new(cidr)
|
313
|
+
net.include?(IPAddr.new(ip_address))
|
320
314
|
end
|
321
315
|
|
322
316
|
############################################################################
|
@@ -333,7 +327,7 @@ module EmailAddress
|
|
333
327
|
# Returns: [official_hostname, alias_hostnames, address_family, *address_list]
|
334
328
|
def dns_a_record
|
335
329
|
@_dns_a_record = "0.0.0.0" if @config[:dns_lookup] == :off
|
336
|
-
@_dns_a_record ||=
|
330
|
+
@_dns_a_record ||= Addrinfo.getaddrinfo(dns_name, 80) # Port 80 for A rec, 25 for MX
|
337
331
|
rescue SocketError # not found, but could also mean network not work
|
338
332
|
@_dns_a_record ||= []
|
339
333
|
end
|
@@ -353,8 +347,8 @@ module EmailAddress
|
|
353
347
|
records = begin
|
354
348
|
dns.getresources(alternate_host || dns_name,
|
355
349
|
Resolv::DNS::Resource::IN::TXT)
|
356
|
-
|
357
|
-
|
350
|
+
rescue Resolv::ResolvTimeout
|
351
|
+
[]
|
358
352
|
end
|
359
353
|
|
360
354
|
records.empty? ? nil : records.map(&:data).join(" ")
|
@@ -407,11 +401,7 @@ module EmailAddress
|
|
407
401
|
# True if the host name has a DNS A Record
|
408
402
|
def valid_dns?
|
409
403
|
return true unless dns_enabled?
|
410
|
-
|
411
|
-
if localhost? && !@config[:host_local]
|
412
|
-
bool = set_error(:domain_no_localhost)
|
413
|
-
end
|
414
|
-
bool
|
404
|
+
dns_a_record.size > 0 || set_error(:domain_unknown)
|
415
405
|
end
|
416
406
|
|
417
407
|
# True if the host name has valid MX servers configured in DNS
|
@@ -435,7 +425,9 @@ module EmailAddress
|
|
435
425
|
# True if the host_name passes Regular Expression match and size limits.
|
436
426
|
def valid_format?
|
437
427
|
if host_name =~ CANONICAL_HOST_REGEX && to_s.size <= MAX_HOST_LENGTH
|
438
|
-
|
428
|
+
if localhost?
|
429
|
+
return @config[:host_local] ? true : set_error(:domain_no_localhost)
|
430
|
+
end
|
439
431
|
return true if host_name.include?(".") # require FQDN
|
440
432
|
end
|
441
433
|
set_error(:domain_invalid)
|
@@ -459,26 +451,18 @@ module EmailAddress
|
|
459
451
|
end
|
460
452
|
|
461
453
|
def localhost?
|
462
|
-
if
|
463
|
-
|
464
|
-
|
465
|
-
NetAddr::IPv6Net.parse("" + "::1").rel(
|
466
|
-
NetAddr::IPv6Net.parse(ip_address)
|
467
|
-
)
|
468
|
-
else
|
469
|
-
NetAddr::IPv4Net.parse("" + "127.0.0.0/8").rel(
|
470
|
-
NetAddr::IPv4Net.parse(ip_address)
|
471
|
-
)
|
472
|
-
end
|
473
|
-
!rel.nil? && rel >= 0
|
474
|
-
else
|
475
|
-
host_name == "localhost"
|
476
|
-
end
|
454
|
+
return true if host_name == "localhost"
|
455
|
+
return false unless ip_address
|
456
|
+
IPAddr.new(ip_address).loopback?
|
477
457
|
end
|
478
458
|
|
479
459
|
# Connects to host to test it can receive email. This should NOT be performed
|
480
460
|
# as an email address check, but is provided to assist in problem resolution.
|
481
461
|
# If you abuse this, you *could* be blocked by the ESP.
|
462
|
+
#
|
463
|
+
# NOTE: As of Ruby 3.1, Net::SMTP was moved from the standard library to the
|
464
|
+
# 'net-smtp' gem. In order to avoid adding that dependency for this experimental
|
465
|
+
# feature, please add the gem to your Gemfile and require it to use this feature.
|
482
466
|
def connect
|
483
467
|
smtp = Net::SMTP.new(host_name || ip_address)
|
484
468
|
smtp.start(@config[:helo_name] || "localhost")
|
@@ -497,7 +481,7 @@ module EmailAddress
|
|
497
481
|
def set_error(err, reason = nil)
|
498
482
|
@error = err
|
499
483
|
@reason = reason
|
500
|
-
@error_message = Config.error_message(err)
|
484
|
+
@error_message = Config.error_message(err, locale)
|
501
485
|
false
|
502
486
|
end
|
503
487
|
|