validators 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -0
  3. data/README.md +20 -5
  4. data/bin/sync-disposable-hostnames +208 -23
  5. data/bin/sync-tld +11 -8
  6. data/data/country_tlds.json +237 -0
  7. data/data/disposable.json +42 -60317
  8. data/data/disposable/10minutemail.json +3 -0
  9. data/data/disposable/1secmail.json +5 -0
  10. data/data/disposable/FGRibreau_mailchecker.json +33602 -0
  11. data/data/disposable/andreis_disposable_email_domains.json +48320 -0
  12. data/data/disposable/clipmails.json +27 -0
  13. data/data/disposable/cs.json +15 -0
  14. data/data/disposable/emailfake.json +128 -0
  15. data/data/disposable/fake_email_generator.json +11 -0
  16. data/data/disposable/fnando_dafe542cac13f831bbf5521a55248116.json +20 -0
  17. data/data/disposable/gmailnator.json +3 -0
  18. data/data/disposable/guerrillamail.json +13 -0
  19. data/data/disposable/ically.json +28 -0
  20. data/data/disposable/itemp.json +4 -0
  21. data/data/disposable/ivolo_disposable_email_domains.json +48177 -0
  22. data/data/disposable/jespernissen_disposable_maildomain_list.json +2372 -0
  23. data/data/disposable/maxmalysh_disposable_emails.json +21372 -0
  24. data/data/disposable/moakt.json +12 -0
  25. data/data/disposable/receivemail.json +9 -0
  26. data/data/disposable/sneakykiwi_LeagueCreatorPublic.json +2364 -0
  27. data/data/disposable/tempemail.json +7 -0
  28. data/data/disposable/tempemails.json +10 -0
  29. data/data/disposable/tempmail.json +31 -0
  30. data/data/disposable/tempmail_io.json +8 -0
  31. data/data/disposable/tempmailaddress.json +4 -0
  32. data/data/disposable/tempomail.json +3 -0
  33. data/data/disposable/tempr.json +97 -0
  34. data/data/disposable/tmail.json +3 -0
  35. data/data/disposable/wesbos_burner_email_providers.json +4711 -0
  36. data/data/disposable/willwhite_freemail.json +352 -0
  37. data/data/disposable/yepmail.json +12 -0
  38. data/data/disposable_emails.json +3 -0
  39. data/data/disposable_patterns.json +3 -0
  40. data/data/disposable_raw.json +128 -0
  41. data/data/{reserved_hostnames.json → reserved_subdomains.json} +2 -0
  42. data/data/sld.json +5564 -0
  43. data/data/tld.json +2 -0
  44. data/lib/validators.rb +3 -3
  45. data/lib/validators/locale/en.yml +3 -1
  46. data/lib/validators/{reserved_hostnames.rb → reserved_subdomains.rb} +2 -2
  47. data/lib/validators/validates_subdomain.rb +67 -0
  48. data/lib/validators/validates_username.rb +15 -0
  49. data/lib/validators/version.rb +2 -2
  50. data/test/validators/validates_subdomain_test.rb +63 -0
  51. data/test/validators/{validates_reserved_username_test.rb → validates_username_test.rb} +27 -4
  52. data/validators.gemspec +6 -1
  53. metadata +87 -10
  54. data/lib/validators/validates_reserved_hostname.rb +0 -45
  55. data/lib/validators/validates_reserved_username.rb +0 -29
  56. data/test/validators/validates_reserved_hostname_test.rb +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25a774489dab7d08065e4fd32aae5540c7539ad2089096074e0e9bddfdb6d409
4
- data.tar.gz: bf68b6e29ab10f72a01db549003ae545f09ca03b9b6e727d85bd99f773edacd2
3
+ metadata.gz: a9866f3a757a7bc59c1ea77da0ad9bd8f6c375f7caad128c0abcecfb596ed56a
4
+ data.tar.gz: d4e52e82630413c929e5c007ae31dd7667c128ec8201850546d8ba331148b5ed
5
5
  SHA512:
6
- metadata.gz: b01421cbb3d76ddfc08327c4c86ab9d1af981f4f32c7648146c715978029345c09ef7428a6e9917e4eae2605ca12121f9aa5930a83a5ddaaf5eaeb8204c62455
7
- data.tar.gz: e1f6d616018639bec217b77c3ceb94a0fa444eac20b46833f00c5f809d53c5c1c0d8a45bbf690c14ad7790cc460364e901e4434010b524f65bd29fc55844a5f1
6
+ metadata.gz: 11b91772a301abd780957d7697f75bc41da64a43ef6a55d2a38a298522571f9117b9cec1ac1403b0cf9e56e825e3cd1dc7dac3471ca77ab6823790f2c0d96c84
7
+ data.tar.gz: 2cafe7d2fc93d7ca8e26d0317857b959ca9070f6240f5b74e27e9c8b4706b9f727d7a18ebf3a610579d20ee7d617637b1896686609f23e332c2914c0a4378320
data/.rubocop.yml CHANGED
@@ -28,3 +28,8 @@ Style/IfUnlessModifier:
28
28
 
29
29
  Metrics/MethodLength:
30
30
  Enabled: false
31
+
32
+ Metrics/BlockLength:
33
+ Exclude:
34
+ - bin/**/*
35
+ - "*.gemspec"
data/README.md CHANGED
@@ -144,17 +144,24 @@ class Server < ActiveRecord::Base
144
144
  end
145
145
  ```
146
146
 
147
- ### validates_reserved_username / validates_reserved_hostname
147
+ ### validates_username / validates_subdomain
148
148
 
149
- The compiled list will be used for both username and hostname validations.
149
+ A valid username/subdomain follows the hostname label validation:
150
+
151
+ - maximum length is 63 characters
152
+ - allowed characters are a-z, A-Z, 0-9 and hyphen
153
+ - cannot begin or end with a hyphen
154
+ - cannot consist of numeric values only
155
+
156
+ The compiled list will be used for both username and subdomain validations.
150
157
 
151
158
  ```ruby
152
159
  class Server < ActiveRecord::Base
153
- validates_reserved_hostname :hostname
160
+ validates_subdomain :subdomain
154
161
  end
155
162
 
156
163
  class User < ActiveRecord::Base
157
- validates_reserved_username :username
164
+ validates_username :username
158
165
  end
159
166
  ```
160
167
 
@@ -168,7 +175,15 @@ ReservedUsernames = Validators::ReservedHostnames.parse_list([
168
175
  ])
169
176
 
170
177
  class User < ActiveRecord::Base
171
- validates_reserved_username, in: ReservedUsernames
178
+ validates_username :username, in: ReservedUsernames
179
+ end
180
+ ```
181
+
182
+ To disable the reserved validation, use `reserved: false`.
183
+
184
+ ```ruby
185
+ class User < ActiveRecord::Base
186
+ validates_username :username, reserved: false
172
187
  end
173
188
  ```
174
189
 
@@ -1,35 +1,220 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "open-uri"
5
- require "json"
4
+ require_relative "helpers"
6
5
 
7
- urls = %w[
8
- https://raw.githubusercontent.com/ivolo/disposable-email-domains/master/index.json
9
- https://raw.githubusercontent.com/andreis/disposable-email-domains/master/domains.json
10
- https://raw.githubusercontent.com/FGRibreau/mailchecker/master/list.txt
11
- https://raw.githubusercontent.com/willwhite/freemail/master/data/disposable.txt
12
- ]
6
+ def ten_minute_mail
7
+ path = "disposable/10minutemail.json"
8
+ url = "https://10minutemail.com/10MinuteMail/resources/session/address"
9
+
10
+ refresh_list(url: url, path: path) do |response|
11
+ _account, host = response.body.split("@")
12
+
13
+ [host]
14
+ end
15
+ end
16
+
17
+ def temp_mail
18
+ path = "disposable/tempmail.json"
19
+ url = "https://api4.temp-mail.org/request/domains/format/json"
20
+
21
+ refresh_list(url: url, path: path) do |response|
22
+ response.data.map {|domain| domain.tr("@", "") }
23
+ end
24
+ end
25
+
26
+ def temp_mail_address
27
+ path = "disposable/tempmailaddress.json"
28
+ url = "https://www.tempmailaddress.com/index/index"
29
+
30
+ refresh_list(url: url, path: path) do |response|
31
+ data = JSON.parse(
32
+ response.body.gsub(/[^-,:\w@.{}"]/, ""),
33
+ symbolize_names: true
34
+ )
35
+ [data[:email].split("@").last]
36
+ end
37
+ end
38
+
39
+ def tempmail_io
40
+ path = "disposable/tempmail_io.json"
41
+ url = "https://api.internal.temp-mail.io/api/v2/domains"
42
+
43
+ refresh_list(url: url, path: path) do |response|
44
+ response.data["domains"]
45
+ end
46
+ end
47
+
48
+ def gmailnator
49
+ path = "disposable/gmailnator.json"
50
+ url = "https://gmailnator.com/index/indexquery"
51
+
52
+ refresh_list(
53
+ verb: :post,
54
+ url: url,
55
+ path: path,
56
+ params: {action: "GenerateEmail"}
57
+ ) do |response|
58
+ email = response.body.gsub(/(\+[^@]+)/, "")
59
+ [email]
60
+ end
61
+ end
62
+
63
+ def prepare_patterns(domains)
64
+ new_domains = domains.map do |domain|
65
+ {
66
+ domain: domain,
67
+ processed: domain.gsub(/\..*?$/, "")
68
+ }
69
+ end
70
+
71
+ stats = new_domains
72
+ .count_by {|info| info[:processed] }
73
+ .select {|_processed, count| count > 2 }
74
+
75
+ stats_map = stats.each_with_object({}) do |(name, count), buffer|
76
+ buffer[name] = count
77
+ end
78
+
79
+ domains = new_domains
80
+ .reject {|info| stats_map[info[:processed]] }
81
+ .map {|info| info[:domain] }
82
+ domains += stats_map.keys.map {|processed| "/#{processed}\\..+$/" }
83
+
84
+ domains.select {|domain| domain.start_with?("/") }
85
+ end
86
+
87
+ def domain_scraping(name, url, selector)
88
+ puts "=> scraping #{url}"
89
+
90
+ selector, value_selector = selector.split("::")
91
+ path = "disposable/#{name}.json"
92
+ host_regex = /@?(.*?(\.[^.]+)+)/
93
+
94
+ refresh_list(url: url, path: path) do |response|
95
+ new_domains = response
96
+ .data
97
+ .css(selector)
98
+ .map {|element| process_scraping(element, value_selector) }
99
+
100
+ new_domains = new_domains
101
+ .map(&:squeeze)
102
+ .map(&:strip)
103
+ .reject(&:empty?)
104
+ .map {|domain| domain[host_regex, 1]&.squeeze&.tr("@", "") }
105
+ .reject(&:nil?)
106
+ .reject(&:empty?)
107
+ .map {|domain| domain.gsub(/\s*\((.*?)\)/, "") }
108
+
109
+ raise "No #{name} hosts found" if new_domains.empty?
110
+
111
+ new_domains
112
+ end
113
+ rescue StandardError => error
114
+ puts "=> [ERROR] Unable to scrape #{url}; #{error.class}: #{error.message}"
115
+ []
116
+ end
117
+
118
+ def process_scraping(element, value_selector)
119
+ value = nil
120
+
121
+ case value_selector
122
+ when "text()"
123
+ value = element.text
124
+ when /^attr\((.*?)\)/
125
+ value = element[Regexp.last_match(1)]
126
+ else
127
+ element.attributes.each do |_name, attr|
128
+ attr = attr.value.to_s
129
+ value = attr if attr =~ host_regex
130
+ end
131
+ end
132
+
133
+ raise "no value found: #{element} (value_selector: #{value_selector})" unless value
134
+
135
+ value
136
+ end
137
+
138
+ def load_github_url(url)
139
+ puts "=> Fetching #{url}"
140
+
141
+ basename = URI.parse(url).path[%r{/([^/]+/[^/]+)}, 1].tr("/", "_").tr("-", "_")
142
+ path = "disposable/#{basename}.json"
143
+ domains = load_file(path)
13
144
 
14
- domains = urls.each_with_object([]) do |url, buffer|
15
145
  ext = File.extname(url)
16
146
 
17
- result = case ext
18
- when ".json"
19
- JSON.parse(URI.open(url).read)
20
- when ".txt"
21
- URI.open(url).read.lines.map(&:chomp)
22
- else
23
- raise "Unknown extension"
24
- end
147
+ domains += case ext
148
+ when ".json"
149
+ JSON.parse(http_request(:get, url).body)
150
+ when ".txt"
151
+ http_request(:get, url).body.lines.map(&:chomp)
152
+ else
153
+ raise "Unknown extension"
154
+ end
25
155
 
26
- buffer.push(*result)
156
+ append_to_file(path, domains)
157
+ domains
158
+ rescue error
159
+ puts "=> Unable to load #{url}; #{error.class}: #{error.message}"
160
+ []
27
161
  end
28
162
 
29
- domains.map!(&:downcase)
30
- domains.uniq!
31
- domains.sort!
163
+ domains = []
164
+ threads = []
32
165
 
33
- File.open("./data/disposable.json", "w") do |file|
34
- file << JSON.pretty_generate(domains)
166
+ threads << thread { domains += load_github_url("https://raw.githubusercontent.com/ivolo/disposable-email-domains/master/index.json") }
167
+ threads << thread { domains += load_github_url("https://raw.githubusercontent.com/andreis/disposable-email-domains/master/domains.json") }
168
+ threads << thread { domains += load_github_url("https://raw.githubusercontent.com/FGRibreau/mailchecker/master/list.txt") }
169
+ threads << thread { domains += load_github_url("https://raw.githubusercontent.com/willwhite/freemail/master/data/disposable.txt") }
170
+ threads << thread { domains += load_github_url("https://raw.githubusercontent.com/maxmalysh/disposable-emails/master/disposable_emails/data/domains.txt") }
171
+ threads << thread { domains += load_github_url("https://raw.githubusercontent.com/sneakykiwi/LeagueCreatorPublic/master/emails.txt") }
172
+ threads << thread { domains += load_github_url("https://raw.githubusercontent.com/jespernissen/disposable-maildomain-list/master/disposable-maildomain-list.txt") }
173
+ threads << thread { domains += load_github_url("https://raw.githubusercontent.com/wesbos/burner-email-providers/master/emails.txt") }
174
+ threads << thread { domains += load_github_url("https://gist.github.com/fnando/dafe542cac13f831bbf5521a55248116/raw/f364d880cee5c878531e4f48be1744ff6ec84cb8/disposable.txt") }
175
+ threads << thread { domains += ten_minute_mail }
176
+ threads << thread { domains += temp_mail }
177
+ threads << thread { domains += temp_mail_address }
178
+ threads << thread { domains += tempmail_io }
179
+ threads << thread { domains += load_file("disposable/disposable_manually_added.json") }
180
+ threads << thread { domains += domain_scraping("guerrillamail", "https://www.guerrillamail.com/", "select option::attr(value)") }
181
+ threads << thread { domains += domain_scraping("moakt", "https://www.moakt.com", "select option::attr(value)") }
182
+ threads << thread { domains += domain_scraping("tempr", "https://tempr.email/", "select[name=DomainId] option::text()") }
183
+ threads << thread { domains += domain_scraping("ically", "https://ically.net/", "select[name=domain] option::text()") }
184
+ threads << thread { domains += domain_scraping("yepmail", "https://yepmail.co/", "select[name=domain] option::text()") }
185
+ threads << thread { domains += domain_scraping("fake_email_generator", "https://fakemailgenerator.net", "[data-mailhost]::attr(data-mailhost)") }
186
+ threads << thread { domains += domain_scraping("tempemails", "https://www.tempemails.net/", "select[name=domain] option::attr(value)") }
187
+ threads << thread { domains += domain_scraping("clipmails", "https://clipmails.com/", "select[name=domain] option::attr(value)") }
188
+ threads << thread { domains += domain_scraping("1secmail", "https://www.1secmail.com/", "select[id=domain] option::attr(value)") }
189
+ threads << thread { domains += domain_scraping("emailfake", "https://generator.email", ".tt-suggestion p::text()") }
190
+ threads << thread { domains += domain_scraping("emailfake", "https://emailfake.com/", ".tt-suggestion p::text()") }
191
+ threads << thread { domains += domain_scraping("emailfake", "https://email-fake.com/", ".tt-suggestion p::text()") }
192
+ threads << thread { domains += domain_scraping("receivemail", "https://www.receivemail.org/", "select[name=domain] option::text()") }
193
+ threads << thread { domains += domain_scraping("itemp", "https://itemp.email", "select[name=domain] option::text()") }
194
+ threads << thread { domains += domain_scraping("tempomail", "https://tempomail.org", "select[name=domain] option::text()") }
195
+ threads << thread { domains += domain_scraping("tempmail", "https://tempmail.top", "select[name=domain] option::text()") }
196
+ threads << thread { domains += domain_scraping("cs", "https://www.cs.email", "select[id=gm-host-select] option::text()") }
197
+ threads << thread { domains += domain_scraping("tempmail", "https://tempmail.io/settings/", "select[id=domain] option::text()") }
198
+ threads << thread { domains += domain_scraping("tempemail", "https://tempemail.co", "select[name=email_domain] option::text()") }
199
+ threads << thread { domains += domain_scraping("tmail", "https://mytemp-email.com/", "a.domain-selector::text()") }
200
+
201
+ threads.each_slice(5) do |slice|
202
+ slice.each(&:join)
35
203
  end
204
+
205
+ threads.clear
206
+
207
+ # Unprocessed domains
208
+ save_file("disposable_raw.json", domains)
209
+
210
+ domains.map!(&:downcase)
211
+ domains = root_domains(domains)
212
+
213
+ # All disposable domains, unfiltered
214
+ save_file("disposable.json", domains)
215
+
216
+ # All disposable domains, with patterns
217
+ save_file("disposable_patterns.json", prepare_patterns(domains))
218
+
219
+ # Emails being used as proxy.
220
+ save_file("disposable_emails.json", gmailnator)
data/bin/sync-tld CHANGED
@@ -1,17 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "open-uri"
5
- require "json"
4
+ require_relative "helpers"
6
5
 
7
- tlds = URI.open("https://data.iana.org/TLD/tlds-alpha-by-domain.txt").read.lines
6
+ tlds = http_request(:get, "https://data.iana.org/TLD/tlds-alpha-by-domain.txt").body.lines
8
7
  tlds.shift # remove update notice
9
8
 
10
9
  tlds.map!(&:downcase)
11
10
  tlds.map!(&:strip)
12
- tlds.sort!
13
- tlds.uniq!
11
+ tlds.map! {|tld| SimpleIDN.to_ascii(tld) }
14
12
 
15
- File.open("./data/tld.json", "w") do |file|
16
- file << JSON.pretty_generate(tlds)
17
- end
13
+ save_file("tld.json", tlds)
14
+
15
+ country_tlds = JSON.parse(http_request(:get, "https://github.com/samayo/country-json/raw/master/src/country-by-domain-tld.json").body, symbolize_names: true)
16
+ country_tlds = country_tlds
17
+ .reject {|info| info[:tld].nil? }
18
+ .map {|info| info[:tld].gsub(/^\./, "") }
19
+
20
+ save_file("country_tlds.json", country_tlds)
@@ -0,0 +1,237 @@
1
+ [
2
+ "ad",
3
+ "ae",
4
+ "af",
5
+ "ag",
6
+ "ai",
7
+ "al",
8
+ "am",
9
+ "an",
10
+ "ao",
11
+ "aq",
12
+ "ar",
13
+ "as",
14
+ "at",
15
+ "au",
16
+ "aw",
17
+ "az",
18
+ "ba",
19
+ "bb",
20
+ "bd",
21
+ "be",
22
+ "bf",
23
+ "bg",
24
+ "bh",
25
+ "bi",
26
+ "bj",
27
+ "bm",
28
+ "bn",
29
+ "bo",
30
+ "br",
31
+ "bs",
32
+ "bt",
33
+ "bv",
34
+ "bw",
35
+ "by",
36
+ "bz",
37
+ "ca",
38
+ "cc",
39
+ "cd",
40
+ "cf",
41
+ "cg",
42
+ "ch",
43
+ "ci",
44
+ "ck",
45
+ "cl",
46
+ "cm",
47
+ "cn",
48
+ "co",
49
+ "cr",
50
+ "cu",
51
+ "cv",
52
+ "cx",
53
+ "cy",
54
+ "cz",
55
+ "de",
56
+ "dj",
57
+ "dk",
58
+ "dm",
59
+ "do",
60
+ "dz",
61
+ "ec",
62
+ "ee",
63
+ "eg",
64
+ "eh",
65
+ "er",
66
+ "es",
67
+ "et",
68
+ "fi",
69
+ "fj",
70
+ "fk",
71
+ "fr",
72
+ "ga",
73
+ "gb",
74
+ "gd",
75
+ "ge",
76
+ "gf",
77
+ "gh",
78
+ "gi",
79
+ "gl",
80
+ "gm",
81
+ "gn",
82
+ "gp",
83
+ "gq",
84
+ "gr",
85
+ "gs",
86
+ "gt",
87
+ "gu",
88
+ "gw",
89
+ "gy",
90
+ "hk",
91
+ "hm",
92
+ "hn",
93
+ "hr",
94
+ "ht",
95
+ "hu",
96
+ "id",
97
+ "ie",
98
+ "il",
99
+ "in",
100
+ "io",
101
+ "iq",
102
+ "ir",
103
+ "is",
104
+ "it",
105
+ "jm",
106
+ "jo",
107
+ "jp",
108
+ "ke",
109
+ "kg",
110
+ "kh",
111
+ "ki",
112
+ "km",
113
+ "kn",
114
+ "kp",
115
+ "kr",
116
+ "kw",
117
+ "ky",
118
+ "kz",
119
+ "la",
120
+ "lb",
121
+ "lc",
122
+ "li",
123
+ "lk",
124
+ "lr",
125
+ "ls",
126
+ "lt",
127
+ "lu",
128
+ "lv",
129
+ "ly",
130
+ "ma",
131
+ "mc",
132
+ "md",
133
+ "mg",
134
+ "mh",
135
+ "mk",
136
+ "ml",
137
+ "mm",
138
+ "mn",
139
+ "mo",
140
+ "mp",
141
+ "mq",
142
+ "mr",
143
+ "ms",
144
+ "mt",
145
+ "mu",
146
+ "mv",
147
+ "mw",
148
+ "mx",
149
+ "my",
150
+ "mz",
151
+ "na",
152
+ "nc",
153
+ "ne",
154
+ "nf",
155
+ "ng",
156
+ "ni",
157
+ "nl",
158
+ "no",
159
+ "np",
160
+ "nr",
161
+ "nu",
162
+ "nz",
163
+ "om",
164
+ "pa",
165
+ "pe",
166
+ "pf",
167
+ "pg",
168
+ "ph",
169
+ "pk",
170
+ "pl",
171
+ "pm",
172
+ "pn",
173
+ "pr",
174
+ "ps",
175
+ "pt",
176
+ "pw",
177
+ "py",
178
+ "qa",
179
+ "re",
180
+ "ro",
181
+ "ru",
182
+ "rw",
183
+ "sa",
184
+ "sb",
185
+ "sc",
186
+ "sd",
187
+ "se",
188
+ "sg",
189
+ "sh",
190
+ "si",
191
+ "sj",
192
+ "sk",
193
+ "sl",
194
+ "sm",
195
+ "sn",
196
+ "so",
197
+ "sr",
198
+ "ss",
199
+ "st",
200
+ "sv",
201
+ "sy",
202
+ "sz",
203
+ "tc",
204
+ "td",
205
+ "tf",
206
+ "tg",
207
+ "th",
208
+ "tj",
209
+ "tk",
210
+ "tl",
211
+ "tm",
212
+ "tn",
213
+ "to",
214
+ "tr",
215
+ "tt",
216
+ "tv",
217
+ "tz",
218
+ "ua",
219
+ "ug",
220
+ "us",
221
+ "uy",
222
+ "uz",
223
+ "va",
224
+ "vc",
225
+ "ve",
226
+ "vg",
227
+ "vi",
228
+ "vn",
229
+ "vu",
230
+ "wf",
231
+ "ws",
232
+ "ye",
233
+ "yt",
234
+ "za",
235
+ "zm",
236
+ "zw"
237
+ ]