email_address 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +173 -45
- data/Rakefile +18 -0
- data/email_address.gemspec +1 -0
- data/lib/email_address.rb +27 -2
- data/lib/email_address/active_record_validator.rb +42 -0
- data/lib/email_address/address.rb +98 -11
- data/lib/email_address/config.rb +4 -0
- data/lib/email_address/domain_matcher.rb +12 -4
- data/lib/email_address/domain_parser.rb +0 -2
- data/lib/email_address/email_address_type.rb +62 -0
- data/lib/email_address/exchanger.rb +35 -3
- data/lib/email_address/host.rb +31 -4
- data/lib/email_address/local.rb +11 -5
- data/lib/email_address/matcher.rb +119 -0
- data/lib/email_address/validator.rb +18 -12
- data/lib/email_address/version.rb +1 -1
- data/test/email_address/test_address.rb +24 -2
- data/test/email_address/test_domain_matcher.rb +12 -6
- data/test/email_address/test_host.rb +7 -1
- data/test/email_address/test_local.rb +7 -3
- data/test/email_address/test_matcher.rb +44 -0
- data/test/test_email_address.rb +5 -0
- metadata +22 -3
data/lib/email_address/config.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
module EmailAddress
|
2
2
|
##############################################################################
|
3
|
+
#
|
4
|
+
# DEPRECATING... See EmailAddress::Matcher now
|
5
|
+
#
|
3
6
|
# DomainMatcher - Matches a domain to a set of patterns
|
4
|
-
#
|
7
|
+
#
|
5
8
|
# Match Patterns
|
6
9
|
# hostname sub.domain.tld
|
7
10
|
# domain domain.tld
|
8
|
-
# registration domain
|
11
|
+
# registration domain
|
9
12
|
# tld .tld, .domain.tld
|
10
13
|
##############################################################################
|
11
14
|
class DomainMatcher
|
@@ -38,7 +41,7 @@ module EmailAddress
|
|
38
41
|
def rule_matches?(rule)
|
39
42
|
rule.downcase!
|
40
43
|
@host_name == rule || registration_name_matches?(rule) ||
|
41
|
-
domain_matches?(rule) || tld_matches?(rule)
|
44
|
+
domain_matches?(rule) || tld_matches?(rule) || glob_matches?(rule)
|
42
45
|
end
|
43
46
|
|
44
47
|
def list_matches?(list)
|
@@ -46,6 +49,11 @@ module EmailAddress
|
|
46
49
|
false
|
47
50
|
end
|
48
51
|
|
52
|
+
# Matches a rule as a glob match, with * and ? characters
|
53
|
+
def glob_matches?(rule)
|
54
|
+
File.fnmatch?(rule, @host_name)
|
55
|
+
end
|
56
|
+
|
49
57
|
# Does "sub.example.com" match "example" registration name
|
50
58
|
def registration_name_matches?(rule)
|
51
59
|
rule.match(/\A(\w+)\z/) && @host.registration_name == rule.downcase ? true : false
|
@@ -58,7 +66,7 @@ module EmailAddress
|
|
58
66
|
|
59
67
|
# Does "sub.example.com" match ".com" and ".example.com" top level names?
|
60
68
|
def tld_matches?(rule)
|
61
|
-
rule.match(/\A\..+\z/) &&
|
69
|
+
rule.match(/\A\..+\z/) &&
|
62
70
|
( @host_name[-rule.size, rule.size] == rule.downcase || ".#{@host_name}" == rule) \
|
63
71
|
? true : false
|
64
72
|
end
|
@@ -60,9 +60,7 @@ module EmailAddress
|
|
60
60
|
# Returns provider based on configured domain name matches, or nil if unmatched
|
61
61
|
# For best results, # consider the Exchanger.provider result as well.
|
62
62
|
def provider
|
63
|
-
base = EmailAddress::Config.providers[:default]
|
64
63
|
EmailAddress::Config.providers.each do |name, defn|
|
65
|
-
defn = base.merge(defn)
|
66
64
|
return name if EmailAddress::DomainMatcher.matches?(@host_name, defn[:domains])
|
67
65
|
end
|
68
66
|
nil
|
@@ -0,0 +1,62 @@
|
|
1
|
+
################################################################################
|
2
|
+
# ActiveRecord v5.0 Custom Type
|
3
|
+
# This class is not automatically loaded by the gem.
|
4
|
+
#-------------------------------------------------------------------------------
|
5
|
+
# 1) Register this type
|
6
|
+
#
|
7
|
+
# # config/initializers.types.rb
|
8
|
+
# require "email_address/email_address_type"
|
9
|
+
# ActiveRecord::Type.register(:email_address, EmailAddress::Address)
|
10
|
+
# ActiveRecord::Type.register(:canonical_email_address,
|
11
|
+
# EmailAddress::CanonicalEmailAddressType)
|
12
|
+
#
|
13
|
+
# 2) Define your email address columns in your model class
|
14
|
+
#
|
15
|
+
# class User < ActiveRecord::Base
|
16
|
+
# attribute :email, :email_address
|
17
|
+
# attribute :unique_email, :canonical_email_address
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# 3) Profit!
|
21
|
+
#
|
22
|
+
# user = User.new(email:"Pat.Smith+registrations@gmail.com",
|
23
|
+
# unique_email:"Pat.Smith+registrations@gmail.com")
|
24
|
+
# user.email #=> "pat.smith+registrations@gmail.com"
|
25
|
+
# user.unique_email #=> "patsmith@gmail.com"
|
26
|
+
################################################################################
|
27
|
+
|
28
|
+
class EmailAddress::EmailAddressType < ActiveRecord::Type::Value
|
29
|
+
|
30
|
+
# From user input, setter
|
31
|
+
def cast(value)
|
32
|
+
super(EmailAddress.normal(value))
|
33
|
+
end
|
34
|
+
|
35
|
+
# From a database value
|
36
|
+
def deserialize(value)
|
37
|
+
EmailAddress.normal(value)
|
38
|
+
end
|
39
|
+
#
|
40
|
+
# To a database value (string)
|
41
|
+
def serialize(value)
|
42
|
+
EmailAddress.normal(value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class CanonicalEmailAddressType < EmailAddress::EmailAddressType
|
47
|
+
|
48
|
+
# From user input, setter
|
49
|
+
def cast(value)
|
50
|
+
super(EmailAddress.canonical(value))
|
51
|
+
end
|
52
|
+
|
53
|
+
# From a database value
|
54
|
+
def deserialize(value)
|
55
|
+
EmailAddress.canonical(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
# To a database value (string)
|
59
|
+
def serialize(value)
|
60
|
+
EmailAddress.canonical(value)
|
61
|
+
end
|
62
|
+
end
|
@@ -6,6 +6,20 @@ module EmailAddress
|
|
6
6
|
class Exchanger
|
7
7
|
include Enumerable
|
8
8
|
|
9
|
+
def self.cached(host)
|
10
|
+
@host_cache ||= {}
|
11
|
+
@cache_size ||= ENV['EMAIL_ADDRESS_CACHE_SIZE'].to_i || 100
|
12
|
+
if @host_cache.has_key?(host)
|
13
|
+
o = @host_cache.delete(host)
|
14
|
+
@host_cache[host] = o # LRU cache, move to end
|
15
|
+
elsif @host_cache.size >= @cache_size
|
16
|
+
@host_cache.delete(@host_cache.keys.first)
|
17
|
+
@host_cache[host] = new(host)
|
18
|
+
else
|
19
|
+
@host_cache[host] = new(host)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
9
23
|
def initialize(host, options={})
|
10
24
|
@host = host
|
11
25
|
@options = options
|
@@ -46,6 +60,8 @@ module EmailAddress
|
|
46
60
|
ress = dns.getresources(@host, Resolv::DNS::Resource::IN::MX)
|
47
61
|
ress.map { |r| [r.exchange.to_s, IPSocket::getaddress(r.exchange.to_s), r.preference] }
|
48
62
|
end
|
63
|
+
rescue SocketError # not found, but could also mean network not work
|
64
|
+
@_dns_a_record ||= []
|
49
65
|
end
|
50
66
|
|
51
67
|
# Returns Array of domain names for the MX'ers, used to determine the Provider
|
@@ -55,13 +71,29 @@ module EmailAddress
|
|
55
71
|
|
56
72
|
# Returns an array of MX IP address (String) for the given email domain
|
57
73
|
def mx_ips
|
58
|
-
mxers
|
74
|
+
mxers.map {|m| m[1] }
|
59
75
|
end
|
60
76
|
|
61
77
|
# Given a cidr (ip/bits) and ip address, returns true on match. Caches cidr object.
|
62
78
|
def in_cidr?(cidr)
|
63
|
-
|
64
|
-
|
79
|
+
if cidr.include?(":")
|
80
|
+
in_ipv6_cidr?(cidr)
|
81
|
+
else
|
82
|
+
in_ipv4_cidr?(cidr)
|
83
|
+
end
|
65
84
|
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def in_ipv4_cidr?(cidr)
|
89
|
+
cidr = NetAddr::CIDR.create(cidr)
|
90
|
+
mx_ips.find { |ip| !ip.include?(":") && cidr.matches?(ip) } ? true : false
|
91
|
+
end
|
92
|
+
|
93
|
+
def in_ipv6_cidr?(cidr)
|
94
|
+
cidr = NetAddr::CIDR.create(cidr)
|
95
|
+
mx_ips.find { |ip| ip.include?(":") && cidr.matches?(ip) } ? true : false
|
96
|
+
end
|
97
|
+
|
66
98
|
end
|
67
99
|
end
|
data/lib/email_address/host.rb
CHANGED
@@ -22,10 +22,11 @@ module EmailAddress
|
|
22
22
|
# host name -
|
23
23
|
# * full domain name after @ for email types
|
24
24
|
# * fully-qualified domain name
|
25
|
-
# host type -
|
25
|
+
# host type -
|
26
26
|
# :email - email address domain
|
27
27
|
# :mx - email exchanger domain
|
28
28
|
def initialize(host_name, host_type=:email)
|
29
|
+
host_name||= ''
|
29
30
|
@host_name = host_name.downcase
|
30
31
|
@host_type = host_type
|
31
32
|
parse_host(@host_name)
|
@@ -34,7 +35,8 @@ module EmailAddress
|
|
34
35
|
def to_s
|
35
36
|
@host_name
|
36
37
|
end
|
37
|
-
|
38
|
+
alias :name :to_s
|
39
|
+
|
38
40
|
def parse_host(host)
|
39
41
|
@parser = EmailAddress::DomainParser.new(host)
|
40
42
|
@parts = @parser.parts
|
@@ -57,7 +59,7 @@ module EmailAddress
|
|
57
59
|
|
58
60
|
def exchanger
|
59
61
|
return nil unless @host_type == :email
|
60
|
-
@exchanger = EmailAddress::Exchanger.
|
62
|
+
@exchanger = EmailAddress::Exchanger.cached(self.dns_host_name)
|
61
63
|
end
|
62
64
|
|
63
65
|
def provider
|
@@ -67,10 +69,35 @@ module EmailAddress
|
|
67
69
|
end
|
68
70
|
@provider ||= :unknown
|
69
71
|
end
|
70
|
-
|
72
|
+
|
71
73
|
def matches?(*names)
|
72
74
|
DomainMatcher.matches?(@host_name, names.flatten)
|
73
75
|
end
|
74
76
|
|
77
|
+
def txt(alternate_host=nil)
|
78
|
+
Resolv::DNS.open do |dns|
|
79
|
+
records = dns.getresources(alternate_host || self.dns_host_name,
|
80
|
+
Resolv::DNS::Resource::IN::TXT)
|
81
|
+
records.empty? ? nil : records.map(&:data).join(" ")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Parses TXT record pairs into a hash
|
86
|
+
def txt_hash(alternate_host=nil)
|
87
|
+
fields = {}
|
88
|
+
record = self.txt(alternate_host)
|
89
|
+
return fields unless record
|
90
|
+
|
91
|
+
record.split(/\s*;\s*/).each do |pair|
|
92
|
+
(n,v) = pair.split(/\s*=\s*/)
|
93
|
+
fields[n.to_sym] = v
|
94
|
+
end
|
95
|
+
fields
|
96
|
+
end
|
97
|
+
|
98
|
+
def dmarc
|
99
|
+
self.txt_hash("_dmarc." + self.dns_host_name)
|
100
|
+
end
|
101
|
+
|
75
102
|
end
|
76
103
|
end
|
data/lib/email_address/local.rb
CHANGED
@@ -2,7 +2,7 @@ module EmailAddress
|
|
2
2
|
##############################################################################
|
3
3
|
# EmailAddress Local part consists of
|
4
4
|
# - comments
|
5
|
-
# - mailbox
|
5
|
+
# - mailbox
|
6
6
|
# - tag
|
7
7
|
#-----------------------------------------------------------------------------
|
8
8
|
# Parsing id provider-dependent, but RFC allows:
|
@@ -12,7 +12,7 @@ module EmailAddress
|
|
12
12
|
# Quote local part or dot-separated sub-parts x."y".z
|
13
13
|
# (comment)mailbox | mailbox(comment)
|
14
14
|
# 8-bit/UTF-8: allowed but mail-system defined
|
15
|
-
# RFC 5321 also warns that "a host that expects to receive mail SHOULD avoid
|
15
|
+
# RFC 5321 also warns that "a host that expects to receive mail SHOULD avoid
|
16
16
|
# defining mailboxes where the Local-part requires (or uses) the Quoted-string form".
|
17
17
|
# Postmaster: must always be case-insensitive
|
18
18
|
# Case: sensitive, but usually treated as equivalent
|
@@ -24,12 +24,13 @@ module EmailAddress
|
|
24
24
|
ROLE_NAMES = %w(info marketing sales support abuse noc security postmaster
|
25
25
|
hostmaster usenet news webmaster www uucp ftp)
|
26
26
|
|
27
|
-
def initialize(local,
|
28
|
-
@provider = EmailAddress::Config.provider(provider
|
27
|
+
def initialize(local, host=nil)
|
28
|
+
@provider = EmailAddress::Config.provider(host ? host.provider : :default)
|
29
29
|
parse(local)
|
30
30
|
end
|
31
31
|
|
32
32
|
def parse(local)
|
33
|
+
local ||= ''
|
33
34
|
@local = local =~ /\A"(.)"\z/ ? $1 : local
|
34
35
|
@local.gsub!(/\\(.)/, '\1') # Unescape
|
35
36
|
@local.downcase! unless @provider[:case_sensitive]
|
@@ -70,7 +71,7 @@ module EmailAddress
|
|
70
71
|
|
71
72
|
def format(m)
|
72
73
|
m = m.gsub(/([\\\"])/, '\\\1') # Escape \ and "
|
73
|
-
if m =~ /[ \"\(\)
|
74
|
+
if m =~ /[ \"\(\),:<>@\[\\\]]/ # Space and "(),:;<>@[\]
|
74
75
|
m = %Q("#{m}")
|
75
76
|
end
|
76
77
|
m
|
@@ -97,5 +98,10 @@ module EmailAddress
|
|
97
98
|
def role?
|
98
99
|
ROLE_NAMES.include?(@mailbox)
|
99
100
|
end
|
101
|
+
|
102
|
+
# Mailbox with trailing numbers removed
|
103
|
+
def root_name
|
104
|
+
canonical =~ /\A(.+?)\d+\z/ ? $1 : canonical
|
105
|
+
end
|
100
106
|
end
|
101
107
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module EmailAddress
|
2
|
+
##############################################################################
|
3
|
+
# Matcher - Allows matching of an email address against a list of matching
|
4
|
+
# tokens.
|
5
|
+
#
|
6
|
+
# Match Patterns
|
7
|
+
# * Top-Level-Domain: .org
|
8
|
+
# * Domain Name: example.com
|
9
|
+
# * Registration Name: hotmail. (matches any TLD)
|
10
|
+
# * Domain Glob: *.exampl?.com
|
11
|
+
# * Provider Name: google
|
12
|
+
# * Mailbox Name or Glob: user00*@
|
13
|
+
# * Address or Glob: postmaster@domain*.com
|
14
|
+
# * Provider or Registration: msn (?? Possible combo for either match?)
|
15
|
+
#
|
16
|
+
# Usage:
|
17
|
+
# m = EmailAddress::Matcher.new(".org example.com hotmail. google user*@ root@*.com")
|
18
|
+
# m.include?("pat@example.com")
|
19
|
+
##############################################################################
|
20
|
+
|
21
|
+
class Matcher
|
22
|
+
attr_reader :rules, :email
|
23
|
+
|
24
|
+
def self.matches?(rule, email)
|
25
|
+
EmailAddress::Matcher.new(rule).matches?(email)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(rules=[], empty_rules_return=false)
|
29
|
+
self.rules = rules
|
30
|
+
@empty_rules_return = empty_rules_return
|
31
|
+
end
|
32
|
+
|
33
|
+
def rules=(r)
|
34
|
+
@rules = r.is_a?(Array) ? r : r.split(/\s+/)
|
35
|
+
@rules = @rules.map(&:downcase)
|
36
|
+
end
|
37
|
+
|
38
|
+
def email=(e)
|
39
|
+
e = EmailAddress.new(e) if e.is_a?(String)
|
40
|
+
if e.is_a?(EmailAddress::Address)
|
41
|
+
@email_address = e
|
42
|
+
@email = e.normalize
|
43
|
+
@mailbox = e.mailbox
|
44
|
+
@domain = e.host.name
|
45
|
+
@domain_parts = e.host.parts
|
46
|
+
@provider = e.provider
|
47
|
+
elsif e.is_a?(Hash)
|
48
|
+
@email = e[:email]
|
49
|
+
@mailbox = e[:mailbox]
|
50
|
+
@domain = e[:domain]
|
51
|
+
@domain_parts = EmailAddress::DomainParser.new(@domain).parts
|
52
|
+
@provider = e[:provider]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Takes a email address string, returns true if it matches a rule
|
57
|
+
def include?(email_address)
|
58
|
+
self.email = email_address
|
59
|
+
return @empty_rules_return if @rules.empty?
|
60
|
+
@rules.each do |rule|
|
61
|
+
return true if registration_name_matches?(rule)
|
62
|
+
return true if tld_matches?(rule)
|
63
|
+
return true if provider_matches?(rule)
|
64
|
+
return true if domain_matches?(rule)
|
65
|
+
return true if email_matches?(rule)
|
66
|
+
return true if @email_address && ip_cidr_matches?(rule)
|
67
|
+
end
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
# Does "example." match any tld?
|
72
|
+
def registration_name_matches?(rule)
|
73
|
+
@domain_parts[:registration_name]+'.' == rule ? true : false
|
74
|
+
end
|
75
|
+
|
76
|
+
# Does "sub.example.com" match ".com" and ".example.com" top level names?
|
77
|
+
def tld_matches?(rule)
|
78
|
+
rule.match(/\A\..+\z/) &&
|
79
|
+
( @domain[-rule.size, rule.size] == rule || ".#{@domain}" == rule) \
|
80
|
+
? true : false
|
81
|
+
end
|
82
|
+
|
83
|
+
def provider_matches?(rule)
|
84
|
+
rule =~ /\A[\w\-]*\z/ && self.provider == rule.to_sym
|
85
|
+
end
|
86
|
+
|
87
|
+
def provider
|
88
|
+
@provider ||= EmailAddress::Config.providers.each do |prov, defn|
|
89
|
+
if defn.has_key?(:domains) && !defn[:domains].empty?
|
90
|
+
defn[:domains].each do |d|
|
91
|
+
if domain_matches?(d) || registration_name_matches?(d)
|
92
|
+
return @provider = prov
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
@provider ||= :unknown
|
98
|
+
end
|
99
|
+
|
100
|
+
# Does domain == rule or glob matches?
|
101
|
+
def domain_matches?(rule, domain=@domain)
|
102
|
+
return false if rule.include?("@")
|
103
|
+
domain == rule || File.fnmatch?(rule, domain)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Does "root@*.com" match "root@example.com" domain name
|
107
|
+
def email_matches?(rule)
|
108
|
+
return false unless rule.include?("@")
|
109
|
+
@email == rule || File.fnmatch?(rule, @email)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Does an IP of mail exchanger for "sub.example.com" match "xxx.xx.xx.xx/xx"?
|
113
|
+
def ip_cidr_matches?(rule)
|
114
|
+
return false unless rule.match(/\A\d.+\/\d+\z/) && @email_address.host.exchanger
|
115
|
+
@email_address.host.exchanger.in_cidr?(rule) ? true : false
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -2,6 +2,8 @@ module EmailAddress
|
|
2
2
|
class Validator
|
3
3
|
LEGIBLE_LOCAL_REGEX = /\A[a-z0-9]+(([\.\-\_\'\+][a-z0-9]+)+)?\z/
|
4
4
|
DOT_ATOM_REGEX = /[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]/
|
5
|
+
ERRORS = %i(bad_syntax bad_email bad_mailbox bad_domain bad_exchanger bad_recipient)
|
6
|
+
attr_reader :error
|
5
7
|
|
6
8
|
def self.validate(address, options={})
|
7
9
|
EmailAddress::Validator.new(address, options).valid?
|
@@ -13,25 +15,29 @@ module EmailAddress
|
|
13
15
|
@host = address.host
|
14
16
|
@options = options
|
15
17
|
@rules = EmailAddress::Config.provider(@host.provider)
|
16
|
-
@
|
18
|
+
@error = nil
|
17
19
|
end
|
18
20
|
|
21
|
+
# Returns true if email address seems valid, false otherwise.
|
22
|
+
# For error, call #error method to get error code (symbol).
|
19
23
|
def valid?
|
20
24
|
return false unless valid_sizes?
|
21
25
|
if @rules[:valid_mailbox] && ! @rules[:valid_mailbox].call(@local.to_s)
|
22
26
|
#p ["VALIDATOR", @local.to_s, @rules[:valid_mailbox]]
|
23
|
-
return invalid(:
|
27
|
+
return invalid(:bad_mailbox)
|
24
28
|
else
|
25
29
|
return false unless valid_local?
|
26
30
|
end
|
27
|
-
|
31
|
+
if EmailAddress::Config.options[:check_dns]
|
32
|
+
return invalid(:bad_exchanger) unless valid_mx? || (valid_dns? && @options[:allow_dns_a])
|
33
|
+
end
|
28
34
|
true
|
29
35
|
end
|
30
36
|
|
31
37
|
def mailbox_validator(v)
|
32
38
|
return true unless v
|
33
39
|
if v.is_a?(Proc)
|
34
|
-
return invalid(:
|
40
|
+
return invalid(:bad_mailbox) unless @rules[:valid_mailbox].call(@local)
|
35
41
|
elsif v == :legible
|
36
42
|
return legible?
|
37
43
|
elsif v == :rfc
|
@@ -56,19 +62,19 @@ module EmailAddress
|
|
56
62
|
end
|
57
63
|
|
58
64
|
def valid_sizes?
|
59
|
-
return invalid(:
|
60
|
-
return invalid(:
|
61
|
-
return invalid(:
|
62
|
-
return invalid(:
|
65
|
+
return invalid(:bad_email) unless @rules[:address_size].include?(@address.to_s.size)
|
66
|
+
return invalid(:bad_mailbox) unless @rules[:local_size ].include?(@local.to_s.size)
|
67
|
+
return invalid(:bad_mailbox) unless @rules[:mailbox_size].include?(@local.mailbox.size)
|
68
|
+
return invalid(:bad_domain ) unless @rules[:domain_size ].include?(@host.to_s.size)
|
63
69
|
true
|
64
70
|
end
|
65
71
|
|
66
72
|
def valid_local?
|
67
|
-
return invalid(:
|
68
|
-
return invalid(:
|
73
|
+
return invalid(:bad_mailbox) unless valid_local_part?(@local.mailbox)
|
74
|
+
return invalid(:bad_mailbox) unless @local.comment.empty? || valid_local_part?(@local.comment)
|
69
75
|
if @local.tag
|
70
76
|
@local.tag.split(@rules[:tag_separator]).each do |t|
|
71
|
-
return invalid(:
|
77
|
+
return invalid(:bad_mailbox, t) unless valid_local_part?(t)
|
72
78
|
end
|
73
79
|
end
|
74
80
|
true
|
@@ -81,7 +87,7 @@ module EmailAddress
|
|
81
87
|
|
82
88
|
|
83
89
|
def invalid(reason, *info)
|
84
|
-
@
|
90
|
+
@error = reason
|
85
91
|
#p "INVALID ----> #{reason} for #{@local.to_s}@#{@host.to_s} #{info.inspect}"
|
86
92
|
false
|
87
93
|
end
|