email_address 0.1.2 → 0.2.0
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 +255 -123
- data/Rakefile +2 -3
- data/email_address.gemspec +27 -22
- data/lib/email_address/active_record_validator.rb +10 -11
- data/lib/email_address/address.rb +126 -80
- data/lib/email_address/canonical_email_address_type.rb +16 -12
- data/lib/email_address/config.rb +102 -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 +217 -105
- data/lib/email_address/local.rb +127 -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 +84 -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 +59 -21
- data/test/email_address/test_local.rb +49 -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 +43 -21
- data/.travis.yml +0 -10
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
################################################################################
|
2
4
|
# ActiveRecord v5.0 Custom Type
|
3
5
|
#
|
@@ -27,20 +29,22 @@
|
|
27
29
|
# user.canonical_email #=> "patsmith@gmail.com"
|
28
30
|
################################################################################
|
29
31
|
|
30
|
-
|
32
|
+
module EmailAddress
|
33
|
+
class EmailAddressType < ActiveRecord::Type::Value
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
# From user input, setter
|
36
|
+
def cast(value)
|
37
|
+
super(Address.new(value).normal)
|
38
|
+
end
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
# From a database value
|
41
|
+
def deserialize(value)
|
42
|
+
value && Address.new(value).normal
|
43
|
+
end
|
44
|
+
|
45
|
+
# To a database value (string)
|
46
|
+
def serialize(value)
|
47
|
+
value && Address.new(value).normal
|
48
|
+
end
|
45
49
|
end
|
46
50
|
end
|
@@ -1,41 +1,44 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "resolv"
|
4
|
+
require "socket"
|
4
5
|
|
5
6
|
module EmailAddress
|
6
7
|
class Exchanger
|
7
8
|
include Enumerable
|
8
9
|
|
9
|
-
def self.cached(host)
|
10
|
+
def self.cached(host, config = {})
|
10
11
|
@host_cache ||= {}
|
11
|
-
@cache_size ||= ENV[
|
12
|
+
@cache_size ||= ENV["EMAIL_ADDRESS_CACHE_SIZE"].to_i || 100
|
12
13
|
if @host_cache.has_key?(host)
|
13
14
|
o = @host_cache.delete(host)
|
14
15
|
@host_cache[host] = o # LRU cache, move to end
|
15
16
|
elsif @host_cache.size >= @cache_size
|
16
17
|
@host_cache.delete(@host_cache.keys.first)
|
17
|
-
@host_cache[host] = new(host)
|
18
|
+
@host_cache[host] = new(host, config)
|
18
19
|
else
|
19
|
-
@host_cache[host] = new(host)
|
20
|
+
@host_cache[host] = new(host, config)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
23
|
-
def initialize(host, config={})
|
24
|
+
def initialize(host, config = {})
|
24
25
|
@host = host
|
25
|
-
@config = config
|
26
|
+
@config = config.is_a?(Hash) ? Config.new(config) : config
|
27
|
+
@dns_disabled = @config[:host_validation] == :syntax || @config[:dns_lookup] == :off
|
26
28
|
end
|
27
29
|
|
28
30
|
def each(&block)
|
31
|
+
return if @dns_disabled
|
29
32
|
mxers.each do |m|
|
30
|
-
yield({host:m[0], ip:m[1], priority:m[2]})
|
33
|
+
yield({host: m[0], ip: m[1], priority: m[2]})
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
34
37
|
# Returns the provider name based on the MX-er host names, or nil if not matched
|
35
38
|
def provider
|
36
39
|
return @provider if defined? @provider
|
37
|
-
|
38
|
-
if config[:exchanger_match] &&
|
40
|
+
Config.providers.each do |provider, config|
|
41
|
+
if config[:exchanger_match] && matches?(config[:exchanger_match])
|
39
42
|
return @provider = provider
|
40
43
|
end
|
41
44
|
end
|
@@ -44,28 +47,41 @@ module EmailAddress
|
|
44
47
|
|
45
48
|
# Returns: [["mta7.am0.yahoodns.net", "66.94.237.139", 1], ["mta5.am0.yahoodns.net", "67.195.168.230", 1], ["mta6.am0.yahoodns.net", "98.139.54.60", 1]]
|
46
49
|
# If not found, returns []
|
50
|
+
# Returns a dummy record when dns_lookup is turned off since it may exists, though
|
51
|
+
# may not find provider by MX name or IP. I'm not sure about the "0.0.0.0" ip, it should
|
52
|
+
# be good in this context, but in "listen" context it means "all bound IP's"
|
47
53
|
def mxers
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
return [["example.com", "0.0.0.0", 1]] if @dns_disabled
|
55
|
+
@mxers ||= Resolv::DNS.open { |dns|
|
56
|
+
dns.timeouts = @config[:dns_timeout] if @config[:dns_timeout]
|
57
|
+
|
58
|
+
ress = begin
|
59
|
+
dns.getresources(@host, Resolv::DNS::Resource::IN::MX)
|
60
|
+
rescue Resolv::ResolvTimeout
|
61
|
+
[]
|
56
62
|
end
|
63
|
+
|
64
|
+
records = ress.map { |r|
|
65
|
+
if r.exchange.to_s > " "
|
66
|
+
[r.exchange.to_s, IPSocket.getaddress(r.exchange.to_s), r.preference]
|
67
|
+
end
|
68
|
+
}
|
57
69
|
records.compact
|
58
|
-
|
70
|
+
}
|
71
|
+
# not found, but could also mean network not work or it could mean one record doesn't resolve an address
|
72
|
+
rescue SocketError
|
73
|
+
[["example.com", "0.0.0.0", 1]]
|
59
74
|
end
|
60
75
|
|
61
76
|
# Returns Array of domain names for the MX'ers, used to determine the Provider
|
62
77
|
def domains
|
63
|
-
@_domains ||= mxers.map {|m|
|
78
|
+
@_domains ||= mxers.map { |m| Host.new(m.first).domain_name }.sort.uniq
|
64
79
|
end
|
65
80
|
|
66
81
|
# Returns an array of MX IP address (String) for the given email domain
|
67
82
|
def mx_ips
|
68
|
-
|
83
|
+
return ["0.0.0.0"] if @dns_disabled
|
84
|
+
mxers.map { |m| m[1] }
|
69
85
|
end
|
70
86
|
|
71
87
|
# Simple matcher, takes an array of CIDR addresses (ip/bits) and strings.
|
@@ -78,9 +94,9 @@ module EmailAddress
|
|
78
94
|
rules = Array(rules)
|
79
95
|
rules.each do |rule|
|
80
96
|
if rule.include?("/")
|
81
|
-
return rule if
|
97
|
+
return rule if in_cidr?(rule)
|
82
98
|
else
|
83
|
-
|
99
|
+
each { |mx| return rule if mx[:host].end_with?(rule) }
|
84
100
|
end
|
85
101
|
end
|
86
102
|
false
|
@@ -88,14 +104,9 @@ module EmailAddress
|
|
88
104
|
|
89
105
|
# Given a cidr (ip/bits) and ip address, returns true on match. Caches cidr object.
|
90
106
|
def in_cidr?(cidr)
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
elsif cidr.include?(".")
|
95
|
-
mx_ips.find { |ip| !ip.include?(":") && c.matches?(ip) } ? true : false
|
96
|
-
else
|
97
|
-
false
|
98
|
-
end
|
107
|
+
net = IPAddr.new(cidr)
|
108
|
+
found = mx_ips.detect { |ip| net.include?(IPAddr.new(ip)) }
|
109
|
+
!!found
|
99
110
|
end
|
100
111
|
end
|
101
112
|
end
|