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
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
|