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
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "resolv"
|
4
|
+
require "netaddr"
|
5
|
+
require "socket"
|
6
6
|
|
7
7
|
module EmailAddress
|
8
8
|
class Exchanger
|
9
9
|
include Enumerable
|
10
10
|
|
11
|
-
def self.cached(host, config={})
|
11
|
+
def self.cached(host, config = {})
|
12
12
|
@host_cache ||= {}
|
13
|
-
@cache_size ||= ENV[
|
13
|
+
@cache_size ||= ENV["EMAIL_ADDRESS_CACHE_SIZE"].to_i || 100
|
14
14
|
if @host_cache.has_key?(host)
|
15
15
|
o = @host_cache.delete(host)
|
16
16
|
@host_cache[host] = o # LRU cache, move to end
|
@@ -22,14 +22,16 @@ module EmailAddress
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def initialize(host, config={})
|
25
|
+
def initialize(host, config = {})
|
26
26
|
@host = host
|
27
27
|
@config = config
|
28
|
+
@dns_disabled = @config[:host_validation] == :syntax || @config[:dns_lookup] == :off
|
28
29
|
end
|
29
30
|
|
30
31
|
def each(&block)
|
32
|
+
return if @dns_disabled
|
31
33
|
mxers.each do |m|
|
32
|
-
yield({host:m[0], ip:m[1], priority:m[2]})
|
34
|
+
yield({host: m[0], ip: m[1], priority: m[2]})
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -37,7 +39,7 @@ module EmailAddress
|
|
37
39
|
def provider
|
38
40
|
return @provider if defined? @provider
|
39
41
|
EmailAddress::Config.providers.each do |provider, config|
|
40
|
-
if config[:exchanger_match] &&
|
42
|
+
if config[:exchanger_match] && matches?(config[:exchanger_match])
|
41
43
|
return @provider = provider
|
42
44
|
end
|
43
45
|
end
|
@@ -50,40 +52,37 @@ module EmailAddress
|
|
50
52
|
# may not find provider by MX name or IP. I'm not sure about the "0.0.0.0" ip, it should
|
51
53
|
# be good in this context, but in "listen" context it means "all bound IP's"
|
52
54
|
def mxers
|
53
|
-
return [["example.com", "0.0.0.0", 1]] if @
|
54
|
-
@mxers ||= Resolv::DNS.open
|
55
|
+
return [["example.com", "0.0.0.0", 1]] if @dns_disabled
|
56
|
+
@mxers ||= Resolv::DNS.open { |dns|
|
55
57
|
dns.timeouts = @config[:dns_timeout] if @config[:dns_timeout]
|
56
58
|
|
57
59
|
ress = begin
|
58
60
|
dns.getresources(@host, Resolv::DNS::Resource::IN::MX)
|
59
|
-
|
60
|
-
|
61
|
+
rescue Resolv::ResolvTimeout
|
62
|
+
[]
|
61
63
|
end
|
62
64
|
|
63
|
-
records = ress.map
|
64
|
-
|
65
|
-
|
66
|
-
[r.exchange.to_s, IPSocket::getaddress(r.exchange.to_s), r.preference]
|
67
|
-
else
|
68
|
-
nil
|
69
|
-
end
|
70
|
-
rescue SocketError # not found, but could also mean network not work or it could mean one record doesn't resolve an address
|
71
|
-
nil
|
65
|
+
records = ress.map { |r|
|
66
|
+
if r.exchange.to_s > " "
|
67
|
+
[r.exchange.to_s, IPSocket.getaddress(r.exchange.to_s), r.preference]
|
72
68
|
end
|
73
|
-
|
69
|
+
}
|
74
70
|
records.compact
|
75
|
-
|
71
|
+
}
|
72
|
+
# not found, but could also mean network not work or it could mean one record doesn't resolve an address
|
73
|
+
rescue SocketError
|
74
|
+
[["example.com", "0.0.0.0", 1]]
|
76
75
|
end
|
77
76
|
|
78
77
|
# Returns Array of domain names for the MX'ers, used to determine the Provider
|
79
78
|
def domains
|
80
|
-
@_domains ||= mxers.map {|m| EmailAddress::Host.new(m.first).domain_name }.sort.uniq
|
79
|
+
@_domains ||= mxers.map { |m| EmailAddress::Host.new(m.first).domain_name }.sort.uniq
|
81
80
|
end
|
82
81
|
|
83
82
|
# Returns an array of MX IP address (String) for the given email domain
|
84
83
|
def mx_ips
|
85
|
-
return ["0.0.0.0"] if @
|
86
|
-
mxers.map {|m| m[1] }
|
84
|
+
return ["0.0.0.0"] if @dns_disabled
|
85
|
+
mxers.map { |m| m[1] }
|
87
86
|
end
|
88
87
|
|
89
88
|
# Simple matcher, takes an array of CIDR addresses (ip/bits) and strings.
|
@@ -96,9 +95,9 @@ module EmailAddress
|
|
96
95
|
rules = Array(rules)
|
97
96
|
rules.each do |rule|
|
98
97
|
if rule.include?("/")
|
99
|
-
return rule if
|
98
|
+
return rule if in_cidr?(rule)
|
100
99
|
else
|
101
|
-
|
100
|
+
each { |mx| return rule if mx[:host].end_with?(rule) }
|
102
101
|
end
|
103
102
|
end
|
104
103
|
false
|
data/lib/email_address/host.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require 'net/smtp'
|
1
|
+
require "simpleidn"
|
2
|
+
require "resolv"
|
3
|
+
require "netaddr"
|
4
|
+
require "net/smtp"
|
6
5
|
|
7
6
|
module EmailAddress
|
8
7
|
##############################################################################
|
@@ -34,12 +33,12 @@ module EmailAddress
|
|
34
33
|
class Host
|
35
34
|
attr_reader :host_name
|
36
35
|
attr_accessor :dns_name, :domain_name, :registration_name,
|
37
|
-
|
38
|
-
|
36
|
+
:tld, :tld2, :subdomains, :ip_address, :config, :provider,
|
37
|
+
:comment, :error_message, :reason
|
39
38
|
MAX_HOST_LENGTH = 255
|
40
39
|
|
41
40
|
# Sometimes, you just need a Regexp...
|
42
|
-
DNS_HOST_REGEX
|
41
|
+
DNS_HOST_REGEX = / [\p{L}\p{N}]+ (?: (?: \-{1,2} | \.) [\p{L}\p{N}]+ )*/x
|
43
42
|
|
44
43
|
# The IPv4 and IPv6 were lifted from Resolv::IPv?::Regex and tweaked to not
|
45
44
|
# \A...\z anchor at the edges.
|
@@ -85,46 +84,45 @@ module EmailAddress
|
|
85
84
|
|
86
85
|
# host name -
|
87
86
|
# * host type - :email for an email host, :mx for exchanger host
|
88
|
-
def initialize(host_name, config={})
|
89
|
-
@original
|
87
|
+
def initialize(host_name, config = {})
|
88
|
+
@original = host_name ||= ""
|
90
89
|
config[:host_type] ||= :email
|
91
|
-
@config
|
92
|
-
@error
|
90
|
+
@config = config
|
91
|
+
@error = @error_message = nil
|
93
92
|
parse(host_name)
|
94
93
|
end
|
95
94
|
|
96
95
|
# Returns the String representation of the host name (or IP)
|
97
96
|
def name
|
98
|
-
if
|
99
|
-
"[#{
|
100
|
-
elsif
|
101
|
-
"[IPv6:#{
|
97
|
+
if ipv4?
|
98
|
+
"[#{ip_address}]"
|
99
|
+
elsif ipv6?
|
100
|
+
"[IPv6:#{ip_address}]"
|
102
101
|
elsif @config[:host_encoding] && @config[:host_encoding] == :unicode
|
103
|
-
::SimpleIDN.to_unicode(
|
102
|
+
::SimpleIDN.to_unicode(host_name)
|
104
103
|
else
|
105
|
-
|
104
|
+
dns_name
|
106
105
|
end
|
107
106
|
end
|
108
|
-
alias
|
107
|
+
alias to_s name
|
109
108
|
|
110
109
|
# The canonical host name is the simplified, DNS host name
|
111
110
|
def canonical
|
112
|
-
|
111
|
+
dns_name
|
113
112
|
end
|
114
113
|
|
115
114
|
# Returns the munged version of the name, replacing everything after the
|
116
115
|
# initial two characters with "*****" or the configured "munge_string".
|
117
116
|
def munge
|
118
|
-
|
117
|
+
host_name.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] }
|
119
118
|
end
|
120
119
|
|
121
120
|
############################################################################
|
122
121
|
# Parsing
|
123
122
|
############################################################################
|
124
123
|
|
125
|
-
|
126
124
|
def parse(host) # :nodoc:
|
127
|
-
host =
|
125
|
+
host = parse_comment(host)
|
128
126
|
|
129
127
|
if host =~ /\A\[IPv6:(.+)\]/i
|
130
128
|
self.ip_address = $1
|
@@ -149,34 +147,34 @@ module EmailAddress
|
|
149
147
|
name = fully_qualified_domain_name(name.downcase)
|
150
148
|
@host_name = name
|
151
149
|
if @config[:host_remove_spaces]
|
152
|
-
@host_name = @host_name.
|
150
|
+
@host_name = @host_name.delete(" ")
|
153
151
|
end
|
154
|
-
|
155
|
-
|
152
|
+
@dns_name = if /[^[:ascii:]]/.match?(host_name)
|
153
|
+
::SimpleIDN.to_ascii(host_name)
|
156
154
|
else
|
157
|
-
|
155
|
+
host_name
|
158
156
|
end
|
159
157
|
|
160
158
|
# Subdomain only (root@localhost)
|
161
|
-
if name.index(
|
159
|
+
if name.index(".").nil?
|
162
160
|
self.subdomains = name
|
163
161
|
|
164
162
|
# Split sub.domain from .tld: *.com, *.xx.cc, *.cc
|
165
163
|
elsif name =~ /\A(.+)\.(\w{3,10})\z/ ||
|
166
|
-
|
167
|
-
|
164
|
+
name =~ /\A(.+)\.(\w{1,3}\.\w\w)\z/ ||
|
165
|
+
name =~ /\A(.+)\.(\w\w)\z/
|
168
166
|
|
169
167
|
sub_and_domain, self.tld2 = [$1, $2] # sub+domain, com || co.uk
|
170
|
-
self.tld =
|
168
|
+
self.tld = tld2.sub(/\A.+\./, "") # co.uk => uk
|
171
169
|
if sub_and_domain =~ /\A(.+)\.(.+)\z/ # is subdomain? sub.example [.tld2]
|
172
|
-
self.subdomains
|
170
|
+
self.subdomains = $1
|
173
171
|
self.registration_name = $2
|
174
172
|
else
|
175
173
|
self.registration_name = sub_and_domain
|
176
|
-
#self.domain_name = sub_and_domain + '.' + self.tld2
|
174
|
+
# self.domain_name = sub_and_domain + '.' + self.tld2
|
177
175
|
end
|
178
|
-
self.domain_name =
|
179
|
-
|
176
|
+
self.domain_name = registration_name + "." + tld2
|
177
|
+
find_provider
|
180
178
|
else # Bad format
|
181
179
|
self.subdomains = self.tld = self.tld2 = ""
|
182
180
|
self.domain_name = self.registration_name = name
|
@@ -187,7 +185,7 @@ module EmailAddress
|
|
187
185
|
dn = @config[:address_fqdn_domain]
|
188
186
|
if !dn
|
189
187
|
if (host_part.nil? || host_part <= " ") && @config[:host_local]
|
190
|
-
|
188
|
+
"localhost"
|
191
189
|
else
|
192
190
|
host_part
|
193
191
|
end
|
@@ -205,39 +203,39 @@ module EmailAddress
|
|
205
203
|
return false unless registration_name
|
206
204
|
find_provider
|
207
205
|
return false unless config[:host_match]
|
208
|
-
!
|
206
|
+
!matches?(config[:host_match])
|
209
207
|
end
|
210
208
|
|
211
209
|
def find_provider # :nodoc:
|
212
|
-
return
|
210
|
+
return provider if provider
|
213
211
|
|
214
212
|
EmailAddress::Config.providers.each do |provider, config|
|
215
|
-
if config[:host_match] &&
|
216
|
-
return
|
213
|
+
if config[:host_match] && matches?(config[:host_match])
|
214
|
+
return set_provider(provider, config)
|
217
215
|
end
|
218
216
|
end
|
219
217
|
|
220
|
-
return
|
218
|
+
return set_provider(:default) unless dns_enabled?
|
221
219
|
|
222
|
-
provider =
|
220
|
+
provider = exchangers.provider
|
223
221
|
if provider != :default
|
224
|
-
|
222
|
+
set_provider(provider,
|
225
223
|
EmailAddress::Config.provider(provider))
|
226
224
|
end
|
227
225
|
|
228
|
-
self.provider ||=
|
226
|
+
self.provider ||= set_provider(:default)
|
229
227
|
end
|
230
228
|
|
231
|
-
def set_provider(name, provider_config={}) # :nodoc:
|
229
|
+
def set_provider(name, provider_config = {}) # :nodoc:
|
232
230
|
self.config = EmailAddress::Config.all_settings(provider_config, @config)
|
233
231
|
self.provider = name
|
234
232
|
end
|
235
233
|
|
236
234
|
# Returns a hash of the parts of the host name after parsing.
|
237
235
|
def parts
|
238
|
-
{
|
239
|
-
|
240
|
-
|
236
|
+
{host_name: host_name, dns_name: dns_name, subdomain: subdomains,
|
237
|
+
registration_name: registration_name, domain_name: domain_name,
|
238
|
+
tld2: tld2, tld: tld, ip_address: ip_address}
|
241
239
|
end
|
242
240
|
|
243
241
|
############################################################################
|
@@ -246,19 +244,19 @@ module EmailAddress
|
|
246
244
|
|
247
245
|
# Is this a fully-qualified domain name?
|
248
246
|
def fqdn?
|
249
|
-
|
247
|
+
tld ? true : false
|
250
248
|
end
|
251
249
|
|
252
250
|
def ip?
|
253
|
-
|
251
|
+
ip_address.nil? ? false : true
|
254
252
|
end
|
255
253
|
|
256
254
|
def ipv4?
|
257
|
-
|
255
|
+
ip? && ip_address.include?(".")
|
258
256
|
end
|
259
257
|
|
260
258
|
def ipv6?
|
261
|
-
|
259
|
+
ip? && ip_address.include?(":")
|
262
260
|
end
|
263
261
|
|
264
262
|
############################################################################
|
@@ -276,25 +274,25 @@ module EmailAddress
|
|
276
274
|
rules = Array(rules)
|
277
275
|
return false if rules.empty?
|
278
276
|
rules.each do |rule|
|
279
|
-
return rule if rule ==
|
277
|
+
return rule if rule == domain_name || rule == dns_name
|
280
278
|
return rule if registration_name_matches?(rule)
|
281
279
|
return rule if tld_matches?(rule)
|
282
280
|
return rule if domain_matches?(rule)
|
283
281
|
return rule if self.provider && provider_matches?(rule)
|
284
|
-
return rule if
|
282
|
+
return rule if ip_matches?(rule)
|
285
283
|
end
|
286
284
|
false
|
287
285
|
end
|
288
286
|
|
289
287
|
# Does "example." match any tld?
|
290
288
|
def registration_name_matches?(rule)
|
291
|
-
"#{
|
289
|
+
"#{registration_name}." == rule
|
292
290
|
end
|
293
291
|
|
294
292
|
# Does "sub.example.com" match ".com" and ".example.com" top level names?
|
295
293
|
# Matches TLD (uk) or TLD2 (co.uk)
|
296
294
|
def tld_matches?(rule)
|
297
|
-
rule.match(/\A\.(.+)\z/) && ($1 ==
|
295
|
+
rule.match(/\A\.(.+)\z/) && ($1 == tld || $1 == tld2) ? true : false
|
298
296
|
end
|
299
297
|
|
300
298
|
def provider_matches?(rule)
|
@@ -305,20 +303,20 @@ module EmailAddress
|
|
305
303
|
# Requires optionally starts with a "@".
|
306
304
|
def domain_matches?(rule)
|
307
305
|
rule = $1 if rule =~ /\A@(.+)/
|
308
|
-
return rule if File.fnmatch?(rule,
|
309
|
-
return rule if File.fnmatch?(rule,
|
306
|
+
return rule if domain_name && File.fnmatch?(rule, domain_name)
|
307
|
+
return rule if dns_name && File.fnmatch?(rule, dns_name)
|
310
308
|
false
|
311
309
|
end
|
312
310
|
|
313
311
|
# True if the host is an IP Address form, and that address matches
|
314
312
|
# the passed CIDR string ("10.9.8.0/24" or "2001:..../64")
|
315
313
|
def ip_matches?(cidr)
|
316
|
-
return false unless
|
317
|
-
return cidr if !cidr.include?("/") && cidr ==
|
318
|
-
if cidr.include?(":") &&
|
319
|
-
return cidr if NetAddr::IPv6Net.parse(cidr).contains(NetAddr::IPv6.parse(
|
320
|
-
elsif cidr.include?(".") &&
|
321
|
-
return cidr if NetAddr::IPv4Net.parse(cidr).contains(NetAddr::IPv4.parse(
|
314
|
+
return false unless ip_address
|
315
|
+
return cidr if !cidr.include?("/") && cidr == ip_address
|
316
|
+
if cidr.include?(":") && ip_address.include?(":")
|
317
|
+
return cidr if NetAddr::IPv6Net.parse(cidr).contains(NetAddr::IPv6.parse(ip_address))
|
318
|
+
elsif cidr.include?(".") && ip_address.include?(".")
|
319
|
+
return cidr if NetAddr::IPv4Net.parse(cidr).contains(NetAddr::IPv4.parse(ip_address))
|
322
320
|
end
|
323
321
|
false
|
324
322
|
end
|
@@ -329,13 +327,15 @@ module EmailAddress
|
|
329
327
|
|
330
328
|
# True if the :dns_lookup setting is enabled
|
331
329
|
def dns_enabled?
|
332
|
-
[:
|
330
|
+
return false if @config[:dns_lookup] == :off
|
331
|
+
return false if @config[:host_validation] == :syntax
|
332
|
+
true
|
333
333
|
end
|
334
334
|
|
335
335
|
# Returns: [official_hostname, alias_hostnames, address_family, *address_list]
|
336
336
|
def dns_a_record
|
337
337
|
@_dns_a_record = "0.0.0.0" if @config[:dns_lookup] == :off
|
338
|
-
@_dns_a_record ||= Socket.gethostbyname(
|
338
|
+
@_dns_a_record ||= Socket.gethostbyname(dns_name)
|
339
339
|
rescue SocketError # not found, but could also mean network not work
|
340
340
|
@_dns_a_record ||= []
|
341
341
|
end
|
@@ -343,19 +343,20 @@ module EmailAddress
|
|
343
343
|
# Returns an array of EmailAddress::Exchanger hosts configured in DNS.
|
344
344
|
# The array will be empty if none are configured.
|
345
345
|
def exchangers
|
346
|
-
#return nil if @config[:host_type] != :email || !self.dns_enabled?
|
347
|
-
@_exchangers ||= EmailAddress::Exchanger.cached(
|
346
|
+
# return nil if @config[:host_type] != :email || !self.dns_enabled?
|
347
|
+
@_exchangers ||= EmailAddress::Exchanger.cached(dns_name, @config)
|
348
348
|
end
|
349
349
|
|
350
350
|
# Returns a DNS TXT Record
|
351
|
-
def txt(alternate_host=nil)
|
351
|
+
def txt(alternate_host = nil)
|
352
|
+
return nil unless dns_enabled?
|
352
353
|
Resolv::DNS.open do |dns|
|
353
354
|
dns.timeouts = @config[:dns_timeout] if @config[:dns_timeout]
|
354
355
|
records = begin
|
355
|
-
dns.getresources(alternate_host ||
|
356
|
-
|
357
|
-
|
358
|
-
|
356
|
+
dns.getresources(alternate_host || dns_name,
|
357
|
+
Resolv::DNS::Resource::IN::TXT)
|
358
|
+
rescue Resolv::ResolvTimeout
|
359
|
+
[]
|
359
360
|
end
|
360
361
|
|
361
362
|
records.empty? ? nil : records.map(&:data).join(" ")
|
@@ -363,13 +364,13 @@ module EmailAddress
|
|
363
364
|
end
|
364
365
|
|
365
366
|
# Parses TXT record pairs into a hash
|
366
|
-
def txt_hash(alternate_host=nil)
|
367
|
+
def txt_hash(alternate_host = nil)
|
367
368
|
fields = {}
|
368
|
-
record =
|
369
|
+
record = txt(alternate_host)
|
369
370
|
return fields unless record
|
370
371
|
|
371
372
|
record.split(/\s*;\s*/).each do |pair|
|
372
|
-
(n,v) = pair.split(/\s*=\s*/)
|
373
|
+
(n, v) = pair.split(/\s*=\s*/)
|
373
374
|
fields[n.to_sym] = v
|
374
375
|
end
|
375
376
|
fields
|
@@ -378,7 +379,7 @@ module EmailAddress
|
|
378
379
|
# Returns a hash of the domain's DMARC (https://en.wikipedia.org/wiki/DMARC)
|
379
380
|
# settings.
|
380
381
|
def dmarc
|
381
|
-
|
382
|
+
dns_name ? txt_hash("_dmarc." + dns_name) : {}
|
382
383
|
end
|
383
384
|
|
384
385
|
############################################################################
|
@@ -386,13 +387,13 @@ module EmailAddress
|
|
386
387
|
############################################################################
|
387
388
|
|
388
389
|
# Returns true if the host name is valid according to the current configuration
|
389
|
-
def valid?(rules={})
|
390
|
+
def valid?(rules = {})
|
390
391
|
host_validation = rules[:host_validation] || @config[:host_validation] || :mx
|
391
|
-
dns_lookup
|
392
|
+
dns_lookup = rules[:dns_lookup] || host_validation
|
392
393
|
self.error_message = nil
|
393
|
-
if
|
394
|
+
if ip_address
|
394
395
|
valid_ip?
|
395
|
-
elsif !
|
396
|
+
elsif !valid_format?
|
396
397
|
false
|
397
398
|
elsif dns_lookup == :connect
|
398
399
|
valid_mx? && connect
|
@@ -407,8 +408,9 @@ module EmailAddress
|
|
407
408
|
|
408
409
|
# True if the host name has a DNS A Record
|
409
410
|
def valid_dns?
|
411
|
+
return true unless dns_enabled?
|
410
412
|
bool = dns_a_record.size > 0 || set_error(:domain_unknown)
|
411
|
-
if
|
413
|
+
if localhost? && !@config[:host_local]
|
412
414
|
bool = set_error(:domain_no_localhost)
|
413
415
|
end
|
414
416
|
bool
|
@@ -416,10 +418,11 @@ module EmailAddress
|
|
416
418
|
|
417
419
|
# True if the host name has valid MX servers configured in DNS
|
418
420
|
def valid_mx?
|
419
|
-
|
421
|
+
return true unless dns_enabled?
|
422
|
+
if exchangers.nil?
|
420
423
|
set_error(:domain_unknown)
|
421
|
-
elsif
|
422
|
-
if
|
424
|
+
elsif exchangers.mx_ips.size > 0
|
425
|
+
if localhost? && !@config[:host_local]
|
423
426
|
set_error(:domain_no_localhost)
|
424
427
|
else
|
425
428
|
true
|
@@ -433,9 +436,9 @@ module EmailAddress
|
|
433
436
|
|
434
437
|
# True if the host_name passes Regular Expression match and size limits.
|
435
438
|
def valid_format?
|
436
|
-
if
|
439
|
+
if host_name =~ CANONICAL_HOST_REGEX && to_s.size <= MAX_HOST_LENGTH
|
437
440
|
return true if localhost?
|
438
|
-
return true if
|
441
|
+
return true if host_name.include?(".") # require FQDN
|
439
442
|
end
|
440
443
|
set_error(:domain_invalid)
|
441
444
|
end
|
@@ -444,12 +447,12 @@ module EmailAddress
|
|
444
447
|
# is a potentially valid IP address. It does not check if the address
|
445
448
|
# is reachable.
|
446
449
|
def valid_ip?
|
447
|
-
if
|
450
|
+
if !@config[:host_allow_ip]
|
448
451
|
bool = set_error(:ip_address_forbidden)
|
449
|
-
elsif
|
450
|
-
bool =
|
451
|
-
elsif
|
452
|
-
bool =
|
452
|
+
elsif ip_address.include?(":")
|
453
|
+
bool = Resolv::IPv6::Regex.match?(ip_address) ? true : set_error(:ipv6_address_invalid)
|
454
|
+
elsif ip_address.include?(".")
|
455
|
+
bool = Resolv::IPv4::Regex.match?(ip_address) ? true : set_error(:ipv4_address_invalid)
|
453
456
|
end
|
454
457
|
if bool && (localhost? && !@config[:host_local])
|
455
458
|
bool = set_error(:ip_address_no_localhost)
|
@@ -458,20 +461,20 @@ module EmailAddress
|
|
458
461
|
end
|
459
462
|
|
460
463
|
def localhost?
|
461
|
-
if
|
464
|
+
if ip_address
|
462
465
|
rel =
|
463
|
-
if
|
464
|
-
NetAddr::IPv6Net.parse(""+"::1").rel(
|
465
|
-
NetAddr::IPv6Net.parse(
|
466
|
+
if ip_address.include?(":")
|
467
|
+
NetAddr::IPv6Net.parse("" + "::1").rel(
|
468
|
+
NetAddr::IPv6Net.parse(ip_address)
|
466
469
|
)
|
467
470
|
else
|
468
|
-
NetAddr::IPv4Net.parse(""+"127.0.0.0/8").rel(
|
469
|
-
NetAddr::IPv4Net.parse(
|
471
|
+
NetAddr::IPv4Net.parse("" + "127.0.0.0/8").rel(
|
472
|
+
NetAddr::IPv4Net.parse(ip_address)
|
470
473
|
)
|
471
474
|
end
|
472
475
|
!rel.nil? && rel >= 0
|
473
476
|
else
|
474
|
-
|
477
|
+
host_name == "localhost"
|
475
478
|
end
|
476
479
|
end
|
477
480
|
|
@@ -479,33 +482,30 @@ module EmailAddress
|
|
479
482
|
# as an email address check, but is provided to assist in problem resolution.
|
480
483
|
# If you abuse this, you *could* be blocked by the ESP.
|
481
484
|
def connect
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
+
smtp = Net::SMTP.new(host_name || ip_address)
|
486
|
+
smtp.start(@config[:helo_name] || "localhost")
|
487
|
+
smtp.finish
|
488
|
+
true
|
489
|
+
rescue Net::SMTPFatalError => e
|
490
|
+
set_error(:server_not_available, e.to_s)
|
491
|
+
rescue SocketError => e
|
492
|
+
set_error(:server_not_available, e.to_s)
|
493
|
+
ensure
|
494
|
+
if smtp&.started?
|
485
495
|
smtp.finish
|
486
|
-
true
|
487
|
-
rescue Net::SMTPFatalError => e
|
488
|
-
set_error(:server_not_available, e.to_s)
|
489
|
-
rescue SocketError => e
|
490
|
-
set_error(:server_not_available, e.to_s)
|
491
|
-
ensure
|
492
|
-
if smtp && smtp.started?
|
493
|
-
smtp.finish
|
494
|
-
end
|
495
496
|
end
|
496
497
|
end
|
497
498
|
|
498
|
-
def set_error(err, reason=nil)
|
499
|
-
@error
|
500
|
-
@reason
|
499
|
+
def set_error(err, reason = nil)
|
500
|
+
@error = err
|
501
|
+
@reason = reason
|
501
502
|
@error_message = EmailAddress::Config.error_message(err)
|
502
503
|
false
|
503
504
|
end
|
504
505
|
|
505
506
|
# The inverse of valid? -- Returns nil (falsey) if valid, otherwise error message
|
506
507
|
def error
|
507
|
-
|
508
|
+
valid? ? nil : @error_message
|
508
509
|
end
|
509
|
-
|
510
510
|
end
|
511
511
|
end
|