email_assessor 0.4.13 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e89762778af30f339adc30aaf6cec13e2d31ab19
4
- data.tar.gz: c955f0aa4424bb6b83070baa7aea8e3f92eee78b
2
+ SHA256:
3
+ metadata.gz: 28e8db711b1571f9f105f0e341b8798bf557edcdd21bfa9af93b97a2fc6e7989
4
+ data.tar.gz: 697a161767368eed35a1cf12eaca2fc9c129b6e27f4ab48a4bd07362597a678c
5
5
  SHA512:
6
- metadata.gz: 0e0671fd2e0aa2d3c8abacc0e11a949a71e9107445a0fa77e5377e3cbd6638217351e09eccb79d0abce202fc700bb38484b617c222d4f2e79633a93cf1979d13
7
- data.tar.gz: b25571d50af7636c25670c1758d932c52db506759c2624c5c4e3ce917dc4db20aba60cec4005f2b1957b043d9a0fc6ed363b0c4de71548533ae5d4ef0cdbb090
6
+ metadata.gz: ac12ac0944a707a72b3d7f507b016c0d4dad6cd3cd924c1f229178e8dd806cff1e2bdf4eca887566c2271c15ea4a9210f02b2f6e24e26d768c85e47e73104fdb
7
+ data.tar.gz: fd148a7343b485d42f320c0e7fcc2265a636f2973faacf3e928d5315c39e8b909c970809c6ebd07b3d929484a2239b33be3c7b2bb38ef0aeb0bd3648e2288db4
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.required_ruby_version = ">= 2.4.0"
25
25
 
26
- spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "bundler", "~> 2.0"
27
27
  spec.add_development_dependency "rake"
28
28
  spec.add_development_dependency "rspec", "~> 3.4"
29
29
  spec.add_runtime_dependency "mail", "~> 2.5"
@@ -7,6 +7,82 @@ module EmailAssessor
7
7
  class Address
8
8
  attr_accessor :address
9
9
 
10
+ PROHIBITED_DOMAIN_PREFIXES = [
11
+ '.',
12
+ '-',
13
+ ].freeze
14
+
15
+ PROHIBITED_DOMAIN_CONTENT = [
16
+ '+',
17
+ '!',
18
+ '_',
19
+ '/',
20
+ ' ',
21
+ '..',
22
+ '-.',
23
+ "'",
24
+ ].freeze
25
+
26
+ PROHIBITED_DOMAIN_SUFFIXES = [
27
+ # none
28
+ ].freeze
29
+
30
+ PROHIBITED_LOCAL_PREFIXES = [
31
+ '.',
32
+ ].freeze
33
+
34
+ PROHIBITED_LOCAL_CONTENT = [
35
+ '..',
36
+ ].freeze
37
+
38
+ PROHIBITED_LOCAL_SUFFIXES = [
39
+ '.',
40
+ ].freeze
41
+
42
+ class << self
43
+ def prohibited_domain_regex
44
+ @prohibited_domain_content_regex ||= make_regex(
45
+ prefixes: PROHIBITED_DOMAIN_PREFIXES,
46
+ content: PROHIBITED_DOMAIN_CONTENT,
47
+ suffixes: PROHIBITED_DOMAIN_SUFFIXES
48
+ )
49
+ end
50
+
51
+ def prohibited_local_regex
52
+ @prohibited_local_content_regex ||= make_regex(
53
+ prefixes: PROHIBITED_LOCAL_PREFIXES,
54
+ content: PROHIBITED_LOCAL_CONTENT,
55
+ suffixes: PROHIBITED_LOCAL_SUFFIXES
56
+ )
57
+ end
58
+
59
+ private
60
+
61
+ def make_regex(prefixes: nil, content: nil, suffixes: nil)
62
+ parts = []
63
+
64
+ unless prefixes.nil?
65
+ prefixes.each do |prefix|
66
+ parts << "\\A#{Regexp.escape(prefix)}"
67
+ end
68
+ end
69
+
70
+ unless content.nil?
71
+ content.each do |prefix|
72
+ parts << Regexp.escape(prefix)
73
+ end
74
+ end
75
+
76
+ unless suffixes.nil?
77
+ suffixes.each do |prefix|
78
+ parts << "#{Regexp.escape(prefix)}\\z"
79
+ end
80
+ end
81
+
82
+ Regexp.new(parts.join("|"), Regexp::IGNORECASE)
83
+ end
84
+ end
85
+
10
86
  def initialize(address)
11
87
  @parse_error = false
12
88
  @raw_address = address
@@ -19,18 +95,19 @@ module EmailAssessor
19
95
  end
20
96
 
21
97
  def valid?
98
+ return @valid unless @valid.nil?
22
99
  return false if @parse_error
23
100
 
24
- if address.domain && address.address == @raw_address
25
- domain = address.domain
101
+ @valid =
102
+ if address.domain && address.address == @raw_address
103
+ domain = address.domain
26
104
 
27
- domain.match?(%r{\.}) && # Valid address domain must contain a period
28
- !domain.match?(%r{\.{2,}}) && # Valid address domain cannot have consecutive periods
29
- !domain.match?(%r{\A\.}) && # Valid address domain cannot start with a period
30
- domain.match?(%r{[a-z]\z}i) # Valid address domain must end with letters
31
- else
32
- false
33
- end
105
+ domain.include?('.') &&
106
+ !domain.match?(self.class.prohibited_domain_regex) &&
107
+ !address.local.match?(self.class.prohibited_local_regex)
108
+ else
109
+ false
110
+ end
34
111
  end
35
112
 
36
113
  def disposable?
@@ -42,15 +119,24 @@ module EmailAssessor
42
119
  end
43
120
 
44
121
  def valid_mx?
45
- return false unless valid?
122
+ valid? && mx_servers.any?
123
+ end
46
124
 
47
- mx = []
125
+ def mx_server_is_in?(domain_list_file)
126
+ mx_servers.any? do |mx_server|
127
+ return false unless mx_server.respond_to?(:exchange)
128
+ mx_server = mx_server.exchange.to_s
48
129
 
49
- Resolv::DNS.open do |dns|
50
- mx.concat dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
130
+ EmailAssessor.domain_in_file?(mx_server, domain_list_file)
51
131
  end
132
+ end
52
133
 
53
- mx.any?
134
+ def mx_servers
135
+ @mx_servers ||= Resolv::DNS.open do |dns|
136
+ mx_servers = dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
137
+ (mx_servers.any? && mx_servers) ||
138
+ dns.getresources(address.domain, Resolv::DNS::Resource::IN::A)
139
+ end
54
140
  end
55
141
  end
56
142
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module EmailAssessor
3
- VERSION = "0.4.13"
3
+ VERSION = "0.7.1"
4
4
  end
@@ -13,14 +13,14 @@ module EmailAssessor
13
13
  domain_in_file?(domain, BLACKLISTED_DOMAINS_FILE_NAME)
14
14
  end
15
15
 
16
- protected
17
-
18
16
  def self.domain_in_file?(domain, file_name)
19
17
  file_name ||= ""
20
18
  domain = domain.downcase
21
19
 
22
- # Using String#end_with? here would lead to unexpected quirks and false positives.
23
- # For instance, hotmail.com is valid but tmail.com is not.
24
- File.foreach(file_name).any? { |line| domain.match?(%r{\A(?:.+\.)*?#{line.chomp}\z}i) }
20
+ File.foreach(file_name, chomp: true).any? do |line|
21
+ # String#end_with? is used as a cheaper initial check but due to potential false positives
22
+ # (hotmail.com is valid but tmail.com is not) regex is also necessary.
23
+ domain.end_with?(line) && domain.match?(%r{\A(?:.*\.)?#{line}\z}i)
24
+ end
25
25
  end
26
26
  end
@@ -5,16 +5,17 @@ require "yaml"
5
5
 
6
6
  require "json"
7
7
  require "net/http"
8
+ require "set"
8
9
 
9
10
  whitelisted_domains = %w(poczta.onet.pl fastmail.fm hushmail.com naver.com qq.com nus.edu.sg)
10
11
 
11
12
  existing_domains = File.readlines("vendor/disposable_domains.txt")
12
13
 
13
- url = "https://raw.githubusercontent.com/FGRibreau/mailchecker/master/list.json"
14
+ url = "https://raw.githubusercontent.com/FGRibreau/mailchecker/master/list.txt"
14
15
  resp = Net::HTTP.get_response(URI.parse(url))
15
16
 
16
- remote_domains = JSON.parse(resp.body).flatten - whitelisted_domains
17
+ remote_domains = (resp.body.split("\n")) - whitelisted_domains
17
18
 
18
- result_domains = (existing_domains + remote_domains).map { |domain| domain.strip.downcase }.uniq.sort
19
+ result_domains = SortedSet.new((existing_domains + remote_domains).map! { |domain| domain.strip.downcase })
19
20
 
20
- File.open("vendor/disposable_domains.txt", "w") { |f| f.write result_domains.join("\n") }
21
+ File.open("vendor/disposable_domains.txt", "w") { |f| f.write result_domains.to_a.join("\n") }
@@ -32,55 +32,107 @@ describe EmailAssessor do
32
32
  describe "basic validation" do
33
33
  subject(:user) { plain_user }
34
34
 
35
- it "should be valid when email is empty" do
35
+ it "is valid when email is empty" do
36
36
  is_expected.to be_valid
37
37
  end
38
38
 
39
- it "should not be valid when domain is missing" do
39
+ it "is invalid if the address starts with a dot" do
40
+ user = TestUser.new(email: ".foo@bar.com")
41
+ expect(user.valid?).to be_falsey
42
+ end
43
+
44
+ it "is invalid if the address contains consecutive dots" do
45
+ user = TestUser.new(email: "foo..bar@gmail.com")
46
+ expect(user.valid?).to be_falsey
47
+ end
48
+
49
+ it "is invalid if the address ends with a dot" do
50
+ user = TestUser.new(email: "foo.@bar.com")
51
+ expect(user.valid?).to be_falsey
52
+ end
53
+
54
+ it "is invalid when domain is missing" do
40
55
  user.email = "foo@.com"
41
56
  is_expected.to be_invalid
42
57
  end
43
58
 
44
- it "should be invalid when email is malformed" do
59
+ it "is invalid when domain starts with a dot" do
60
+ user.email = "foo@.example.com"
61
+ is_expected.to be_invalid
62
+ end
63
+
64
+ it "is invalid when email is malformed" do
45
65
  user.email = "foo@bar"
46
66
  is_expected.to be_invalid
47
67
  end
48
68
 
49
- it "should be invalid when email contains a trailing symbol" do
69
+ it "is invalid when email contains a trailing symbol" do
50
70
  user.email = "foo@bar.com/"
51
71
  is_expected.to be_invalid
52
72
  end
53
73
 
54
- it "should be invalid if Mail::AddressListsParser raises exception" do
74
+ it "is invalid if Mail::AddressListsParser raises exception" do
55
75
  allow(Mail::Address).to receive(:new).and_raise(Mail::Field::ParseError.new(nil, nil, nil))
56
76
  user.email = "foo@gmail.com"
57
77
  is_expected.to be_invalid
58
78
  end
59
79
 
60
- it "shouldn't be valid if the domain constains consecutives dots" do
80
+ it "is invalid if the domain constains consecutives dots" do
61
81
  user.email = "foo@bar..com"
62
82
  is_expected.to be_invalid
63
83
  end
84
+
85
+ it "is invalid if the domain contains emoticons" do
86
+ user.email = "foo🙈@gmail.com"
87
+ is_expected.to be_invalid
88
+ end
89
+
90
+ it "is invalid if the domain contains spaces" do
91
+ user.email = "user@gmail .com"
92
+ is_expected.to be_invalid
93
+ end
94
+
95
+ it "is invalid if the domain contains '.@'" do
96
+ user.email = "foo.@gmail.com"
97
+ expect(user.valid?).to be_falsy
98
+ end
99
+
100
+ it "is invalid if the domain begins with a hyphen" do
101
+ user.email = "foo@-gmail.com"
102
+ expect(user.valid?).to be_falsy
103
+ end
104
+
105
+ it "is invalid if the domain name ends with a hyphen" do
106
+ user.email = "foo@gmail-.com"
107
+ expect(user.valid?).to be_falsy
108
+ end
109
+
110
+ %w[+ _ ! / \ '].each do |invalid_character|
111
+ it "is invalid if domain contains a \"#{invalid_character}\" character" do
112
+ user.email = "foo@google#{invalid_character}yahoo.com"
113
+ is_expected.to be_invalid
114
+ end
115
+ end
64
116
  end
65
117
 
66
118
  describe "disposable domains" do
67
119
  subject(:user) { disposable_user }
68
120
 
69
- it "should be valid when email is not in the list of disposable domains" do
121
+ it "is valid when email is not in the list of disposable domains" do
70
122
  is_expected.to be_valid
71
123
  end
72
124
 
73
- it "should be invalid when email is in the list of disposable domains" do
125
+ it "is invalid when email is in the list of disposable domains" do
74
126
  user.email = "foo@#{disposable_domain}"
75
127
  is_expected.to be_invalid
76
128
  end
77
129
 
78
- it "should be invalid when email is in the list of disposable domains regardless of case" do
130
+ it "is invalid when email is in the list of disposable domains regardless of case" do
79
131
  user.email = "foo@#{disposable_domain.upcase}"
80
132
  is_expected.to be_invalid
81
133
  end
82
134
 
83
- it "should be invalid when email is in the list of disposable domains regardless of subdomain" do
135
+ it "is invalid when email is in the list of disposable domains regardless of subdomain" do
84
136
  user.email = "foo@abc123.#{disposable_domain}"
85
137
  is_expected.to be_invalid
86
138
  end
@@ -89,21 +141,21 @@ describe EmailAssessor do
89
141
  describe "blacklisted domains" do
90
142
  subject(:user) { blacklist_user }
91
143
 
92
- it "should be valid when email domain is not in the blacklist" do
144
+ it "is valid when email domain is not in the blacklist" do
93
145
  is_expected.to be_valid
94
146
  end
95
147
 
96
- it "should be invalid when email domain is in the blacklist" do
148
+ it "is invalid when email domain is in the blacklist" do
97
149
  user.email = "foo@#{blacklisted_domain}"
98
150
  is_expected.to be_invalid
99
151
  end
100
152
 
101
- it "should be invalid when email is in the blacklist regardless of case" do
153
+ it "is invalid when email is in the blacklist regardless of case" do
102
154
  user.email = "foo@#{blacklisted_domain.upcase}"
103
155
  is_expected.to be_invalid
104
156
  end
105
157
 
106
- it "should be invalid when email domain is in the blacklist regardless of subdomain" do
158
+ it "is invalid when email domain is in the blacklist regardless of subdomain" do
107
159
  user.email = "foo@abc123.#{blacklisted_domain}"
108
160
  is_expected.to be_invalid
109
161
  end
@@ -112,11 +164,11 @@ describe EmailAssessor do
112
164
  describe "mx lookup" do
113
165
  subject(:user) { mx_user }
114
166
 
115
- it "should be valid if mx records are found" do
167
+ it "is valid if mx records are found" do
116
168
  is_expected.to be_valid
117
169
  end
118
170
 
119
- it "should be invalid if no mx records are found" do
171
+ it "is invalid if no mx records are found" do
120
172
  user.email = "foo@subdomain.gmail.com"
121
173
  is_expected.to be_invalid
122
174
  end