email_address 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|