email_address 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +10 -0
- data/Gemfile +0 -1
- data/README.md +451 -197
- data/Rakefile +4 -9
- data/email_address.gemspec +9 -5
- data/lib/email_address.rb +55 -24
- data/lib/email_address/active_record_validator.rb +5 -5
- data/lib/email_address/address.rb +152 -72
- data/lib/email_address/canonical_email_address_type.rb +46 -0
- data/lib/email_address/config.rb +148 -64
- data/lib/email_address/email_address_type.rb +15 -31
- data/lib/email_address/exchanger.rb +31 -34
- data/lib/email_address/host.rb +327 -51
- data/lib/email_address/local.rb +304 -52
- data/lib/email_address/version.rb +1 -1
- data/test/activerecord/test_ar.rb +22 -0
- data/test/activerecord/user.rb +71 -0
- data/test/email_address/test_address.rb +53 -27
- data/test/email_address/test_config.rb +23 -8
- data/test/email_address/test_exchanger.rb +22 -10
- data/test/email_address/test_host.rb +47 -6
- data/test/email_address/test_local.rb +80 -16
- data/test/test_email_address.rb +38 -4
- data/test/test_helper.rb +7 -5
- metadata +68 -34
- data/lib/email_address/domain_matcher.rb +0 -98
- data/lib/email_address/domain_parser.rb +0 -69
- data/lib/email_address/matcher.rb +0 -119
- data/lib/email_address/validator.rb +0 -141
- data/test/email_address/test_domain_matcher.rb +0 -21
- data/test/email_address/test_domain_parser.rb +0 -29
- data/test/email_address/test_matcher.rb +0 -44
- data/test/email_address/test_validator.rb +0 -16
@@ -1,119 +0,0 @@
|
|
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
|
@@ -1,141 +0,0 @@
|
|
1
|
-
module EmailAddress
|
2
|
-
class Validator
|
3
|
-
LEGIBLE_LOCAL_REGEX = /\A[a-z0-9]+(([\.\-\_\'\+][a-z0-9]+)+)?\z/
|
4
|
-
DOT_ATOM_REGEX = /[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]/
|
5
|
-
ERRORS = %i(bad_syntax bad_email bad_mailbox bad_domain bad_exchanger bad_recipient)
|
6
|
-
attr_reader :error
|
7
|
-
|
8
|
-
def self.validate(address, options={})
|
9
|
-
EmailAddress::Validator.new(address, options).valid?
|
10
|
-
end
|
11
|
-
|
12
|
-
def initialize(address, options={})
|
13
|
-
@address = address
|
14
|
-
@local = address.local
|
15
|
-
@host = address.host
|
16
|
-
@options = options
|
17
|
-
@rules = EmailAddress::Config.provider(@host.provider)
|
18
|
-
@error = nil
|
19
|
-
end
|
20
|
-
|
21
|
-
# Returns true if email address seems valid, false otherwise.
|
22
|
-
# For error, call #error method to get error code (symbol).
|
23
|
-
def valid?
|
24
|
-
return false unless valid_sizes?
|
25
|
-
if @rules[:valid_mailbox] && ! @rules[:valid_mailbox].call(@local.to_s)
|
26
|
-
#p ["VALIDATOR", @local.to_s, @rules[:valid_mailbox]]
|
27
|
-
return invalid(:bad_mailbox)
|
28
|
-
else
|
29
|
-
return false unless valid_local?
|
30
|
-
end
|
31
|
-
if EmailAddress::Config.options[:check_dns]
|
32
|
-
return invalid(:bad_exchanger) unless valid_mx? || (valid_dns? && @options[:allow_dns_a])
|
33
|
-
end
|
34
|
-
true
|
35
|
-
end
|
36
|
-
|
37
|
-
def mailbox_validator(v)
|
38
|
-
return true unless v
|
39
|
-
if v.is_a?(Proc)
|
40
|
-
return invalid(:bad_mailbox) unless @rules[:valid_mailbox].call(@local)
|
41
|
-
elsif v == :legible
|
42
|
-
return legible?
|
43
|
-
elsif v == :rfc
|
44
|
-
return rfc_compliant?
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# True if the DNS A record or MX records are defined
|
49
|
-
# Why A record? Some domains are misconfigured with only the A record.
|
50
|
-
def valid_dns?
|
51
|
-
@host.exchanger.has_dns_a_record?
|
52
|
-
end
|
53
|
-
|
54
|
-
# True if the DNS MX records have been defined. More strict than #valid?
|
55
|
-
def valid_mx?
|
56
|
-
@host.exchanger.mxers.size > 0
|
57
|
-
end
|
58
|
-
|
59
|
-
# Allows single, simple punctua3Nz=Xj/7c9 tion character between words
|
60
|
-
def legible?
|
61
|
-
@local.to_s =~ LEGIBLE_LOCAL_REGEX
|
62
|
-
end
|
63
|
-
|
64
|
-
def valid_sizes?
|
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)
|
69
|
-
true
|
70
|
-
end
|
71
|
-
|
72
|
-
def valid_local?
|
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)
|
75
|
-
if @local.tag
|
76
|
-
@local.tag.split(@rules[:tag_separator]).each do |t|
|
77
|
-
return invalid(:bad_mailbox, t) unless valid_local_part?(t)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
true
|
81
|
-
end
|
82
|
-
|
83
|
-
# Valid within a mailbox, tag, comment
|
84
|
-
def valid_local_part?(p)
|
85
|
-
p =~ LEGIBLE_LOCAL_REGEX
|
86
|
-
end
|
87
|
-
|
88
|
-
|
89
|
-
def invalid(reason, *info)
|
90
|
-
@error = reason
|
91
|
-
#p "INVALID ----> #{reason} for #{@local.to_s}@#{@host.to_s} #{info.inspect}"
|
92
|
-
false
|
93
|
-
end
|
94
|
-
|
95
|
-
def valid_google_local?
|
96
|
-
true
|
97
|
-
end
|
98
|
-
|
99
|
-
############################################################################
|
100
|
-
# RFC5322 Rules (Oct 2008):
|
101
|
-
#---------------------------------------------------------------------------
|
102
|
-
# addr-spec = local-part "@" domain
|
103
|
-
# local-part = dot-atom / quoted-string / obs-local-part
|
104
|
-
# domain = dot-atom / domain-literal / obs-domain
|
105
|
-
# domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
|
106
|
-
# dtext = %d33-90 / ; Printable US-ASCII
|
107
|
-
# %d94-126 / ; characters not including
|
108
|
-
# obs-dtext ; "[", "]", or "\"
|
109
|
-
# atext = ALPHA / DIGIT / ; Printable US-ASCII
|
110
|
-
# "!" / "#" / ; characters not including
|
111
|
-
# "$" / "%" / ; specials. Used for atoms.
|
112
|
-
# "&" / "'" /
|
113
|
-
# "*" / "+" /
|
114
|
-
# "-" / "/" /
|
115
|
-
# "=" / "?" /
|
116
|
-
# "^" / "_" /
|
117
|
-
# "`" / "{" /
|
118
|
-
# "|" / "}" /
|
119
|
-
# "~"
|
120
|
-
# atom = [CFWS] 1*atext [CFWS]
|
121
|
-
# dot-atom-text = 1*atext *("." 1*atext)
|
122
|
-
# dot-atom = [CFWS] dot-atom-text [CFWS]
|
123
|
-
# specials = "(" / ")" / ; Special characters that do
|
124
|
-
# "<" / ">" / ; not appear in atext
|
125
|
-
# "[" / "]" /
|
126
|
-
# ":" / ";" /
|
127
|
-
# "@" / "\" /
|
128
|
-
# "," / "." /
|
129
|
-
# DQUOTE
|
130
|
-
# qtext = %d33 / ; Printable US-ASCII
|
131
|
-
# %d35-91 / ; characters not including
|
132
|
-
# %d93-126 / ; "\" or the quote character
|
133
|
-
# obs-qtext
|
134
|
-
# qcontent = qtext / quoted-pair
|
135
|
-
# quoted-string = [CFWS]
|
136
|
-
# DQUOTE *([FWS] qcontent) [FWS] DQUOTE
|
137
|
-
# [CFWS]
|
138
|
-
############################################################################
|
139
|
-
|
140
|
-
end
|
141
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require_relative '../test_helper'
|
2
|
-
|
3
|
-
class TestDomainMatcher < MiniTest::Test
|
4
|
-
MATCHER = EmailAddress::DomainMatcher
|
5
|
-
|
6
|
-
def test_hostname
|
7
|
-
assert_equal true, MATCHER.matches?("example.com", "example.com")
|
8
|
-
assert_equal true, MATCHER.matches?("example.com", "example")
|
9
|
-
assert_equal true, MATCHER.matches?("example.com", ".com")
|
10
|
-
assert_equal true, MATCHER.matches?("example.com", ".example.com")
|
11
|
-
end
|
12
|
-
|
13
|
-
def test_list
|
14
|
-
assert_equal true, MATCHER.matches?("example.com", %w(ex .tld example))
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_glob_matches
|
18
|
-
assert_equal true, MATCHER.matches?("example.com", %w(ex*.com))
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
require_relative '../test_helper'
|
2
|
-
|
3
|
-
class TestDomainParser < MiniTest::Test
|
4
|
-
def test_hostname
|
5
|
-
parts = EmailAddress::DomainParser.parse("Example.com")
|
6
|
-
assert_equal 'example.com', parts[:domain_name]
|
7
|
-
assert_equal 'example', parts[:registration_name]
|
8
|
-
assert_equal 'com', parts[:tld]
|
9
|
-
assert_equal '', parts[:subdomains]
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_sld
|
13
|
-
parts = EmailAddress::DomainParser.parse("sub.Example.co.uk")
|
14
|
-
assert_equal 'example.co.uk', parts[:domain_name]
|
15
|
-
assert_equal 'co.uk', parts[:tld]
|
16
|
-
assert_equal 'sub', parts[:subdomains]
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_provider
|
20
|
-
parser = EmailAddress::DomainParser.new("gmail.com")
|
21
|
-
assert_equal :google, parser.provider
|
22
|
-
end
|
23
|
-
|
24
|
-
def test_yahoo
|
25
|
-
parser = EmailAddress::DomainParser.new("yahoo.co.uk")
|
26
|
-
assert_equal :yahoo, parser.provider
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require_relative '../test_helper'
|
2
|
-
|
3
|
-
class TestDomainMatcher < MiniTest::Test
|
4
|
-
|
5
|
-
def setup
|
6
|
-
@matcher = EmailAddress::Matcher.new(
|
7
|
-
".org example.com domain*.com hotmail. google user*@ root@*.com 207.99.0.0/16")
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_tld
|
11
|
-
assert_equal true, @matcher.include?("pat@example.org")
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_domain
|
15
|
-
assert_equal true, @matcher.include?("pat@example.com")
|
16
|
-
assert_equal false, @matcher.include?("pat@nomatch.com")
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_registration
|
20
|
-
assert_equal true, @matcher.include?("pat@hotmail.ca")
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_domain_glob
|
24
|
-
assert_equal true, @matcher.include?("pat@domain123.com")
|
25
|
-
end
|
26
|
-
|
27
|
-
def test_provider
|
28
|
-
assert_equal true, @matcher.include?("pat@gmail.com")
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_mailbox
|
32
|
-
assert_equal true, @matcher.include?("user123@example.com")
|
33
|
-
end
|
34
|
-
|
35
|
-
def test_address
|
36
|
-
assert_equal true, @matcher.include?("root@example.com")
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_cidr
|
40
|
-
# If this breaks, check its MX IP addresses against "207.99.0.0/30"
|
41
|
-
assert_equal true, @matcher.include?("test@biglist.com")
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
require_relative '../test_helper'
|
2
|
-
|
3
|
-
class EmailAddress::TestValidator < MiniTest::Test
|
4
|
-
|
5
|
-
def test_basic
|
6
|
-
assert_equal true, EmailAddress.new('user.name@gmail.com').valid?
|
7
|
-
assert_equal true, EmailAddress.new('user.name+tagme@gmail.com').valid?
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_bad_local
|
11
|
-
assert_equal false, EmailAddress.new('user!name@gmail.com').valid?
|
12
|
-
assert_equal false, EmailAddress.new('***@yahoo.com').valid?
|
13
|
-
assert_equal false, EmailAddress.new('***@unknowndom41n.com').valid?
|
14
|
-
end
|
15
|
-
|
16
|
-
end
|