validators 3.3.0 → 3.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 862ad7c2a49bcc38d917336b109047a330c6de6c6da16d83ea7953ddefe70096
4
- data.tar.gz: fb5e0b9db3ec0fdbc064eb6467cb36fab3d0f4bab6f29a5c8439c8d5edeb46fd
3
+ metadata.gz: 49077fb99da3c1c92a3b981cdfd0354acb5a62a9b433512658bfc5c1e4df649b
4
+ data.tar.gz: 9d48b1cf25f3782afeaa00c0fa7f51ecf5581ea7e6bb49ebd5796ab914aecb50
5
5
  SHA512:
6
- metadata.gz: b8842cf3ee2161ef2fe18c384efdb5ff6407c135560a95b91689d29b8ff65d87272c599288de55a6422dfaa6ff4be3b0b00fbd7ea8a8472e47bc08acfb973a85
7
- data.tar.gz: 7a6fe664d6ef7a33735b8bf41cc198f8509a5712f788a2db93d6f2c4b39678ad6965eea1557fb6e43551a4d947a0cfd2e89c16a53c0721b6ada4390baa8cf293
6
+ metadata.gz: 1b307ad09f0e26c9e1db7766a15512aab5c87eb5cbd6217e9da489a45abce98bad30c4c320d0042d46fbf92d1d2c061487736f45d723d569a04cf26fb27bc453
7
+ data.tar.gz: b72f412e65628ac2d4b1e14160e2fcc855383ff3e40641d78e77cfa99f3f9b781e82d9cc4ca269ecd45493f4b199d918102b713d7860c99bf73e88b73ab6e060
@@ -0,0 +1 @@
1
+ github: [fnando]
@@ -4,6 +4,8 @@ inherit_gem:
4
4
 
5
5
  AllCops:
6
6
  TargetRubyVersion: 2.6
7
+ Exclude:
8
+ - vendor/**/*
7
9
 
8
10
  Style/AsciiComments:
9
11
  Enabled: false
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/setup"
4
3
  require "bundler/gem_tasks"
5
4
  require "rake/testtask"
6
5
  require "rubocop/rake_task"
@@ -7,7 +7,7 @@ module Validators
7
7
  require "validators/ip"
8
8
  require "validators/tld"
9
9
  require "validators/hostname"
10
- require "validators/disposable_hostnames"
10
+ require "validators/disposable_domains"
11
11
  require "validators/disposable_emails"
12
12
  require "validators/reserved_subdomains"
13
13
 
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Validators
4
+ class DisposableDomains
5
+ def self.all
6
+ @all ||=
7
+ begin
8
+ require "email_data"
9
+ EmailData.disposable_domains
10
+ rescue LoadError
11
+ raise "email_data is not part of the bundle. Add it to Gemfile."
12
+ end
13
+ end
14
+
15
+ def self.include?(domain)
16
+ all.include?(domain)
17
+ end
18
+ end
19
+ end
@@ -2,10 +2,14 @@
2
2
 
3
3
  module Validators
4
4
  class DisposableEmails
5
- FILE_PATH = File.expand_path("../../data/disposable_emails.txt", __dir__)
6
-
7
5
  def self.all
8
- @all ||= File.read(FILE_PATH).lines.map(&:chomp)
6
+ @all ||=
7
+ begin
8
+ require "email_data"
9
+ EmailData.disposable_emails
10
+ rescue LoadError
11
+ raise "email_data is not part of the bundle. Add it to Gemfile."
12
+ end
9
13
  end
10
14
 
11
15
  def self.include?(email)
@@ -2,10 +2,14 @@
2
2
 
3
3
  module Validators
4
4
  class TLD
5
- FILE_PATH = File.expand_path("../../data/tld.txt", __dir__)
6
-
7
5
  def self.all
8
- @all ||= File.read(FILE_PATH).lines.map(&:chomp)
6
+ @all ||=
7
+ begin
8
+ require "email_data"
9
+ EmailData.tlds
10
+ rescue LoadError
11
+ raise "email_data is not part of the bundle. Add it to Gemfile."
12
+ end
9
13
  end
10
14
 
11
15
  def self.host_with_valid_tld?(host)
@@ -13,10 +17,10 @@ module Validators
13
17
 
14
18
  return false if host.split(".").size == 1
15
19
 
16
- valid?(host[/\.([^.]+)$/, 1].to_s.downcase)
20
+ include?(host[/\.([^.]+)$/, 1].to_s.downcase)
17
21
  end
18
22
 
19
- def self.valid?(tld)
23
+ def self.include?(tld)
20
24
  all.include?(tld)
21
25
  end
22
26
  end
@@ -50,7 +50,7 @@ module ActiveModel
50
50
  hostname = value.to_s.split(AT_SIGN).last.to_s.downcase
51
51
  root_domain = RootDomain.call(hostname)
52
52
 
53
- return unless Validators::DisposableHostnames.all.include?(root_domain)
53
+ return unless Validators::DisposableDomains.include?(root_domain)
54
54
 
55
55
  record.errors.add(
56
56
  attribute,
@@ -3,7 +3,7 @@
3
3
  module Validators
4
4
  module Version
5
5
  MAJOR = 3
6
- MINOR = 3
6
+ MINOR = 4
7
7
  PATCH = 0
8
8
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
9
9
  end
@@ -3,7 +3,6 @@
3
3
  $VERBOSE = nil
4
4
 
5
5
  require "simplecov"
6
- SimpleCov.start
7
6
 
8
7
  SimpleCov.start do
9
8
  add_filter "test/support"
@@ -31,7 +30,7 @@ end
31
30
 
32
31
  Time.zone = "America/Sao_Paulo"
33
32
  TLDs = Validators::TLD.all.sample(10)
34
- DISPOSABLE_DOMAINS = Validators::DisposableHostnames.all.sample(10)
33
+ DISPOSABLE_DOMAINS = Validators::DisposableDomains.all.sample(10)
35
34
  DISPOSABLE_EMAILS = Validators::DisposableEmails.all +
36
35
  Validators::DisposableEmails.all.sample(10).map {|email| build_email_with_filter(email) } +
37
36
  Validators::DisposableEmails.all.sample(10).map {|email| build_email_with_dots(email) } +
@@ -14,4 +14,22 @@ class ValidatesurlFormatUrlWithTldValidationTest < Minitest::Test
14
14
  assert user.valid?
15
15
  end
16
16
  end
17
+
18
+ test "rejects invalid TLD (alternative syntax)" do
19
+ user_model = Class.new do
20
+ include ActiveModel::Validations
21
+ attr_accessor :site_url
22
+
23
+ def self.name
24
+ "User"
25
+ end
26
+
27
+ validates :site_url, url: {tld: true}
28
+ end
29
+
30
+ user = user_model.new
31
+ user.site_url = "https://example.xy"
32
+
33
+ refute user.valid?
34
+ end
17
35
  end
@@ -47,4 +47,22 @@ class ValidatesurlFormatUrlWithoutTldValidationTest < Minitest::Test
47
47
  user = User.new(url: "")
48
48
  refute user.valid?
49
49
  end
50
+
51
+ test "accepts invalid TLD (alternative syntax)" do
52
+ user_model = Class.new do
53
+ include ActiveModel::Validations
54
+ attr_accessor :site_url
55
+
56
+ def self.name
57
+ "User"
58
+ end
59
+
60
+ validates :site_url, url: {tld: false}
61
+ end
62
+
63
+ user = user_model.new
64
+ user.site_url = "https://example.xy"
65
+
66
+ assert user.valid?
67
+ end
50
68
  end
@@ -12,6 +12,7 @@ Gem::Specification.new do |s|
12
12
  s.summary = "Add some nice ActiveModel/ActiveRecord validators."
13
13
  s.description = s.summary
14
14
  s.license = "MIT"
15
+ s.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
15
16
 
16
17
  s.files = `git ls-files`.split("\n")
17
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -23,6 +24,7 @@ Gem::Specification.new do |s|
23
24
  s.add_development_dependency "activerecord"
24
25
  s.add_development_dependency "aitch"
25
26
  s.add_development_dependency "cpf_cnpj"
27
+ s.add_development_dependency "email_data"
26
28
  s.add_development_dependency "minitest-utils"
27
29
  s.add_development_dependency "mocha"
28
30
  s.add_development_dependency "nokogiri"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validators
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nando Vieira
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-22 00:00:00.000000000 Z
11
+ date: 2020-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: email_data
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: minitest-utils
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -265,29 +279,22 @@ dependencies:
265
279
  description: Add some nice ActiveModel/ActiveRecord validators.
266
280
  email:
267
281
  - fnando.vieira@gmail.com
268
- executables:
269
- - sync-disposable-hostnames
270
- - sync-tld
282
+ executables: []
271
283
  extensions: []
272
284
  extra_rdoc_files: []
273
285
  files:
286
+ - ".github/FUNDING.yml"
274
287
  - ".gitignore"
275
288
  - ".rubocop.yml"
276
289
  - ".travis.yml"
277
290
  - Gemfile
278
291
  - README.md
279
292
  - Rakefile
280
- - bin/sync-disposable-hostnames
281
- - bin/sync-tld
282
- - data/country_tlds.txt
283
- - data/disposable_domains.txt
284
- - data/disposable_emails.txt
285
293
  - data/reserved_subdomains.txt
286
- - data/tld.txt
287
294
  - lib/validators.rb
288
295
  - lib/validators/constants.rb
296
+ - lib/validators/disposable_domains.rb
289
297
  - lib/validators/disposable_emails.rb
290
- - lib/validators/disposable_hostnames.rb
291
298
  - lib/validators/hostname.rb
292
299
  - lib/validators/ip.rb
293
300
  - lib/validators/locale/en.yml
@@ -351,7 +358,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
351
358
  requirements:
352
359
  - - ">="
353
360
  - !ruby/object:Gem::Version
354
- version: '0'
361
+ version: 2.3.0
355
362
  required_rubygems_version: !ruby/object:Gem::Requirement
356
363
  requirements:
357
364
  - - ">="
@@ -1,230 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require_relative "helpers"
5
-
6
- def ten_minute_mail
7
- path = "disposable/10minutemail.txt"
8
- url = "https://10minutemail.com/session/address"
9
-
10
- 20.times do
11
- refresh_list(url: url, path: path) do |response|
12
- _account, host = response.data.fetch("address").split("@")
13
-
14
- [host]
15
- end
16
-
17
- sleep random_timeout
18
- end
19
- end
20
-
21
- def temp_mail
22
- path = "disposable/tempmail.txt"
23
- url = "https://api4.temp-mail.org/request/domains/format/json"
24
-
25
- refresh_list(url: url, path: path) do |response|
26
- response.data.map {|domain| domain.tr("@", "") }
27
- end
28
- end
29
-
30
- def temp_mail_address
31
- path = "disposable/tempmailaddress.txt"
32
- url = "https://www.tempmailaddress.com/index/index"
33
-
34
- refresh_list(url: url, path: path) do |response|
35
- data = JSON.parse(
36
- response.body.gsub(/[^-,:\w@.{}"]/, ""),
37
- symbolize_names: true
38
- )
39
- [data[:email].split("@").last]
40
- end
41
- end
42
-
43
- def tempmail_io
44
- path = "disposable/tempmail_io.txt"
45
- url = "https://api.internal.temp-mail.io/api/v2/domains"
46
-
47
- refresh_list(url: url, path: path) do |response|
48
- response.data["domains"]
49
- end
50
- end
51
-
52
- def gmailnator
53
- emails = []
54
-
55
- 5.times do
56
- url = "https://gmailnator.com/bulk-emails"
57
- default_headers = {"user-agent" => USER_AGENT.sample}
58
-
59
- response = Aitch.get(url: url, headers: default_headers)
60
-
61
- throw "Received #{response.status} when getting CSRF token" unless response.ok?
62
-
63
- cookie_header = response.headers["set-cookie"]
64
- attr = response.data.css("#csrf_token").first
65
- csrf_token = attr[:value]
66
- csrf_field = attr[:name]
67
-
68
- response = Aitch.post(
69
- url: url,
70
- params: {email_list: "1000", email: [3], csrf_field => csrf_token},
71
- headers: default_headers.merge({"cookie" => cookie_header})
72
- )
73
-
74
- throw "Received #{response.status} when fetching list" unless response.ok?
75
-
76
- emails += response.data.css("#email-list-message a").map do |node|
77
- mailbox, domain = node.text.gsub(/\+[^@]+/, "").split("@")
78
- mailbox = mailbox.gsub(/\./m, "")
79
- "#{mailbox}@#{domain}"
80
- end
81
-
82
- sleep random_timeout
83
- end
84
-
85
- append_to_file("disposable/gmailnator.txt", emails)
86
- end
87
-
88
- def domain_scraping(name, url, selector)
89
- timeout(10) do
90
- puts "=> Scraping #{url}"
91
-
92
- selector, value_selector = selector.split("::")
93
- path = "disposable/#{name}.txt"
94
- host_regex = /@?(.*?(\.[^.]+)+)/
95
-
96
- refresh_list(url: url, path: path) do |response|
97
- new_domains = response
98
- .data
99
- .css(selector)
100
- .map {|element| process_scraping(element, value_selector) }
101
-
102
- new_domains = new_domains
103
- .map(&:squish)
104
- .reject(&:empty?)
105
- .map {|domain| domain[host_regex, 1]&.squish&.tr("@", "") }
106
- .reject(&:nil?)
107
- .reject(&:empty?)
108
- .map {|domain| domain.gsub(/\s*\((.*?)\)/, "") }
109
-
110
- raise "No #{name} hosts found" if new_domains.empty?
111
-
112
- new_domains
113
- end
114
- end
115
- rescue StandardError => error
116
- puts "=> [ERROR] Unable to scrape #{url}; #{error.class}: #{error.message}"
117
- []
118
- end
119
-
120
- def process_scraping(element, value_selector)
121
- value = nil
122
-
123
- case value_selector
124
- when "text()"
125
- value = element.text
126
- when /^attr\((.*?)\)/
127
- value = element[Regexp.last_match(1)]
128
- else
129
- element.attributes.each do |_name, attr|
130
- attr = attr.value.to_s
131
- value = attr if attr =~ host_regex
132
- end
133
- end
134
-
135
- raise "no value found: #{element} (value_selector: #{value_selector})" unless value
136
-
137
- value
138
- end
139
-
140
- def load_github_url(url)
141
- puts "=> Fetching #{url}"
142
-
143
- basename = URI.parse(url).path[%r{/([^/]+/[^/]+)}, 1].tr("/", "_").tr("-", "_")
144
- path = "disposable/#{basename}.txt"
145
- domains = load_file(path)
146
-
147
- ext = File.extname(url)
148
-
149
- domains += case ext
150
- when ".json"
151
- JSON.parse(http_request(:get, url).body)
152
- when ".txt"
153
- http_request(:get, url).body.lines.map(&:chomp)
154
- else
155
- raise "Unknown extension"
156
- end
157
-
158
- append_to_file(path, domains)
159
- domains
160
- rescue StandardError => error
161
- puts "=> Unable to load #{url}; #{error.class}: #{error.message}"
162
- []
163
- end
164
-
165
- threads = []
166
-
167
- threads << thread { load_github_url("https://raw.githubusercontent.com/ivolo/disposable-email-domains/master/index.json") }
168
- threads << thread { load_github_url("https://raw.githubusercontent.com/andreis/disposable-email-domains/master/domains.json") }
169
- threads << thread { load_github_url("https://raw.githubusercontent.com/FGRibreau/mailchecker/master/list.txt") }
170
- threads << thread { load_github_url("https://raw.githubusercontent.com/willwhite/freemail/master/data/disposable.txt") }
171
- threads << thread { load_github_url("https://raw.githubusercontent.com/maxmalysh/disposable-emails/master/disposable_emails/data/domains.txt") }
172
- threads << thread { load_github_url("https://raw.githubusercontent.com/jespernissen/disposable-maildomain-list/master/disposable-maildomain-list.txt") }
173
- threads << thread { load_github_url("https://raw.githubusercontent.com/wesbos/burner-email-providers/master/emails.txt") }
174
- threads << thread { load_github_url("https://gist.github.com/fnando/dafe542cac13f831bbf5521a55248116/raw/disposable.txt") }
175
- threads << thread { ten_minute_mail }
176
- threads << thread { temp_mail }
177
- threads << thread { temp_mail_address }
178
- threads << thread { tempmail_io }
179
- threads << thread { load_file("disposable/disposable_manually_added.txt") }
180
- threads << thread { domain_scraping("guerrillamail", "https://www.guerrillamail.com/", "select option::attr(value)") }
181
- threads << thread { domain_scraping("moakt", "https://www.moakt.com", "select option::attr(value)") }
182
- threads << thread { domain_scraping("tempr", "https://tempr.email/", "select[name=DomainId] option::text()") }
183
- threads << thread { domain_scraping("yepmail", "https://yepmail.co/", "select[name=domain] option::text()") }
184
- threads << thread { domain_scraping("fake_email_generator", "https://fakemailgenerator.net", "[data-mailhost]::attr(data-mailhost)") }
185
- threads << thread { domain_scraping("tempemails", "https://www.tempemails.net/", "select[name=domain] option::attr(value)") }
186
- threads << thread { domain_scraping("clipmails", "https://clipmails.com/", "select[name=domain] option::attr(value)") }
187
- threads << thread { domain_scraping("1secmail", "https://www.1secmail.com/", "select[id=domain] option::attr(value)") }
188
- threads << thread { domain_scraping("emailfake", "https://generator.email", ".tt-suggestion p::text()") }
189
- threads << thread { domain_scraping("emailfake", "https://emailfake.com/", ".tt-suggestion p::text()") }
190
- threads << thread { domain_scraping("emailfake", "https://email-fake.com/", ".tt-suggestion p::text()") }
191
- threads << thread { domain_scraping("receivemail", "https://www.receivemail.org/", "select[name=domain] option::text()") }
192
- threads << thread { domain_scraping("itemp", "https://itemp.email", "select[name=domain] option::text()") }
193
- threads << thread { domain_scraping("cs", "https://www.cs.email", "select[id=gm-host-select] option::text()") }
194
- threads << thread { domain_scraping("tempmail", "https://tempmail.io/settings/", "select[id=domain] option::text()") }
195
- threads << thread { domain_scraping("tempemail", "https://tempemail.co", "select[name=email_domain] option::text()") }
196
- threads << thread { domain_scraping("tmail", "https://mytemp-email.com/", "a.domain-selector::text()") }
197
-
198
- threads.each_slice(5) do |slice|
199
- slice.each(&:join)
200
- end
201
-
202
- threads.clear
203
-
204
- domains = []
205
-
206
- puts "=> Loading disposable_domains.txt"
207
- domains += File.read("#{__dir__}/../data/disposable_domains.txt").lines.map(&:chomp)
208
-
209
- puts "=> Loading disposable/*.txt"
210
- Dir["./data/disposable/**/*.txt"].map do |file|
211
- file = File.expand_path(file)
212
- domains += File.read(file).lines.map(&:chomp).flatten.compact
213
- end
214
-
215
- ignore_domains = %w[gmail.com hotmail.com]
216
-
217
- puts "=> Normalize domains (count: #{domains.size})"
218
- domains = domains
219
- .uniq
220
- .map {|domain| RootDomain.call(domain.split("@").last.downcase) }
221
- .compact
222
- .uniq
223
- .reject {|domain| ignore_domains.include?(domain) }
224
-
225
- puts "=> Saving domains (count: #{domains.size})"
226
- save_file("disposable_domains.txt", domains)
227
-
228
- emails = gmailnator
229
- puts "=> Saving email proxies (count: #{emails.size})"
230
- save_file("disposable_emails.txt", emails)