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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/README.md +99 -32
  4. data/email_address.gemspec +2 -1
  5. data/lib/email_address.rb +14 -142
  6. data/lib/email_address/address.rb +70 -0
  7. data/lib/email_address/config.rb +75 -0
  8. data/lib/email_address/domain_matcher.rb +90 -0
  9. data/lib/email_address/domain_parser.rb +71 -0
  10. data/lib/email_address/exchanger.rb +67 -0
  11. data/lib/email_address/host.rb +71 -1
  12. data/lib/email_address/local.rb +97 -0
  13. data/lib/email_address/validator.rb +135 -0
  14. data/lib/email_address/version.rb +1 -1
  15. data/test/email_address/test_address.rb +30 -0
  16. data/test/email_address/test_config.rb +13 -0
  17. data/test/email_address/test_domain_matcher.rb +15 -0
  18. data/test/email_address/test_domain_parser.rb +29 -0
  19. data/test/email_address/test_exchanger.rb +19 -0
  20. data/test/email_address/test_host.rb +43 -0
  21. data/test/email_address/test_local.rb +27 -0
  22. data/test/email_address/test_validator.rb +16 -0
  23. data/test/test_email_address.rb +12 -0
  24. metadata +40 -27
  25. data/lib/email_address/esp.rb +0 -4
  26. data/lib/email_address/providers/default.rb +0 -8
  27. data/lib/email_address/providers/google.rb +0 -8
  28. data/lib/email_providers/address.rb +0 -102
  29. data/lib/email_providers/config.rb +0 -36
  30. data/lib/email_providers/factory.rb +0 -17
  31. data/lib/email_providers/host.rb +0 -87
  32. data/lib/email_providers/mail_exchanger.rb +0 -60
  33. data/lib/email_providers/mailbox.rb +0 -44
  34. data/lib/email_providers/providers/default.rb +0 -55
  35. data/lib/email_providers/providers/google.rb +0 -27
  36. data/lib/email_providers/version.rb +0 -3
  37. data/test/email_address.rb +0 -52
  38. data/test/email_address/address.rb +0 -16
  39. data/test/email_address/config.rb +0 -13
  40. data/test/email_address/host.rb +0 -29
  41. data/test/email_address/mail_exchanger.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 225554c1b1abf4ead2900a7209b55ec7babeaefe
4
- data.tar.gz: 4de853f82e80be5e9e6ce843a16fd2dc3f4a3255
3
+ metadata.gz: da957eceefc70a7dc4a21a1d1f44296e36783021
4
+ data.tar.gz: fffc1230d4c3080ab56229e34604838543b659e6
5
5
  SHA512:
6
- metadata.gz: 11b73a88096b1553dac4aa7979a1a7385c568ae8d5c2dca268c88bf1fd2827aa77bb76e3a412a6cc85d1f5d9983f470a1a6b1840df73d428fbfdae76277bfe9a
7
- data.tar.gz: 97b3f3497d00346404b94dbfde915bbbcf1dae41bcbc6d7fc98a4e0dc0072245b57597ca4939295e6db47fc2700ed935add5f8019519389950288f2501ad7bc6
6
+ metadata.gz: e638d9efbba9834cd1857150db91821d82e6bbbbf95ee694bc30287d5c9f69893d87928ee49b044207e9503accf4eefc354295b29e3d5ec44ad6ee5ab2918653
7
+ data.tar.gz: 90749c6bdbc84c2586208eefb04f8eb208812124d3253170d46471b64df4e23c5ff24bc6967c426285e8cd838f71bcd1fb3b910a7a82690ab3a91da5665e9e40
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in email_address.gemspec
4
4
  gemspec
5
+ gem 'minitest'
data/README.md CHANGED
@@ -1,25 +1,31 @@
1
- # EmailAddress
2
-
3
- The EmailAddress library is an _opinionated_ email address handler and
4
- validator.
5
-
6
- So you have an email address input by a user. Do you want to validate
7
- it, check it for uniqueness, or mine statistics on all your addresses?
8
- Then the email_address gem is for you!
9
-
10
- Opininated? Yes. By default, this does not support RFC822 specification
11
- because it allows addresses that should never exist for real people.
12
- By limiting email addresses to a subset of standardly used ones, you can
13
- remove false positives in validation, and help the email community
14
- evolve into a friendlier place. I like standards as much as the next
15
- person, but enough is enough!
16
-
17
- Why my opinion? I've been working with email and such since the late
18
- 1990's. I would like to see modern practices applied to email addresses.
19
- These rules really do apply to most (without real statistics, I'll claim
20
- 99.9+%) usage in the 21st Century. They may not be for everyone, you may
21
- need strict adherence to these standards for historical reasons, but I
22
- bet you'll wish you could support this one instead!
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("user@example.com")
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
- This runs the following checks you can do yourself:
117
+ ## Customizing
50
118
 
51
- email.valid_format?
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
- Of course, the last couple tests can't be published, so you can provide
58
- a callback to check them yourself if you need.
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
 
@@ -21,5 +21,6 @@ validator.}
21
21
 
22
22
  spec.add_development_dependency "bundler", "~> 1.3"
23
23
  spec.add_development_dependency "rake"
24
- spec.add_development_dependency "simpleidn"
24
+ spec.add_dependency "simpleidn"
25
+ spec.add_dependency "netaddr"
25
26
  end
data/lib/email_address.rb CHANGED
@@ -1,145 +1,17 @@
1
- #require "email_address/version"
2
- #require "email_address/config"
3
- #require "email_address/host"
4
- #require "email_address/address"
5
- require 'simpleidn'
6
-
7
- class EmailAddress
8
- attr_reader :address, :local, :account, :tag, :comment,
9
- :domain, :subdomains, :domain_name, :base_domain, :top_level_domain
10
-
11
- def initialize(email)
12
- self.address = email
13
- end
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
@@ -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