validators 3.1.1 → 3.2.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.
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
+ ]