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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da957eceefc70a7dc4a21a1d1f44296e36783021
|
4
|
+
data.tar.gz: fffc1230d4c3080ab56229e34604838543b659e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e638d9efbba9834cd1857150db91821d82e6bbbbf95ee694bc30287d5c9f69893d87928ee49b044207e9503accf4eefc354295b29e3d5ec44ad6ee5ab2918653
|
7
|
+
data.tar.gz: 90749c6bdbc84c2586208eefb04f8eb208812124d3253170d46471b64df4e23c5ff24bc6967c426285e8cd838f71bcd1fb3b910a7a82690ab3a91da5665e9e40
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,25 +1,31 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
The EmailAddress
|
4
|
-
validator.
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
1
|
+
# Email Address
|
2
|
+
|
3
|
+
The EmailAddress gem is an _opinionated_ email address handler and
|
4
|
+
validator. It does not use RFC standards because they make things worse.
|
5
|
+
Email addresses should conform to a few practices that are not
|
6
|
+
RFC-Compliant.
|
7
|
+
|
8
|
+
Specifically, local parts (left side of the @):
|
9
|
+
|
10
|
+
* Should not be case sensitive.
|
11
|
+
* Should not contain spaces or anything that would cause quoting.
|
12
|
+
* Should not allow Unicode. Addressable items like this need to be
|
13
|
+
entered from any keyboard, such as the US ASCII character set. (Domain
|
14
|
+
names too, but that can be handled with Punycode.)
|
15
|
+
* Should not have comments. Neither should domains.
|
16
|
+
* Should not allow unusual symbols (not usually in names and standard
|
17
|
+
punctuation).
|
18
|
+
* Should not be verified by SMTP connections if possible.
|
19
|
+
* Should have spaces stripped automatically if enabled
|
20
|
+
* Should be of a reasonable length to identify the recipient.
|
21
|
+
* Should be human readable and writable.
|
22
|
+
* Should continue allowing for tagging.
|
23
|
+
* Should provide mechanism for handling bounce backs and VERP.
|
24
|
+
* Should be easily normalized and corrected.
|
25
|
+
* Should be canonicalized to identify duplicates if necessary.
|
26
|
+
* Should be able to be stored as a digest for privacy proctections.
|
27
|
+
|
28
|
+
If you're on board, let's go!
|
23
29
|
|
24
30
|
## Installation
|
25
31
|
|
@@ -40,23 +46,84 @@ Or install it yourself as:
|
|
40
46
|
Inspect your email address string by creating an instance of
|
41
47
|
EmailAddress:
|
42
48
|
|
43
|
-
email = EmailAddress.new("
|
49
|
+
email = EmailAddress.new("USER+tag@EXAMPLE.com")
|
50
|
+
email.normalize #=> "user+tag@example.com"
|
51
|
+
email.canonical #=> "user@example.com"
|
52
|
+
|
53
|
+
Email Service Provider (ESP) specific edits can be created to provide
|
54
|
+
validations and canonical manipulations. A few are given out of the box.
|
55
|
+
Providers can be defined bu email domain match rules, or by match rules
|
56
|
+
for the MX host names using domains or CIDR addresses.
|
57
|
+
|
58
|
+
email = EmailAddress.new("First.Last+Tag@Gmail.Com")
|
59
|
+
email.provider #=> :gmail
|
60
|
+
email.canonical #=> "firstlast@gmail.com"
|
61
|
+
|
62
|
+
Storing the canonical address with the request address (don't remove
|
63
|
+
tags given by users), you can lookup email addresses without the
|
64
|
+
original formatting, case, and tag information.
|
65
|
+
|
66
|
+
You can inspect the MX (Mail Exchanger) records
|
67
|
+
|
68
|
+
email.host.exchanger.mxers.first
|
69
|
+
#=> {:host=>"alt3.gmail-smtp-in.l.google.com", :ip=>"173.194.70.27", :priority=>30}
|
44
70
|
|
45
71
|
You can see if it validates as an opinionated address:
|
46
72
|
|
47
|
-
email.valid?
|
73
|
+
email.valid? # Resonably valid?
|
74
|
+
email.valid_host? # Host name is defined in DNS
|
75
|
+
email.strict? # Strictly valid?
|
76
|
+
|
77
|
+
Email addresses should be able to be "archived" or stored in a digest
|
78
|
+
format. This allows you to safely keep a record of the address and still
|
79
|
+
protect the account's privacy after it has been closed. Given an address
|
80
|
+
for inquiry, it can still look up a closed account.
|
81
|
+
|
82
|
+
email.md5 #=> "dea073fb289e438a6d69c5384113454c"
|
83
|
+
email.archive #=> "554d32017ab3a7fcf51c88ffce078689003bc521@gmail.com"
|
84
|
+
|
85
|
+
|
86
|
+
## Email Address Parts
|
87
|
+
|
88
|
+
The Local and Domain Parsing routines divvy the email address into these
|
89
|
+
parts from `(comment)mailbox+tag@subdomain.domain.tld`
|
90
|
+
|
91
|
+
* Local - Everything to the left of the "@"
|
92
|
+
* Mailbox - The controlling mailbox name
|
93
|
+
* Tag - Anything after the mailbox and tag separator character (usually "+")
|
94
|
+
* Comment - To be removed from the normalized form
|
95
|
+
* Host Name - Everything to the right of the "@"
|
96
|
+
* Subdomains - of the host name, if any.
|
97
|
+
* Domain Name - host name without subdomains, with TLD
|
98
|
+
* TLD - the rightmost word or set of 2-character domains ("co.uk")
|
99
|
+
* Registration Name - host name without subdomain or TLD
|
100
|
+
|
101
|
+
## Domain Matching
|
102
|
+
|
103
|
+
You can also employ domain matching rules
|
104
|
+
|
105
|
+
email.host.matches?('gmail.com', '.us', '.msn.com', 'yahoo')
|
106
|
+
|
107
|
+
This tests the address can be matched in the given list of domain rules:
|
108
|
+
|
109
|
+
* Full host name. (subdomain.example.com)
|
110
|
+
* TLD and domain wildcards (.us, .msg.com)
|
111
|
+
* Registration names matching without the TLD. 'yahoo' matches:
|
112
|
+
* "www.yahoo.com" (with Subdomains)
|
113
|
+
* "yahoo.ca" (any TLD)
|
114
|
+
* "yahoo.co.jp" (2-char TLD with 2-char Second-level)
|
115
|
+
* But _may_ also match non-Yahoo domain names (yahoo.xxx)
|
48
116
|
|
49
|
-
|
117
|
+
## Customizing
|
50
118
|
|
51
|
-
|
52
|
-
email.valid_domain?
|
53
|
-
email.valid_user?
|
54
|
-
email.disposable_email?
|
55
|
-
email.spam_trap?
|
119
|
+
You can change configuration options and add new providers such as:
|
56
120
|
|
57
|
-
|
58
|
-
|
121
|
+
EmailAddress::Config.setup do
|
122
|
+
provider :github, domains:%w(github.com github.io)
|
123
|
+
option :check_dns, false
|
124
|
+
end
|
59
125
|
|
126
|
+
See `lib/email_address/config.rb` for more options.
|
60
127
|
|
61
128
|
## Contributing
|
62
129
|
|
data/email_address.gemspec
CHANGED
data/lib/email_address.rb
CHANGED
@@ -1,145 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
require
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
##############################################################################
|
16
|
-
# Basic email address: local@domain
|
17
|
-
# Only supporting FQDN's (Fully Qualified Domain Names)?
|
18
|
-
# Length: Up to 254 characters
|
19
|
-
##############################################################################
|
20
|
-
def address=(email)
|
21
|
-
@address = email.strip
|
22
|
-
(local_name, domain_name) = @address.split('@')
|
23
|
-
self.domain = domain_name
|
24
|
-
self.local = local_name
|
25
|
-
@address
|
26
|
-
end
|
27
|
-
|
28
|
-
def to_s
|
29
|
-
[local, domain].join('@')
|
30
|
-
end
|
31
|
-
|
32
|
-
##############################################################################
|
33
|
-
# Domain Parsing
|
34
|
-
# Parts: subdomains.basedomain.top-level-domain
|
35
|
-
# IPv6/IPv6: [128.0.0.1], [IPv6:2001:db8:1ff::a0b:dbd0]
|
36
|
-
# Comments: (comment)example.com, example.com(comment)
|
37
|
-
# Internationalized: Unicode to Punycode
|
38
|
-
# Length: up to 255 characters
|
39
|
-
##############################################################################
|
40
|
-
def domain=(host_name)
|
41
|
-
host_name ||= ''
|
42
|
-
@domain = host_name.strip.downcase
|
43
|
-
parse_domain
|
44
|
-
@domain
|
45
|
-
end
|
46
|
-
|
47
|
-
def parse_domain
|
48
|
-
@subdomains = @base_domain = @domain_name = @top_level_domain = ''
|
49
|
-
# Patterns: *.com, *.xx.cc, *.cc
|
50
|
-
if @domain =~ /\A(.+)\.(\w{3,10})\z/ || @domain =~ /\A(.+)\.(\w{1,3}\.\w\w)\z/ || @domain =~ /\A(.+)\.(\w\w)\z/
|
51
|
-
@top_level_domain = $2;
|
52
|
-
sld = $1 # Second level domain
|
53
|
-
if sld =~ /\A(.+)\.(.+)\z/ # is subdomain? sub.example [.tld]
|
54
|
-
@subdomains = $1
|
55
|
-
@base_domain = $2
|
56
|
-
else
|
57
|
-
@subdomains = ""
|
58
|
-
@base_domain = sld
|
59
|
-
end
|
60
|
-
@domain_name = @base_domain + '.' + @top_level_domain
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def dns_hostname
|
65
|
-
@dns_hostname ||= SimpleIDN.to_ascii(domain)
|
66
|
-
end
|
67
|
-
|
68
|
-
##############################################################################
|
69
|
-
# Parsing id provider-dependent, but RFC allows:
|
70
|
-
# A-Z a-z 0-9 . ! # $ % ' * + - / = ? ^ _ { | } ~
|
71
|
-
# Quoted: space ( ) , : ; < > @ [ ]
|
72
|
-
# Quoted-Backslash-Escaped: \ "
|
73
|
-
# Quote local part or dot-separated sub-parts x."y".z
|
74
|
-
# (comment)mailbox | mailbox(comment)
|
75
|
-
# 8-bit/UTF-8: allowed but mail-system defined
|
76
|
-
# RFC 5321 also warns that "a host that expects to receive mail SHOULD avoid defining mailboxes where the Local-part requires (or uses) the Quoted-string form".
|
77
|
-
# Postmaster: must always be case-insensitive
|
78
|
-
# Case: sensitive, but usually treated as equivalent
|
79
|
-
# Local Parts: comment, account tag
|
80
|
-
# Length: upt o 64 cgaracters
|
81
|
-
##############################################################################
|
82
|
-
def local=(local)
|
83
|
-
local ||= ''
|
84
|
-
@local = local.strip.downcase
|
85
|
-
@account = parse_comment(@local)
|
86
|
-
(@account, @tag) = @account.split(tag_separator)
|
87
|
-
@tag ||= ''
|
88
|
-
|
89
|
-
@local
|
90
|
-
end
|
91
|
-
|
92
|
-
def parse_comment(local)
|
93
|
-
if local =~ /\A\((.+?)\)(.+)\z/
|
94
|
-
(@comment, local) = [$1, $2]
|
95
|
-
elsif @local =~ /\A(.+)\((.+?)\)\z/
|
96
|
-
(@comment, local) = [$1, $2]
|
97
|
-
else
|
98
|
-
@comment = '';
|
99
|
-
end
|
100
|
-
local
|
101
|
-
end
|
102
|
-
|
103
|
-
##############################################################################
|
104
|
-
# Provider-Specific Settings
|
105
|
-
##############################################################################
|
106
|
-
|
107
|
-
def provider
|
108
|
-
# @provider ||= EmailProviders::Default.new
|
109
|
-
'unknown'
|
110
|
-
end
|
111
|
-
|
112
|
-
def tag_separator
|
113
|
-
'+'
|
114
|
-
end
|
115
|
-
|
116
|
-
def case_sensitive_local
|
117
|
-
false
|
118
|
-
end
|
119
|
-
|
120
|
-
# Returns the unique address as simplified account@hostname
|
121
|
-
def unique_address
|
122
|
-
"#{account}@#{dns_hostname}".downcase
|
123
|
-
end
|
124
|
-
|
125
|
-
# Letters, numbers, period (no start) 6-30chars
|
126
|
-
def user_pattern
|
127
|
-
/\A[a-z0-9][\.a-z0-9]{0,29}\z/i
|
128
|
-
end
|
129
|
-
|
130
|
-
##############################################################################
|
131
|
-
# Validations -- Eventually a provider-sepecific check
|
132
|
-
##############################################################################
|
133
|
-
def valid?
|
134
|
-
return false unless @local =~ user_pattern
|
135
|
-
return false unless provider # .valid_domain
|
136
|
-
true
|
137
|
-
end
|
138
|
-
|
139
|
-
def valid_format?
|
140
|
-
return false unless @local.match(user_pattern)
|
141
|
-
return false unless @host.valid_format?
|
142
|
-
true
|
1
|
+
require "email_address/address"
|
2
|
+
require "email_address/config"
|
3
|
+
require "email_address/domain_matcher"
|
4
|
+
require "email_address/domain_parser"
|
5
|
+
require "email_address/exchanger"
|
6
|
+
require "email_address/host"
|
7
|
+
require "email_address/local"
|
8
|
+
require "email_address/validator"
|
9
|
+
require "email_address/version"
|
10
|
+
|
11
|
+
module EmailAddress
|
12
|
+
|
13
|
+
def self.new(address)
|
14
|
+
EmailAddress::Address.new(address)
|
143
15
|
end
|
144
16
|
|
145
17
|
end
|
@@ -1,4 +1,74 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
1
4
|
module EmailAddress
|
2
5
|
class Address
|
6
|
+
|
7
|
+
def initialize(address)
|
8
|
+
@address = address
|
9
|
+
parse
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse
|
13
|
+
(_, local, host) = @address.match(/\A(.+)@(.+)/).to_a
|
14
|
+
@host = EmailAddress::Host.new(host)
|
15
|
+
@local = EmailAddress::Local.new(local, @host.provider)
|
16
|
+
end
|
17
|
+
|
18
|
+
def host
|
19
|
+
@host
|
20
|
+
end
|
21
|
+
|
22
|
+
def local
|
23
|
+
@local
|
24
|
+
end
|
25
|
+
|
26
|
+
def mailbox
|
27
|
+
@local.mailbox
|
28
|
+
end
|
29
|
+
|
30
|
+
def host_name
|
31
|
+
@host.host_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def tag
|
35
|
+
@local.tag
|
36
|
+
end
|
37
|
+
|
38
|
+
def comment
|
39
|
+
@local.comment
|
40
|
+
end
|
41
|
+
|
42
|
+
def provider
|
43
|
+
@host.provider
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
normalize
|
48
|
+
end
|
49
|
+
|
50
|
+
def normalize
|
51
|
+
[@local.normalize, @host.normalize].join('@')
|
52
|
+
end
|
53
|
+
|
54
|
+
def canonical
|
55
|
+
[@local.canonical, @host.canonical].join('@')
|
56
|
+
end
|
57
|
+
|
58
|
+
def md5
|
59
|
+
Digest::MD5.hexdigest(canonical)
|
60
|
+
end
|
61
|
+
|
62
|
+
def sha1
|
63
|
+
Digest::SHA1.hexdigest(canonical)
|
64
|
+
end
|
65
|
+
|
66
|
+
def archive
|
67
|
+
[sha1, @host.canonical].join('@')
|
68
|
+
end
|
69
|
+
|
70
|
+
def valid?(options={})
|
71
|
+
EmailAddress::Validator.validate(self, options)
|
72
|
+
end
|
3
73
|
end
|
4
74
|
end
|
data/lib/email_address/config.rb
CHANGED
@@ -1,4 +1,79 @@
|
|
1
1
|
module EmailAddress
|
2
2
|
class Config
|
3
|
+
@options = {
|
4
|
+
downcase_mailboxes: true,
|
5
|
+
check_dns: true,
|
6
|
+
}
|
7
|
+
|
8
|
+
@providers = {
|
9
|
+
default: {
|
10
|
+
domains: [],
|
11
|
+
exchangers: [],
|
12
|
+
tag_separator: '+',
|
13
|
+
case_sensitive: false,
|
14
|
+
address_size: 3..254,
|
15
|
+
local_size: 1..64,
|
16
|
+
domain_size: 1..253,
|
17
|
+
mailbox_size: 1..64,
|
18
|
+
mailbox_unicode: false,
|
19
|
+
canonical_mailbox: ->(m) {m},
|
20
|
+
valid_mailbox: nil, # :legible, :rfc, ->(m) {true}
|
21
|
+
},
|
22
|
+
aol: {
|
23
|
+
registration_names: %w(aol compuserve netscape aim cs)
|
24
|
+
},
|
25
|
+
google: {
|
26
|
+
domains: %w(gmail.com googlemail.com),
|
27
|
+
exchangers: %w(google.com),
|
28
|
+
local_size: 5..64,
|
29
|
+
canonical_mailbox: ->(m) {m.gsub('.','')},
|
30
|
+
#valid_mailbox: ->(local) { local.mailbox =~ /\A[a-z0-9][\.a-z0-9]{5,29}\z/i},
|
31
|
+
},
|
32
|
+
msn: {
|
33
|
+
valid_mailbox: ->(m) { m =~ /\A[a-z0-9][\.\-a-z0-9]{5,29}\z/i},
|
34
|
+
},
|
35
|
+
yahoo: {
|
36
|
+
domains: %w(yahoo ymail rocketmail),
|
37
|
+
exchangers: %w(yahoodns yahoo-inc),
|
38
|
+
},
|
39
|
+
}
|
40
|
+
|
41
|
+
def self.providers
|
42
|
+
@providers
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.options
|
46
|
+
@options
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.provider(name)
|
50
|
+
@providers[:default].merge(@providers.fetch(name) { Hash.new })
|
51
|
+
end
|
52
|
+
|
53
|
+
class Setup
|
54
|
+
attr_reader :providers
|
55
|
+
|
56
|
+
def initialize
|
57
|
+
@providers = {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def do_block(&block)
|
61
|
+
instance_eval(&block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def provider(name, defn={})
|
65
|
+
EmailAddress::Config.providers[name] = defn
|
66
|
+
end
|
67
|
+
|
68
|
+
def option(name, value)
|
69
|
+
EmailAddress::Config.options[name.to_sym] = value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.setup(&block)
|
74
|
+
@setup ||= Setup.new
|
75
|
+
@setup.do_block(&block) if block_given?
|
76
|
+
@setup
|
77
|
+
end
|
3
78
|
end
|
4
79
|
end
|