email_address 0.1.2 → 0.2.4
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 +5 -5
- data/.github/workflows/ci.yml +18 -0
- data/Gemfile +1 -1
- data/README.md +257 -123
- data/Rakefile +2 -3
- data/email_address.gemspec +28 -22
- data/lib/email_address/active_record_validator.rb +10 -11
- data/lib/email_address/address.rb +130 -80
- data/lib/email_address/canonical_email_address_type.rb +16 -12
- data/lib/email_address/config.rb +111 -51
- data/lib/email_address/email_address_type.rb +17 -13
- data/lib/email_address/exchanger.rb +44 -33
- data/lib/email_address/host.rb +227 -105
- data/lib/email_address/local.rb +129 -87
- data/lib/email_address/messages.yaml +21 -0
- data/lib/email_address/rewriter.rb +144 -0
- data/lib/email_address/version.rb +1 -1
- data/lib/email_address.rb +48 -53
- data/test/activerecord/test_ar.rb +17 -13
- data/test/activerecord/user.rb +31 -30
- data/test/email_address/test_address.rb +87 -21
- data/test/email_address/test_config.rb +10 -10
- data/test/email_address/test_exchanger.rb +6 -7
- data/test/email_address/test_host.rb +79 -21
- data/test/email_address/test_local.rb +53 -36
- data/test/email_address/test_rewriter.rb +11 -0
- data/test/test_aliasing.rb +53 -0
- data/test/test_email_address.rb +15 -19
- data/test/test_helper.rb +9 -8
- metadata +57 -21
- data/.travis.yml +0 -10
data/lib/email_address/host.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require 'netaddr'
|
1
|
+
require "simpleidn"
|
2
|
+
require "resolv"
|
4
3
|
|
5
4
|
module EmailAddress
|
6
5
|
##############################################################################
|
@@ -32,16 +31,16 @@ module EmailAddress
|
|
32
31
|
class Host
|
33
32
|
attr_reader :host_name
|
34
33
|
attr_accessor :dns_name, :domain_name, :registration_name,
|
35
|
-
|
36
|
-
|
34
|
+
:tld, :tld2, :subdomains, :ip_address, :config, :provider,
|
35
|
+
:comment, :error_message, :reason, :locale
|
37
36
|
MAX_HOST_LENGTH = 255
|
38
37
|
|
39
38
|
# Sometimes, you just need a Regexp...
|
40
|
-
DNS_HOST_REGEX
|
39
|
+
DNS_HOST_REGEX = / [\p{L}\p{N}]+ (?: (?: -{1,2} | \.) [\p{L}\p{N}]+ )*/x
|
41
40
|
|
42
41
|
# The IPv4 and IPv6 were lifted from Resolv::IPv?::Regex and tweaked to not
|
43
42
|
# \A...\z anchor at the edges.
|
44
|
-
|
43
|
+
IPV6_HOST_REGEX = /\[IPv6:
|
45
44
|
(?: (?:(?x-mi:
|
46
45
|
(?:[0-9A-Fa-f]{1,4}:){7}
|
47
46
|
[0-9A-Fa-f]{1,4}
|
@@ -60,7 +59,7 @@ module EmailAddress
|
|
60
59
|
(?: \d+)\.(?: \d+)\.(?: \d+)\.(?: \d+)
|
61
60
|
)))\]/ix
|
62
61
|
|
63
|
-
|
62
|
+
IPV4_HOST_REGEX = /\[((?x-mi:0
|
64
63
|
|1(?:[0-9][0-9]?)?
|
65
64
|
|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
|
66
65
|
|[3-9][0-9]?))\.((?x-mi:0
|
@@ -79,49 +78,50 @@ module EmailAddress
|
|
79
78
|
|
80
79
|
# Matches Host forms: DNS name, IPv4, or IPv6 formats
|
81
80
|
STANDARD_HOST_REGEX = /\A (?: #{DNS_HOST_REGEX}
|
82
|
-
| #{
|
81
|
+
| #{IPV4_HOST_REGEX} | #{IPV6_HOST_REGEX}) \z/ix
|
83
82
|
|
84
83
|
# host name -
|
85
84
|
# * host type - :email for an email host, :mx for exchanger host
|
86
|
-
def initialize(host_name, config={})
|
87
|
-
@original
|
85
|
+
def initialize(host_name, config = {}, locale = "en")
|
86
|
+
@original = host_name ||= ""
|
87
|
+
@locale = locale
|
88
88
|
config[:host_type] ||= :email
|
89
|
-
@config
|
89
|
+
@config = config.is_a?(Hash) ? Config.new(config) : config
|
90
|
+
@error = @error_message = nil
|
90
91
|
parse(host_name)
|
91
92
|
end
|
92
93
|
|
93
94
|
# Returns the String representation of the host name (or IP)
|
94
95
|
def name
|
95
|
-
if
|
96
|
-
"[#{
|
97
|
-
elsif
|
98
|
-
"[IPv6:#{
|
96
|
+
if ipv4?
|
97
|
+
"[#{ip_address}]"
|
98
|
+
elsif ipv6?
|
99
|
+
"[IPv6:#{ip_address}]"
|
99
100
|
elsif @config[:host_encoding] && @config[:host_encoding] == :unicode
|
100
|
-
::SimpleIDN.to_unicode(
|
101
|
+
::SimpleIDN.to_unicode(host_name)
|
101
102
|
else
|
102
|
-
|
103
|
+
dns_name
|
103
104
|
end
|
104
105
|
end
|
105
|
-
|
106
|
+
alias_method :to_s, :name
|
106
107
|
|
107
108
|
# The canonical host name is the simplified, DNS host name
|
108
109
|
def canonical
|
109
|
-
|
110
|
+
dns_name
|
110
111
|
end
|
111
112
|
|
112
113
|
# Returns the munged version of the name, replacing everything after the
|
113
114
|
# initial two characters with "*****" or the configured "munge_string".
|
114
115
|
def munge
|
115
|
-
|
116
|
+
host_name.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] }
|
116
117
|
end
|
117
118
|
|
118
119
|
############################################################################
|
119
120
|
# Parsing
|
120
121
|
############################################################################
|
121
122
|
|
122
|
-
|
123
123
|
def parse(host) # :nodoc:
|
124
|
-
host =
|
124
|
+
host = parse_comment(host)
|
125
125
|
|
126
126
|
if host =~ /\A\[IPv6:(.+)\]/i
|
127
127
|
self.ip_address = $1
|
@@ -143,62 +143,96 @@ module EmailAddress
|
|
143
143
|
end
|
144
144
|
|
145
145
|
def host_name=(name)
|
146
|
-
|
147
|
-
@
|
146
|
+
name = fully_qualified_domain_name(name.downcase)
|
147
|
+
@host_name = name
|
148
|
+
if @config[:host_remove_spaces]
|
149
|
+
@host_name = @host_name.delete(" ")
|
150
|
+
end
|
151
|
+
@dns_name = if /[^[:ascii:]]/.match?(host_name)
|
152
|
+
::SimpleIDN.to_ascii(host_name)
|
153
|
+
else
|
154
|
+
host_name
|
155
|
+
end
|
148
156
|
|
149
157
|
# Subdomain only (root@localhost)
|
150
|
-
if name.index(
|
158
|
+
if name.index(".").nil?
|
151
159
|
self.subdomains = name
|
152
160
|
|
153
161
|
# Split sub.domain from .tld: *.com, *.xx.cc, *.cc
|
154
162
|
elsif name =~ /\A(.+)\.(\w{3,10})\z/ ||
|
155
|
-
|
156
|
-
|
163
|
+
name =~ /\A(.+)\.(\w{1,3}\.\w\w)\z/ ||
|
164
|
+
name =~ /\A(.+)\.(\w\w)\z/
|
157
165
|
|
158
166
|
sub_and_domain, self.tld2 = [$1, $2] # sub+domain, com || co.uk
|
159
|
-
self.tld =
|
167
|
+
self.tld = tld2.sub(/\A.+\./, "") # co.uk => uk
|
160
168
|
if sub_and_domain =~ /\A(.+)\.(.+)\z/ # is subdomain? sub.example [.tld2]
|
161
|
-
self.subdomains
|
169
|
+
self.subdomains = $1
|
162
170
|
self.registration_name = $2
|
163
171
|
else
|
164
172
|
self.registration_name = sub_and_domain
|
165
|
-
#self.domain_name = sub_and_domain + '.' + self.tld2
|
173
|
+
# self.domain_name = sub_and_domain + '.' + self.tld2
|
166
174
|
end
|
167
|
-
self.domain_name =
|
168
|
-
|
175
|
+
self.domain_name = registration_name + "." + tld2
|
176
|
+
find_provider
|
177
|
+
else # Bad format
|
178
|
+
self.subdomains = self.tld = self.tld2 = ""
|
179
|
+
self.domain_name = self.registration_name = name
|
169
180
|
end
|
170
181
|
end
|
171
182
|
|
172
|
-
def
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
183
|
+
def fully_qualified_domain_name(host_part)
|
184
|
+
dn = @config[:address_fqdn_domain]
|
185
|
+
if !dn
|
186
|
+
if (host_part.nil? || host_part <= " ") && @config[:host_local] && @config[:host_auto_append]
|
187
|
+
"localhost"
|
188
|
+
else
|
189
|
+
host_part
|
178
190
|
end
|
191
|
+
elsif host_part.nil? || host_part <= " "
|
192
|
+
dn
|
193
|
+
elsif !host_part.include?(".")
|
194
|
+
host_part + "." + dn
|
195
|
+
else
|
196
|
+
host_part
|
179
197
|
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# True if host is hosted at the provider, not a public provider host name
|
201
|
+
def hosted_service?
|
202
|
+
return false unless registration_name
|
203
|
+
find_provider
|
204
|
+
return false unless config[:host_match]
|
205
|
+
!matches?(config[:host_match])
|
206
|
+
end
|
180
207
|
|
181
|
-
|
208
|
+
def find_provider # :nodoc:
|
209
|
+
return provider if provider
|
182
210
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
211
|
+
Config.providers.each do |provider, config|
|
212
|
+
if config[:host_match] && matches?(config[:host_match])
|
213
|
+
return set_provider(provider, config)
|
214
|
+
end
|
187
215
|
end
|
188
216
|
|
189
|
-
|
217
|
+
return set_provider(:default) unless dns_enabled?
|
218
|
+
|
219
|
+
self.provider ||= set_provider(:default)
|
190
220
|
end
|
191
221
|
|
192
|
-
def set_provider(name, provider_config={}) # :nodoc:
|
193
|
-
|
194
|
-
|
222
|
+
def set_provider(name, provider_config = {}) # :nodoc:
|
223
|
+
config.configure(provider_config)
|
224
|
+
@provider = name
|
195
225
|
end
|
196
226
|
|
197
227
|
# Returns a hash of the parts of the host name after parsing.
|
198
228
|
def parts
|
199
|
-
{
|
200
|
-
|
201
|
-
|
229
|
+
{host_name: host_name, dns_name: dns_name, subdomain: subdomains,
|
230
|
+
registration_name: registration_name, domain_name: domain_name,
|
231
|
+
tld2: tld2, tld: tld, ip_address: ip_address}
|
232
|
+
end
|
233
|
+
|
234
|
+
def hosted_provider
|
235
|
+
Exchanger.cached(dns_name).provider
|
202
236
|
end
|
203
237
|
|
204
238
|
############################################################################
|
@@ -207,19 +241,19 @@ module EmailAddress
|
|
207
241
|
|
208
242
|
# Is this a fully-qualified domain name?
|
209
243
|
def fqdn?
|
210
|
-
|
244
|
+
tld ? true : false
|
211
245
|
end
|
212
246
|
|
213
247
|
def ip?
|
214
|
-
|
248
|
+
!!ip_address
|
215
249
|
end
|
216
250
|
|
217
251
|
def ipv4?
|
218
|
-
|
252
|
+
ip? && ip_address.include?(".")
|
219
253
|
end
|
220
254
|
|
221
255
|
def ipv6?
|
222
|
-
|
256
|
+
ip? && ip_address.include?(":")
|
223
257
|
end
|
224
258
|
|
225
259
|
############################################################################
|
@@ -237,25 +271,25 @@ module EmailAddress
|
|
237
271
|
rules = Array(rules)
|
238
272
|
return false if rules.empty?
|
239
273
|
rules.each do |rule|
|
240
|
-
return rule if rule ==
|
274
|
+
return rule if rule == domain_name || rule == dns_name
|
241
275
|
return rule if registration_name_matches?(rule)
|
242
276
|
return rule if tld_matches?(rule)
|
243
277
|
return rule if domain_matches?(rule)
|
244
278
|
return rule if self.provider && provider_matches?(rule)
|
245
|
-
return rule if
|
279
|
+
return rule if ip_matches?(rule)
|
246
280
|
end
|
247
281
|
false
|
248
282
|
end
|
249
283
|
|
250
284
|
# Does "example." match any tld?
|
251
285
|
def registration_name_matches?(rule)
|
252
|
-
|
286
|
+
rule == "#{registration_name}."
|
253
287
|
end
|
254
288
|
|
255
289
|
# Does "sub.example.com" match ".com" and ".example.com" top level names?
|
256
290
|
# Matches TLD (uk) or TLD2 (co.uk)
|
257
291
|
def tld_matches?(rule)
|
258
|
-
rule.match(/\A\.(.+)\z/) && ($1 ==
|
292
|
+
rule.match(/\A\.(.+)\z/) && ($1 == tld || $1 == tld2) # ? true : false
|
259
293
|
end
|
260
294
|
|
261
295
|
def provider_matches?(rule)
|
@@ -266,24 +300,17 @@ module EmailAddress
|
|
266
300
|
# Requires optionally starts with a "@".
|
267
301
|
def domain_matches?(rule)
|
268
302
|
rule = $1 if rule =~ /\A@(.+)/
|
269
|
-
return rule if File.fnmatch?(rule,
|
270
|
-
return rule if File.fnmatch?(rule,
|
303
|
+
return rule if domain_name && File.fnmatch?(rule, domain_name)
|
304
|
+
return rule if dns_name && File.fnmatch?(rule, dns_name)
|
271
305
|
false
|
272
306
|
end
|
273
307
|
|
274
308
|
# True if the host is an IP Address form, and that address matches
|
275
309
|
# the passed CIDR string ("10.9.8.0/24" or "2001:..../64")
|
276
310
|
def ip_matches?(cidr)
|
277
|
-
return false unless
|
278
|
-
|
279
|
-
|
280
|
-
c = NetAddr::CIDR.create(cidr)
|
281
|
-
if cidr.include?(":") && self.ip_address.include?(":")
|
282
|
-
return cidr if c.matches?(self.ip_address)
|
283
|
-
elsif cidr.include?(".") && self.ip_address.include?(".")
|
284
|
-
return cidr if c.matches?(self.ip_address)
|
285
|
-
end
|
286
|
-
false
|
311
|
+
return false unless ip_address
|
312
|
+
net = IPAddr.new(cidr)
|
313
|
+
net.include?(IPAddr.new(ip_address))
|
287
314
|
end
|
288
315
|
|
289
316
|
############################################################################
|
@@ -292,45 +319,50 @@ module EmailAddress
|
|
292
319
|
|
293
320
|
# True if the :dns_lookup setting is enabled
|
294
321
|
def dns_enabled?
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
# True if the host name has a DNS A Record
|
299
|
-
def has_dns_a_record?
|
300
|
-
dns_a_record.size > 0 ? true : false
|
322
|
+
return false if @config[:dns_lookup] == :off
|
323
|
+
return false if @config[:host_validation] == :syntax
|
324
|
+
true
|
301
325
|
end
|
302
326
|
|
303
327
|
# Returns: [official_hostname, alias_hostnames, address_family, *address_list]
|
304
328
|
def dns_a_record
|
305
|
-
@_dns_a_record
|
329
|
+
@_dns_a_record = "0.0.0.0" if @config[:dns_lookup] == :off
|
330
|
+
@_dns_a_record ||= Addrinfo.getaddrinfo(dns_name, 80) # Port 80 for A rec, 25 for MX
|
306
331
|
rescue SocketError # not found, but could also mean network not work
|
307
332
|
@_dns_a_record ||= []
|
308
333
|
end
|
309
334
|
|
310
|
-
# Returns an array of
|
335
|
+
# Returns an array of Exchanger hosts configured in DNS.
|
311
336
|
# The array will be empty if none are configured.
|
312
337
|
def exchangers
|
313
|
-
return nil if @config[:host_type] != :email || !self.dns_enabled?
|
314
|
-
@_exchangers ||=
|
338
|
+
# return nil if @config[:host_type] != :email || !self.dns_enabled?
|
339
|
+
@_exchangers ||= Exchanger.cached(dns_name, @config)
|
315
340
|
end
|
316
341
|
|
317
342
|
# Returns a DNS TXT Record
|
318
|
-
def txt(alternate_host=nil)
|
343
|
+
def txt(alternate_host = nil)
|
344
|
+
return nil unless dns_enabled?
|
319
345
|
Resolv::DNS.open do |dns|
|
320
|
-
|
321
|
-
|
346
|
+
dns.timeouts = @config[:dns_timeout] if @config[:dns_timeout]
|
347
|
+
records = begin
|
348
|
+
dns.getresources(alternate_host || dns_name,
|
349
|
+
Resolv::DNS::Resource::IN::TXT)
|
350
|
+
rescue Resolv::ResolvTimeout
|
351
|
+
[]
|
352
|
+
end
|
353
|
+
|
322
354
|
records.empty? ? nil : records.map(&:data).join(" ")
|
323
355
|
end
|
324
356
|
end
|
325
357
|
|
326
358
|
# Parses TXT record pairs into a hash
|
327
|
-
def txt_hash(alternate_host=nil)
|
359
|
+
def txt_hash(alternate_host = nil)
|
328
360
|
fields = {}
|
329
|
-
record =
|
361
|
+
record = txt(alternate_host)
|
330
362
|
return fields unless record
|
331
363
|
|
332
364
|
record.split(/\s*;\s*/).each do |pair|
|
333
|
-
(n,v) = pair.split(/\s*=\s*/)
|
365
|
+
(n, v) = pair.split(/\s*=\s*/)
|
334
366
|
fields[n.to_sym] = v
|
335
367
|
end
|
336
368
|
fields
|
@@ -339,7 +371,7 @@ module EmailAddress
|
|
339
371
|
# Returns a hash of the domain's DMARC (https://en.wikipedia.org/wiki/DMARC)
|
340
372
|
# settings.
|
341
373
|
def dmarc
|
342
|
-
|
374
|
+
dns_name ? txt_hash("_dmarc." + dns_name) : {}
|
343
375
|
end
|
344
376
|
|
345
377
|
############################################################################
|
@@ -347,34 +379,124 @@ module EmailAddress
|
|
347
379
|
############################################################################
|
348
380
|
|
349
381
|
# Returns true if the host name is valid according to the current configuration
|
350
|
-
def valid?(
|
351
|
-
|
382
|
+
def valid?(rules = {})
|
383
|
+
host_validation = rules[:host_validation] || @config[:host_validation] || :mx
|
384
|
+
dns_lookup = rules[:dns_lookup] || host_validation
|
385
|
+
self.error_message = nil
|
386
|
+
if host_name && !host_name.empty? && !@config[:host_size].include?(host_name.size)
|
387
|
+
return set_error(:invalid_host)
|
388
|
+
end
|
389
|
+
if ip_address
|
390
|
+
valid_ip?
|
391
|
+
elsif !valid_format?
|
392
|
+
false
|
393
|
+
elsif dns_lookup == :connect
|
394
|
+
valid_mx? && connect
|
395
|
+
elsif dns_lookup == :mx
|
396
|
+
valid_mx?
|
397
|
+
elsif dns_lookup == :a
|
398
|
+
valid_dns?
|
399
|
+
else
|
352
400
|
true
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# True if the host name has a DNS A Record
|
405
|
+
def valid_dns?
|
406
|
+
return true unless dns_enabled?
|
407
|
+
dns_a_record.size > 0 || set_error(:domain_unknown)
|
408
|
+
end
|
409
|
+
|
410
|
+
# True if the host name has valid MX servers configured in DNS
|
411
|
+
def valid_mx?
|
412
|
+
return true unless dns_enabled?
|
413
|
+
if exchangers.nil?
|
414
|
+
set_error(:domain_unknown)
|
415
|
+
elsif exchangers.mx_ips.size > 0
|
416
|
+
if localhost? && !@config[:host_local]
|
417
|
+
set_error(:domain_no_localhost)
|
418
|
+
else
|
419
|
+
true
|
420
|
+
end
|
421
|
+
elsif @config[:dns_timeout].nil? && valid_dns?
|
422
|
+
set_error(:domain_does_not_accept_email)
|
361
423
|
else
|
362
|
-
|
424
|
+
set_error(:domain_unknown)
|
363
425
|
end
|
364
426
|
end
|
365
427
|
|
428
|
+
# True if the host_name passes Regular Expression match and size limits.
|
429
|
+
def valid_format?
|
430
|
+
if host_name =~ CANONICAL_HOST_REGEX && to_s.size <= MAX_HOST_LENGTH
|
431
|
+
if localhost?
|
432
|
+
return @config[:host_local] ? true : set_error(:domain_no_localhost)
|
433
|
+
end
|
434
|
+
|
435
|
+
return true if !@config[:host_fqdn]
|
436
|
+
return true if host_name.include?(".") # require FQDN
|
437
|
+
end
|
438
|
+
set_error(:domain_invalid)
|
439
|
+
end
|
440
|
+
|
366
441
|
# Returns true if the IP address given in that form of the host name
|
367
442
|
# is a potentially valid IP address. It does not check if the address
|
368
443
|
# is reachable.
|
369
444
|
def valid_ip?
|
370
|
-
if
|
371
|
-
|
372
|
-
elsif
|
373
|
-
|
374
|
-
elsif
|
375
|
-
|
445
|
+
if !@config[:host_allow_ip]
|
446
|
+
bool = set_error(:ip_address_forbidden)
|
447
|
+
elsif ip_address.include?(":")
|
448
|
+
bool = ip_address.match(Resolv::IPv6::Regex) ? true : set_error(:ipv6_address_invalid)
|
449
|
+
elsif ip_address.include?(".")
|
450
|
+
bool = ip_address.match(Resolv::IPv4::Regex) ? true : set_error(:ipv4_address_invalid)
|
376
451
|
end
|
452
|
+
if bool && (localhost? && !@config[:host_local])
|
453
|
+
bool = set_error(:ip_address_no_localhost)
|
454
|
+
end
|
455
|
+
bool
|
456
|
+
end
|
457
|
+
|
458
|
+
def localhost?
|
459
|
+
return true if host_name == "localhost"
|
460
|
+
return false unless ip_address
|
461
|
+
IPAddr.new(ip_address).loopback?
|
377
462
|
end
|
378
463
|
|
464
|
+
# Connects to host to test it can receive email. This should NOT be performed
|
465
|
+
# as an email address check, but is provided to assist in problem resolution.
|
466
|
+
# If you abuse this, you *could* be blocked by the ESP.
|
467
|
+
#
|
468
|
+
# timeout is the number of seconds to wait before timing out the request and
|
469
|
+
# returns false as the connection was unsuccessful.
|
470
|
+
#
|
471
|
+
# > NOTE: As of Ruby 3.1, Net::SMTP was moved from the standard library to the
|
472
|
+
# > 'net-smtp' gem. In order to avoid adding that dependency for this *experimental*
|
473
|
+
# > feature, please add the gem to your Gemfile and require it to use this feature.
|
474
|
+
def connect(timeout = nil)
|
475
|
+
smtp = Net::SMTP.new(host_name || ip_address)
|
476
|
+
smtp.open_timeout = timeout || @config[:host_timeout]
|
477
|
+
smtp.start(@config[:helo_name] || "localhost")
|
478
|
+
smtp.finish
|
479
|
+
true
|
480
|
+
rescue Net::SMTPFatalError => e
|
481
|
+
set_error(:server_not_available, e.to_s)
|
482
|
+
rescue SocketError => e
|
483
|
+
set_error(:server_not_available, e.to_s)
|
484
|
+
rescue Net::OpenTimeout => e
|
485
|
+
set_error(:server_not_available, e.to_s)
|
486
|
+
ensure
|
487
|
+
smtp.finish if smtp&.started?
|
488
|
+
end
|
489
|
+
|
490
|
+
def set_error(err, reason = nil)
|
491
|
+
@error = err
|
492
|
+
@reason = reason
|
493
|
+
@error_message = Config.error_message(err, locale)
|
494
|
+
false
|
495
|
+
end
|
496
|
+
|
497
|
+
# The inverse of valid? -- Returns nil (falsey) if valid, otherwise error message
|
498
|
+
def error
|
499
|
+
valid? ? nil : @error_message
|
500
|
+
end
|
379
501
|
end
|
380
502
|
end
|