email_assessor 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9f9f5a08d4182ed68bdc232439f615339e0f5158
4
+ data.tar.gz: 76309795d2dad14078647902e21191fc90af9934
5
+ SHA512:
6
+ metadata.gz: 01529449a629cec7b20880b109574d9cc90d4ea1d8a3bd82908a09a82d66a7c5fbf98510246167ab9eab1cdb40e869873b276e78ea41606386b92a2bf8af87b5
7
+ data.tar.gz: 683e9ac09cd34379d909558672886f044d5277bda2573e9c0278b1233993731195d555278072b54d914e5f64ac64cf4d003c3613272116da511d573b20d220ac
@@ -0,0 +1,18 @@
1
+ .DS_STORE
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+
3
+ sudo: false
4
+
5
+ rvm:
6
+ - 2.2.0
7
+ - 2.1.0
8
+ - 2.0.0
9
+ - 1.9.3
10
+ - jruby-19mode
11
+
12
+ gemfile:
13
+ - gemfiles/activemodel3.gemfile
14
+ - gemfiles/activemodel4.gemfile
@@ -0,0 +1,2 @@
1
+ ## Version 0.1
2
+ Gave this fork its very own name: EmailAssessor
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in email_assessor.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright for portions of this project are held by Micke Lisinge, 2013, as part of valid_email2. All other copyright are held by Michael Wolfe Millard, 2016.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,108 @@
1
+ # ValidEmail2
2
+ [![Build Status](https://travis-ci.org/lisinge/email_assessor.png?branch=master)](https://travis-ci.org/lisinge/email_assessor)
3
+ [![Gem Version](https://badge.fury.io/rb/email_assessor.png)](http://badge.fury.io/rb/email_assessor)
4
+
5
+ A fork of of the wonderful [ValidEmail2](https://github.com/lisinge/valid_email2) by [Micke Lisinge](https://github.com/lisinge).
6
+
7
+ ValidEmail2:
8
+
9
+ * Validates emails with the help of the `mail` gem instead of some clunky regexp.
10
+ * Aditionally validates that the domain has a MX record.
11
+ * Optionally validates against a static [list of disposable email services](vendor/disposable_domains.txt).
12
+
13
+
14
+ ### Why?
15
+
16
+ ValidEmail2 offers very comprehensive email validation, but it has a few pitfalls.
17
+
18
+ For starters, it loads the entire list of blacklisted/disposable email domains into memory from a YAML file. In a never ending battle against spam, loading such an extremely large (and ever-growing) array into memory is far from ideal. Instead, EmailAssessor reads a text file line-by-line.
19
+
20
+ Another pitfall is that subdomains are able to bypass the disposable and blacklist checks in ValidEmail2. EmailAssessor checks if a given domain *ends* with a blacklisted/disposable domain, preventing subdomains from masking an email that would otherwise be considered invalid.
21
+
22
+
23
+ ## Installation
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ ```ruby
28
+ gem "email_assessor"
29
+ ```
30
+
31
+ And then execute:
32
+
33
+ $ bundle
34
+
35
+ Or install it yourself as:
36
+
37
+ $ gem install email_assessor
38
+
39
+ ## Usage
40
+
41
+ ### Use with ActiveModel
42
+
43
+ If you just want to validate that it is a valid email address:
44
+ ```ruby
45
+ class User < ActiveRecord::Base
46
+ validates :email, presence: true, email: true
47
+ end
48
+ ```
49
+
50
+ To validate that the domain has a MX record:
51
+ ```ruby
52
+ validates :email, email: { mx: true }
53
+ ```
54
+
55
+ To validate that the domain is not a disposable email:
56
+ ```ruby
57
+ validates :email, email: { disposable: true }
58
+ ```
59
+
60
+ To validate that the domain is not blacklisted (under vendor/blacklisted_domains.txt):
61
+ ```ruby
62
+ validates :email, email: { blacklist: true }
63
+ ```
64
+
65
+ All together:
66
+ ```ruby
67
+ validates :email, email: { mx: true, disposable: true }
68
+ ```
69
+
70
+ > Note that this gem will let an empty email pass through so you will need to
71
+ > add `presence: true` if you require an email
72
+
73
+ ### Use without ActiveModel
74
+
75
+ ```ruby
76
+ address = EmailAssessor::Address.new("lisinge@gmail.com")
77
+ address.valid? => true
78
+ address.disposable? => false
79
+ address.valid_mx? => true
80
+ ```
81
+
82
+ ### Test environment
83
+
84
+ If you are validating `mx` then your specs will fail without an internet connection.
85
+ It is a good idea to stub out that validation in your test environment.
86
+ Do so by adding this in your `spec_helper`:
87
+ ```ruby
88
+ config.before(:each) do
89
+ allow_any_instance_of(EmailAssessor::Address).to receive(:valid_mx?) { true }
90
+ end
91
+ ```
92
+
93
+ ## Requirements
94
+
95
+ This gem requires Rails 3.2 or 4.0. It is tested against both versions using:
96
+ * Ruby-1.9
97
+ * Ruby-2.0
98
+ * Ruby-2.1
99
+ * Ruby-2.2
100
+ * JRuby-1.9
101
+
102
+ ## Contributing
103
+
104
+ 1. Fork it
105
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
106
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
107
+ 4. Push to the branch (`git push origin my-new-feature`)
108
+ 5. Create new Pull Request
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "Run specs"
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.pattern = "spec/**/*_spec.rb"
7
+ end
8
+
9
+ task :default => [:spec]
10
+ task :build => [:spec]
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "email_assessor/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "email_assessor"
8
+ spec.version = EmailAssessor::VERSION
9
+ spec.summary = "Advanced ActiveModel email validation"
10
+ spec.description = "ActiveModel email validation with MX lookups, domain blacklisting and disposable email-domain blocking"
11
+
12
+ spec.license = "MIT"
13
+
14
+ spec.author = "Michael Wolfe Millard"
15
+ spec.email = "wolfemm.development@gmail.com"
16
+ spec.homepage = "https://github.com/wolfemm/email_assessor"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.required_ruby_version = ">= 1.9.3"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec", "~> 2.14.1"
28
+ spec.add_runtime_dependency "mail", "~> 2.5"
29
+ spec.add_runtime_dependency "activemodel", ">= 3.2"
30
+ end
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activemodel", "~> 3.2.13"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activemodel", "~> 4.2.0"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,32 @@
1
+ require "email_assessor/email_validator"
2
+
3
+ module EmailAssessor
4
+ DISPOSABLE_DOMAINS_FILE = File.expand_path("../../vendor/disposable_domains.txt", __FILE__)
5
+ BLACKLISTED_DOMAINS_FILE = File.expand_path("../../vendor/blacklisted_domains.txt", __FILE__)
6
+
7
+ def self.domain_is_disposable?(domain)
8
+ domain_in_file?(domain, DISPOSABLE_DOMAINS_FILE)
9
+ end
10
+
11
+ def self.domain_is_blacklisted?(domain)
12
+ domain_in_file?(domain, BLACKLISTED_DOMAINS_FILE)
13
+ end
14
+
15
+ protected
16
+
17
+ def self.domain_in_file?(domain, filename)
18
+ return false unless File.exists?(filename)
19
+
20
+ domain = domain.downcase
21
+ domain_matched = false
22
+
23
+ File.open(filename).each do |line|
24
+ if domain.end_with?(line.chomp)
25
+ domain_matched = true
26
+ break
27
+ end
28
+ end
29
+
30
+ domain_matched
31
+ end
32
+ end
@@ -0,0 +1,52 @@
1
+ require "email_assessor"
2
+ require "resolv"
3
+ require "mail"
4
+
5
+ module EmailAssessor
6
+ class Address
7
+ attr_accessor :address
8
+
9
+ def initialize(address)
10
+ @parse_error = false
11
+ @raw_address = address
12
+
13
+ begin
14
+ @address = Mail::Address.new(address)
15
+ rescue Mail::Field::ParseError
16
+ @parse_error = true
17
+ end
18
+ end
19
+
20
+ def valid?
21
+ return false if @parse_error
22
+
23
+ if address.domain && address.address == @raw_address
24
+ domain = address.domain
25
+ # Valid address needs to have a dot in the domain
26
+ !!domain.match(/\./) && !domain.match(/\.{2,}/) && domain.match(/[a-z]\Z/i)
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ def disposable?
33
+ valid? && EmailAssessor.domain_is_disposable?(address.domain)
34
+ end
35
+
36
+ def blacklisted?
37
+ valid? && EmailAssessor.domain_is_blacklisted?(address.domain)
38
+ end
39
+
40
+ def valid_mx?
41
+ return false unless valid?
42
+
43
+ mx = []
44
+
45
+ Resolv::DNS.open do |dns|
46
+ mx.concat dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
47
+ end
48
+
49
+ mx.any?
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,34 @@
1
+ require "email_assessor/address"
2
+ require "active_model"
3
+ require "active_model/validations"
4
+
5
+ class EmailValidator < ActiveModel::EachValidator
6
+ def default_options
7
+ { regex: true, disposable: false, mx: false }
8
+ end
9
+
10
+ def validate_each(record, attribute, value)
11
+ return unless value.present?
12
+ options = default_options.merge(self.options)
13
+
14
+ address = EmailAssessor::Address.new(value)
15
+
16
+ error(record, attribute) && return unless address.valid?
17
+
18
+ if options[:disposable]
19
+ error(record, attribute) && return if address.disposable?
20
+ end
21
+
22
+ if options[:blacklist]
23
+ error(record, attribute) && return if address.blacklisted?
24
+ end
25
+
26
+ if options[:mx]
27
+ error(record, attribute) && return unless address.valid_mx?
28
+ end
29
+ end
30
+
31
+ def error(record, attribute)
32
+ record.errors.add(attribute, options[:message] || :invalid)
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module EmailAssessor
2
+ VERSION = "0.1"
3
+ end
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ require "json"
6
+ require "net/http"
7
+
8
+ whitelisted_domains = %w(poczta.onet.pl fastmail.fm hushmail.com naver.com)
9
+
10
+ existing_domains = File.readlines("vendor/disposable_domains.txt")
11
+
12
+ url = "https://raw.githubusercontent.com/FGRibreau/mailchecker/master/list.json"
13
+ resp = Net::HTTP.get_response(URI.parse(url))
14
+
15
+ remote_domains = JSON.parse(resp.body).flatten - whitelisted_domains
16
+
17
+ puts "New domains found: #{(remote_domains - existing_domains).join(', ')}"
18
+
19
+ result_domains = (existing_domains + remote_domains).map { |domain| domain.strip.downcase }.uniq.sort
20
+
21
+ File.open("vendor/disposable_domains.txt", "w") { |f| f.write result_domains.join("\n") }
@@ -0,0 +1,102 @@
1
+ require "spec_helper"
2
+
3
+ class TestUser < TestModel
4
+ validates :email, email: true
5
+ end
6
+
7
+ class TestUserMX < TestModel
8
+ validates :email, email: { mx: true }
9
+ end
10
+
11
+ class TestUserDisallowDisposable < TestModel
12
+ validates :email, email: { disposable: true }
13
+ end
14
+
15
+ class TestUserDisallowBlacklisted < TestModel
16
+ validates :email, email: { blacklist: true }
17
+ end
18
+
19
+ describe EmailAssessor do
20
+ describe "basic validation" do
21
+ subject(:user) { TestUser.new(email: "") }
22
+
23
+ it "should be valid when email is empty" do
24
+ user.valid?.should be_true
25
+ end
26
+
27
+ it "should not be valid when domain is missing" do
28
+ user = TestUser.new(email: "foo")
29
+ user.valid?.should be_false
30
+ end
31
+
32
+ it "should be invalid when email is malformed" do
33
+ user = TestUser.new(email: "foo@bar")
34
+ user.valid?.should be_false
35
+ end
36
+
37
+ it "should be invalid when email contains a trailing symbol" do
38
+ user = TestUser.new(email: "foo@bar.com/")
39
+ user.valid?.should be_false
40
+ end
41
+
42
+ it "should be invalid if Mail::AddressListsParser raises exception" do
43
+ user = TestUser.new(email: "foo@gmail.com")
44
+ Mail::Address.stub(:new).and_raise(Mail::Field::ParseError.new(nil, nil, nil))
45
+ user.valid?.should be_false
46
+ end
47
+
48
+ it "shouldn't be valid if the domain constains consecutives dots" do
49
+ user = TestUser.new(email: "foo@bar..com")
50
+ user.valid?.should be_false
51
+ end
52
+ end
53
+
54
+ describe "disposable domains" do
55
+ let(:disposable_domain) { disposable_domain = File.open(described_class::DISPOSABLE_DOMAINS_FILE, &:readline) }
56
+
57
+ it "should be valid when email is not in the list of disposable domains" do
58
+ user = TestUserDisallowDisposable.new(email: "foo@gmail.com")
59
+ user.valid?.should be_true
60
+ end
61
+
62
+ it "should be invalid when email is in the list of disposable domains" do
63
+ user = TestUserDisallowDisposable.new(email: "foo@#{disposable_domain}")
64
+ user.valid?.should be_false
65
+ end
66
+
67
+ it "should be invalid when email is in the list of disposable domains regardless of subdomain" do
68
+ user = TestUserDisallowDisposable.new(email: "foo@abc123.#{disposable_domain}")
69
+ user.valid?.should be_false
70
+ end
71
+ end
72
+
73
+ describe "blacklisted domains" do
74
+ let(:blacklisted_domain) { File.open(described_class::BLACKLISTED_DOMAINS_FILE, &:readline) }
75
+ it "should be valid when email domain is not in the blacklist" do
76
+ user = TestUserDisallowBlacklisted.new(email: "foo@gmail.com")
77
+ user.valid?.should be_true
78
+ end
79
+
80
+ it "should be invalid when email domain is in the blacklist" do
81
+ user = TestUserDisallowBlacklisted.new(email: "foo@#{blacklisted_domain}")
82
+ user.valid?.should be_false
83
+ end
84
+
85
+ it "should be invalid when email domain is in the blacklist regardless of subdomain" do
86
+ user = TestUserDisallowBlacklisted.new(email: "foo@abc123.#{blacklisted_domain}")
87
+ user.valid?.should be_false
88
+ end
89
+ end
90
+
91
+ describe "mx lookup" do
92
+ it "should be valid if mx records are found" do
93
+ user = TestUserMX.new(email: "foo@gmail.com")
94
+ user.valid?.should be_true
95
+ end
96
+
97
+ it "should be invalid if no mx records are found" do
98
+ user = TestUserMX.new(email: "foo@subdomain.gmail.com")
99
+ user.valid?.should be_false
100
+ end
101
+ end
102
+ end