email_address 0.0.3 → 0.1.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 +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
|