email_address 0.0.1 → 0.0.2
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/Gemfile +1 -0
- data/README.md +99 -32
- data/email_address.gemspec +2 -1
- data/lib/email_address.rb +14 -142
- data/lib/email_address/address.rb +70 -0
- data/lib/email_address/config.rb +75 -0
- data/lib/email_address/domain_matcher.rb +90 -0
- data/lib/email_address/domain_parser.rb +71 -0
- data/lib/email_address/exchanger.rb +67 -0
- data/lib/email_address/host.rb +71 -1
- data/lib/email_address/local.rb +97 -0
- data/lib/email_address/validator.rb +135 -0
- data/lib/email_address/version.rb +1 -1
- data/test/email_address/test_address.rb +30 -0
- data/test/email_address/test_config.rb +13 -0
- data/test/email_address/test_domain_matcher.rb +15 -0
- data/test/email_address/test_domain_parser.rb +29 -0
- data/test/email_address/test_exchanger.rb +19 -0
- data/test/email_address/test_host.rb +43 -0
- data/test/email_address/test_local.rb +27 -0
- data/test/email_address/test_validator.rb +16 -0
- data/test/test_email_address.rb +12 -0
- metadata +40 -27
- data/lib/email_address/esp.rb +0 -4
- data/lib/email_address/providers/default.rb +0 -8
- data/lib/email_address/providers/google.rb +0 -8
- data/lib/email_providers/address.rb +0 -102
- data/lib/email_providers/config.rb +0 -36
- data/lib/email_providers/factory.rb +0 -17
- data/lib/email_providers/host.rb +0 -87
- data/lib/email_providers/mail_exchanger.rb +0 -60
- data/lib/email_providers/mailbox.rb +0 -44
- data/lib/email_providers/providers/default.rb +0 -55
- data/lib/email_providers/providers/google.rb +0 -27
- data/lib/email_providers/version.rb +0 -3
- data/test/email_address.rb +0 -52
- data/test/email_address/address.rb +0 -16
- data/test/email_address/config.rb +0 -13
- data/test/email_address/host.rb +0 -29
- data/test/email_address/mail_exchanger.rb +0 -9
@@ -0,0 +1,90 @@
|
|
1
|
+
module EmailAddress
|
2
|
+
##############################################################################
|
3
|
+
# DomainMatcher - Matches a domain to a set of patterns
|
4
|
+
#
|
5
|
+
# Match Patterns
|
6
|
+
# hostname sub.domain.tld
|
7
|
+
# domain domain.tld
|
8
|
+
# registration domain
|
9
|
+
# tld .tld, .domain.tld
|
10
|
+
##############################################################################
|
11
|
+
class DomainMatcher
|
12
|
+
attr_reader :host_name, :parts, :domain_name, :registration_name,
|
13
|
+
:tld, :subdomains, :ip_address
|
14
|
+
|
15
|
+
def self.matches?(domain, rule)
|
16
|
+
DomainMatcher.new(domain, rule).matches?
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(host_name, rule=nil)
|
20
|
+
@host_name = host_name.downcase
|
21
|
+
@host = EmailAddress::Host.new(@host_name)
|
22
|
+
@rule = rule
|
23
|
+
matches?
|
24
|
+
end
|
25
|
+
|
26
|
+
def matches?(rule=nil)
|
27
|
+
rule ||= @rule
|
28
|
+
case rule
|
29
|
+
when String
|
30
|
+
rule_matches?(rule)
|
31
|
+
when Array
|
32
|
+
list_matches?(rule)
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def rule_matches?(rule)
|
39
|
+
rule.downcase!
|
40
|
+
@host_name == rule || registration_name_matches?(rule) ||
|
41
|
+
domain_matches?(rule) || tld_matches?(rule)
|
42
|
+
end
|
43
|
+
|
44
|
+
def list_matches?(list)
|
45
|
+
list.each {|rule| return true if rule_matches?(rule) }
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
# Does "sub.example.com" match "example" registration name
|
50
|
+
def registration_name_matches?(rule)
|
51
|
+
rule.match(/\A(\w+)\z/) && @host.registration_name == rule.downcase ? true : false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Does "sub.example.com" match "example.com" domain name
|
55
|
+
def domain_matches?(rule)
|
56
|
+
rule.match(/\A[^\.]+\.[^\.]+\z/) && @host.domain_name == rule.downcase ? true : false
|
57
|
+
end
|
58
|
+
|
59
|
+
# Does "sub.example.com" match ".com" and ".example.com" top level names?
|
60
|
+
def tld_matches?(rule)
|
61
|
+
rule.match(/\A\..+\z/) &&
|
62
|
+
( @host_name[-rule.size, rule.size] == rule.downcase || ".#{@host_name}" == rule) \
|
63
|
+
? true : false
|
64
|
+
end
|
65
|
+
|
66
|
+
# Does an IP of mail exchanger for "sub.example.com" match "xxx.xx.xx.xx/xx"?
|
67
|
+
def ip_cidr_matches?(rule)
|
68
|
+
return false unless rule.match(/\A\d.+\/\d+\z/) && @host.exchanger
|
69
|
+
@host.exchanger.in_cidr?(r) ? true : false
|
70
|
+
end
|
71
|
+
|
72
|
+
#def provider_matches?(rule)
|
73
|
+
# if rule.downcase.match(/\A\:(\w+)\z/)
|
74
|
+
# p ["PROVIDER", rule, @host_name, provider_by_domain ]
|
75
|
+
# prov = provider_by_domain
|
76
|
+
# prov && prov == $1 ? true : false
|
77
|
+
# end
|
78
|
+
#end
|
79
|
+
|
80
|
+
## Match only by
|
81
|
+
#def provider_by_domain
|
82
|
+
# base = EmailAddress::Config.providers[:default]
|
83
|
+
# EmailAddress::Config.providers.first do |name, defn|
|
84
|
+
# defn = base.merge(defn)
|
85
|
+
# return name if matches?(defn[:domains])
|
86
|
+
# end
|
87
|
+
# nil
|
88
|
+
#end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'simpleidn'
|
2
|
+
|
3
|
+
module EmailAddress
|
4
|
+
##############################################################################
|
5
|
+
# Builds hash of host name/domain name parts
|
6
|
+
# For: subdomain.example.co.uk
|
7
|
+
# host_name: "subdomain.example.co.uk"
|
8
|
+
# subdomains: "subdomain"
|
9
|
+
# registration_name: "example"
|
10
|
+
# domain_name: "example.co.uk"
|
11
|
+
# tld: "co.uk"
|
12
|
+
# ip_address: nil or "ipaddress" used in [ipaddress] syntax
|
13
|
+
##############################################################################
|
14
|
+
class DomainParser
|
15
|
+
attr_reader :host_name, :parts, :domain_name, :registration_name,
|
16
|
+
:tld, :subdomains, :ip_address
|
17
|
+
|
18
|
+
def self.parse(domain)
|
19
|
+
EmailAddress::DomainParser.new(domain).parts
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(host_name)
|
23
|
+
@host_name = host_name.downcase
|
24
|
+
parse_host(@host_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_host(host)
|
28
|
+
@host_name = host.strip.downcase.gsub(' ', '').gsub(/\(.*\)/, '')
|
29
|
+
@subdomains = @registration_name = @domain_name = @tld = ''
|
30
|
+
@ip_address = nil
|
31
|
+
|
32
|
+
# IP Address: [127.0.0.1], [IPV6:.....]
|
33
|
+
if @host_name =~ /\A\[(.+)\]\z/
|
34
|
+
@ip_address = $1
|
35
|
+
|
36
|
+
# Subdomain only (root@localhost)
|
37
|
+
elsif @host_name.index('.').nil?
|
38
|
+
@subdomains = @host_name
|
39
|
+
|
40
|
+
# Split sub.domain from .tld: *.com, *.xx.cc, *.cc
|
41
|
+
elsif @host_name =~ /\A(.+)\.(\w{3,10})\z/ ||
|
42
|
+
@host_name =~ /\A(.+)\.(\w{1,3}\.\w\w)\z/ ||
|
43
|
+
@host_name =~ /\A(.+)\.(\w\w)\z/
|
44
|
+
|
45
|
+
@tld = $2;
|
46
|
+
sld = $1 # Second level domain
|
47
|
+
if sld =~ /\A(.+)\.(.+)\z/ # is subdomain? sub.example [.tld]
|
48
|
+
@subdomains = $1
|
49
|
+
@registration_name = $2
|
50
|
+
else
|
51
|
+
@registration_name = sld
|
52
|
+
@domain_name = sld + '.' + @tld
|
53
|
+
end
|
54
|
+
@domain_name = @registration_name + '.' + @tld
|
55
|
+
end
|
56
|
+
@parts = {host_name:@host_name, subdomains:@subdomains, domain_name:@domain_name,
|
57
|
+
registration_name:@registration_name, tld:@tld, ip_address:@ip_address}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns provider based on configured domain name matches, or nil if unmatched
|
61
|
+
# For best results, # consider the Exchanger.provider result as well.
|
62
|
+
def provider
|
63
|
+
base = EmailAddress::Config.providers[:default]
|
64
|
+
EmailAddress::Config.providers.each do |name, defn|
|
65
|
+
defn = base.merge(defn)
|
66
|
+
return name if EmailAddress::DomainMatcher.matches?(@host_name, defn[:domains])
|
67
|
+
end
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
require 'netaddr'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module EmailAddress
|
6
|
+
class Exchanger
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(host, options={})
|
10
|
+
@host = host
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def each(&block)
|
15
|
+
mxers.each do |m|
|
16
|
+
yield({host:m[0], ip:m[1], priority:m[2]})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the provider name based on the MX-er host names, or nil if not matched
|
21
|
+
def provider
|
22
|
+
base = EmailAddress::Config.providers[:default]
|
23
|
+
EmailAddress::Config.providers.each do |name, defn|
|
24
|
+
defn = base.merge(defn)
|
25
|
+
self.each do |m|
|
26
|
+
return name if DomainMatcher.matches?(m[:host], defn[:exchangers])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_dns_a_record?
|
33
|
+
dns_a_record.size > 0 ? true : false
|
34
|
+
end
|
35
|
+
|
36
|
+
def dns_a_record
|
37
|
+
@_dns_a_record ||= Socket.gethostbyname(@host)
|
38
|
+
rescue SocketError # not found, but could also mean network not work
|
39
|
+
@_dns_a_record ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
# 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]]
|
43
|
+
# If not found, returns []
|
44
|
+
def mxers
|
45
|
+
@mxers ||= Resolv::DNS.open do |dns|
|
46
|
+
ress = dns.getresources(@host, Resolv::DNS::Resource::IN::MX)
|
47
|
+
ress.map { |r| [r.exchange.to_s, IPSocket::getaddress(r.exchange.to_s), r.preference] }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns Array of domain names for the MX'ers, used to determine the Provider
|
52
|
+
def domains
|
53
|
+
mxers.map {|m| EmailAddress::DomainParser.new(m.first).domain_name}.sort.uniq
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns an array of MX IP address (String) for the given email domain
|
57
|
+
def mx_ips
|
58
|
+
mxers(domain).map {|m| m[1] }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Given a cidr (ip/bits) and ip address, returns true on match. Caches cidr object.
|
62
|
+
def in_cidr?(cidr)
|
63
|
+
@cidr ||= NetAddr::CIDR.create(cidr)
|
64
|
+
mx_ips.first { |ip| @cider.matches?(ip) } ? true : false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/email_address/host.rb
CHANGED
@@ -1,6 +1,76 @@
|
|
1
|
+
require 'simpleidn'
|
2
|
+
|
1
3
|
module EmailAddress
|
4
|
+
##############################################################################
|
5
|
+
# Hostname management for the email address
|
6
|
+
# IPv6/IPv6: [128.0.0.1], [IPv6:2001:db8:1ff::a0b:dbd0]
|
7
|
+
# Comments: (comment)example.com, example.com(comment)
|
8
|
+
# Internationalized: Unicode to Punycode
|
9
|
+
# Length: up to 255 characters
|
10
|
+
# Parts for: subdomain.example.co.uk
|
11
|
+
# host_name: "subdomain.example.co.uk"
|
12
|
+
# subdomain: "subdomain"
|
13
|
+
# registration_name: "example"
|
14
|
+
# domain_name: "example.co.uk"
|
15
|
+
# tld: "co.uk"
|
16
|
+
# ip_address: nil or "ipaddress" used in [ipaddress] syntax
|
17
|
+
##############################################################################
|
2
18
|
class Host
|
3
|
-
|
19
|
+
attr_reader :host_name, :parts, :domain_name, :registration_name,
|
20
|
+
:tld, :subdomains, :ip_address
|
21
|
+
|
22
|
+
# host name -
|
23
|
+
# * full domain name after @ for email types
|
24
|
+
# * fully-qualified domain name
|
25
|
+
# host type -
|
26
|
+
# :email - email address domain
|
27
|
+
# :mx - email exchanger domain
|
28
|
+
def initialize(host_name, host_type=:email)
|
29
|
+
@host_name = host_name.downcase
|
30
|
+
@host_type = host_type
|
31
|
+
parse_host(@host_name)
|
4
32
|
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@host_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_host(host)
|
39
|
+
@parser = EmailAddress::DomainParser.new(host)
|
40
|
+
@parts = @parser.parts
|
41
|
+
@parts.each { |k,v| instance_variable_set("@#{k}", v) }
|
42
|
+
end
|
43
|
+
|
44
|
+
# The host name to send to DNS lookup, Punycode-escaped
|
45
|
+
def dns_host_name
|
46
|
+
@dns_host_name ||= ::SimpleIDN.to_ascii(@host_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def normalize
|
50
|
+
dns_host_name
|
51
|
+
end
|
52
|
+
|
53
|
+
# The canonical host name is the simplified, DNS host name
|
54
|
+
def canonical
|
55
|
+
dns_host_name
|
56
|
+
end
|
57
|
+
|
58
|
+
def exchanger
|
59
|
+
return nil unless @host_type == :email
|
60
|
+
@exchanger = EmailAddress::Exchanger.new(@host_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def provider
|
64
|
+
@provider ||= @parser.provider
|
65
|
+
if !@provider && EmailAddress::Config.options[:check_dns]
|
66
|
+
@provider = exchanger.provider
|
67
|
+
end
|
68
|
+
@provider ||= :unknown
|
69
|
+
end
|
70
|
+
|
71
|
+
def matches?(*names)
|
72
|
+
DomainMatcher.matches?(@host_name, names.flatten)
|
73
|
+
end
|
74
|
+
|
5
75
|
end
|
6
76
|
end
|
data/lib/email_address/local.rb
CHANGED
@@ -1,4 +1,101 @@
|
|
1
1
|
module EmailAddress
|
2
|
+
##############################################################################
|
3
|
+
# EmailAddress Local part consists of
|
4
|
+
# - comments
|
5
|
+
# - mailbox
|
6
|
+
# - tag
|
7
|
+
#-----------------------------------------------------------------------------
|
8
|
+
# Parsing id provider-dependent, but RFC allows:
|
9
|
+
# Chars: A-Z a-z 0-9 . ! # $ % ' * + - / = ? ^G _ { | } ~
|
10
|
+
# Quoted: space ( ) , : ; < > @ [ ]
|
11
|
+
# Quoted-Backslash-Escaped: \ "
|
12
|
+
# Quote local part or dot-separated sub-parts x."y".z
|
13
|
+
# (comment)mailbox | mailbox(comment)
|
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
|
16
|
+
# defining mailboxes where the Local-part requires (or uses) the Quoted-string form".
|
17
|
+
# Postmaster: must always be case-insensitive
|
18
|
+
# Case: sensitive, but usually treated as equivalent
|
19
|
+
# Local Parts: comment, mailbox tag
|
20
|
+
# Length: up to 64 characters
|
21
|
+
##############################################################################
|
2
22
|
class Local
|
23
|
+
attr_accessor :mailbox, :comment, :tag, :local
|
24
|
+
ROLE_NAMES = %w(info marketing sales support abuse noc security postmaster
|
25
|
+
hostmaster usenet news webmaster www uucp ftp)
|
26
|
+
|
27
|
+
def initialize(local, provider=nil)
|
28
|
+
@provider = EmailAddress::Config.provider(provider || :default)
|
29
|
+
parse(local)
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse(local)
|
33
|
+
@local = local =~ /\A"(.)"\z/ ? $1 : local
|
34
|
+
@local.gsub!(/\\(.)/, '\1') # Unescape
|
35
|
+
@local.downcase! unless @provider[:case_sensitive]
|
36
|
+
@local.gsub!(' ','') unless @provider[:keep_space]
|
37
|
+
|
38
|
+
@mailbox = @local
|
39
|
+
@comment = @tag = nil
|
40
|
+
parse_comment
|
41
|
+
parse_tag
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
normalize
|
46
|
+
end
|
47
|
+
|
48
|
+
def normalize
|
49
|
+
m = @mailbox
|
50
|
+
m+= @provider[:tag_separator] + @tag if @tag && !@tag.empty?
|
51
|
+
m+= "(#{@comment})" if @comment && !@comment.empty? && @provider[:keep_comment]
|
52
|
+
format(m)
|
53
|
+
end
|
54
|
+
|
55
|
+
def normalize!
|
56
|
+
parse(normalize)
|
57
|
+
end
|
58
|
+
|
59
|
+
def canonical
|
60
|
+
m= @mailbox.downcase
|
61
|
+
if @provider[:canonical_mailbox]
|
62
|
+
m = @provider[:canonical_mailbox].call(m)
|
63
|
+
end
|
64
|
+
format(m)
|
65
|
+
end
|
66
|
+
|
67
|
+
def canonicalize!
|
68
|
+
parse(canonical)
|
69
|
+
end
|
70
|
+
|
71
|
+
def format(m)
|
72
|
+
m = m.gsub(/([\\\"])/, '\\\1') # Escape \ and "
|
73
|
+
if m =~ /[ \"\(\),:'<>@\[\\\]]/ # Space and "(),:;<>@[\]
|
74
|
+
m = %Q("#{m}")
|
75
|
+
end
|
76
|
+
m
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_comment
|
80
|
+
if @mailbox =~ /\A\((.+?)\)(.+)\z/
|
81
|
+
(@comment, @mailbox) = [$1, $2]
|
82
|
+
elsif @mailbox =~ /\A(.+)\((.+?)\)\z/
|
83
|
+
(@mailbox, @comment) = [$1, $2]
|
84
|
+
else
|
85
|
+
@comment = '';
|
86
|
+
@mailbox = @local
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_tag
|
91
|
+
return unless @provider[:tag_separator]
|
92
|
+
parts = @mailbox.split(@provider[:tag_separator], 2)
|
93
|
+
(@mailbox, @tag) = *parts if parts.size > 1
|
94
|
+
end
|
95
|
+
|
96
|
+
# RFC2142 - Mailbox Names for Common Services, Rules, and Functions
|
97
|
+
def role?
|
98
|
+
ROLE_NAMES.include?(@mailbox)
|
99
|
+
end
|
3
100
|
end
|
4
101
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module EmailAddress
|
2
|
+
class Validator
|
3
|
+
LEGIBLE_LOCAL_REGEX = /\A[a-z0-9]+(([\.\-\_\'\+][a-z0-9]+)+)?\z/
|
4
|
+
DOT_ATOM_REGEX = /[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]/
|
5
|
+
|
6
|
+
def self.validate(address, options={})
|
7
|
+
EmailAddress::Validator.new(address, options).valid?
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(address, options={})
|
11
|
+
@address = address
|
12
|
+
@local = address.local
|
13
|
+
@host = address.host
|
14
|
+
@options = options
|
15
|
+
@rules = EmailAddress::Config.provider(@host.provider)
|
16
|
+
@errors = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid?
|
20
|
+
return false unless valid_sizes?
|
21
|
+
if @rules[:valid_mailbox] && ! @rules[:valid_mailbox].call(@local.to_s)
|
22
|
+
#p ["VALIDATOR", @local.to_s, @rules[:valid_mailbox]]
|
23
|
+
return invalid(:mailbox_validator)
|
24
|
+
else
|
25
|
+
return false unless valid_local?
|
26
|
+
end
|
27
|
+
return invalid(:mx) unless valid_mx? || (valid_dns? && @options[:allow_dns_a])
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def mailbox_validator(v)
|
32
|
+
return true unless v
|
33
|
+
if v.is_a?(Proc)
|
34
|
+
return invalid(:mailbox_proc) unless @rules[:valid_mailbox].call(@local)
|
35
|
+
elsif v == :legible
|
36
|
+
return legible?
|
37
|
+
elsif v == :rfc
|
38
|
+
return rfc_compliant?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# True if the DNS A record or MX records are defined
|
43
|
+
# Why A record? Some domains are misconfigured with only the A record.
|
44
|
+
def valid_dns?
|
45
|
+
@host.exchanger.has_dns_a_record?
|
46
|
+
end
|
47
|
+
|
48
|
+
# True if the DNS MX records have been defined. More strict than #valid?
|
49
|
+
def valid_mx?
|
50
|
+
@host.exchanger.mxers.size > 0
|
51
|
+
end
|
52
|
+
|
53
|
+
# Allows single, simple punctua3Nz=Xj/7c9 tion character between words
|
54
|
+
def legible?
|
55
|
+
@local.to_s =~ LEGIBLE_LOCAL_REGEX
|
56
|
+
end
|
57
|
+
|
58
|
+
def valid_sizes?
|
59
|
+
return invalid(:address_size) unless @rules[:address_size].include?(@address.to_s.size)
|
60
|
+
return invalid(:domain_size ) unless @rules[:domain_size ].include?(@host.to_s.size)
|
61
|
+
return invalid(:local_size ) unless @rules[:local_size ].include?(@local.to_s.size)
|
62
|
+
return invalid(:mailbox_size) unless @rules[:mailbox_size].include?(@local.mailbox.size)
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def valid_local?
|
67
|
+
return invalid(:mailbox) unless valid_local_part?(@local.mailbox)
|
68
|
+
return invalid(:comment) unless @local.comment.empty? || valid_local_part?(@local.comment)
|
69
|
+
if @local.tag
|
70
|
+
@local.tag.split(@rules[:tag_separator]).each do |t|
|
71
|
+
return invalid(:tag, t) unless valid_local_part?(t)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
# Valid within a mailbox, tag, comment
|
78
|
+
def valid_local_part?(p)
|
79
|
+
p =~ LEGIBLE_LOCAL_REGEX
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def invalid(reason, *info)
|
84
|
+
@errors << reason
|
85
|
+
#p "INVALID ----> #{reason} for #{@local.to_s}@#{@host.to_s} #{info.inspect}"
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
def valid_google_local?
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
############################################################################
|
94
|
+
# RFC5322 Rules (Oct 2008):
|
95
|
+
#---------------------------------------------------------------------------
|
96
|
+
# addr-spec = local-part "@" domain
|
97
|
+
# local-part = dot-atom / quoted-string / obs-local-part
|
98
|
+
# domain = dot-atom / domain-literal / obs-domain
|
99
|
+
# domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
|
100
|
+
# dtext = %d33-90 / ; Printable US-ASCII
|
101
|
+
# %d94-126 / ; characters not including
|
102
|
+
# obs-dtext ; "[", "]", or "\"
|
103
|
+
# atext = ALPHA / DIGIT / ; Printable US-ASCII
|
104
|
+
# "!" / "#" / ; characters not including
|
105
|
+
# "$" / "%" / ; specials. Used for atoms.
|
106
|
+
# "&" / "'" /
|
107
|
+
# "*" / "+" /
|
108
|
+
# "-" / "/" /
|
109
|
+
# "=" / "?" /
|
110
|
+
# "^" / "_" /
|
111
|
+
# "`" / "{" /
|
112
|
+
# "|" / "}" /
|
113
|
+
# "~"
|
114
|
+
# atom = [CFWS] 1*atext [CFWS]
|
115
|
+
# dot-atom-text = 1*atext *("." 1*atext)
|
116
|
+
# dot-atom = [CFWS] dot-atom-text [CFWS]
|
117
|
+
# specials = "(" / ")" / ; Special characters that do
|
118
|
+
# "<" / ">" / ; not appear in atext
|
119
|
+
# "[" / "]" /
|
120
|
+
# ":" / ";" /
|
121
|
+
# "@" / "\" /
|
122
|
+
# "," / "." /
|
123
|
+
# DQUOTE
|
124
|
+
# qtext = %d33 / ; Printable US-ASCII
|
125
|
+
# %d35-91 / ; characters not including
|
126
|
+
# %d93-126 / ; "\" or the quote character
|
127
|
+
# obs-qtext
|
128
|
+
# qcontent = qtext / quoted-pair
|
129
|
+
# quoted-string = [CFWS]
|
130
|
+
# DQUOTE *([FWS] qcontent) [FWS] DQUOTE
|
131
|
+
# [CFWS]
|
132
|
+
############################################################################
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|