validates_hostname 1.0.13 → 2.0.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.
@@ -1,5 +1,5 @@
1
- module PAK
2
- module ValidatesHostname
3
- VERSION = '1.0.13'
4
- end
1
+ # frozen_string_literal: true
2
+
3
+ module ValidatesHostname
4
+ VERSION = '2.0.1'
5
5
  end
@@ -1,305 +1,236 @@
1
- require 'active_support/concern'
2
- require 'active_record'
3
- require 'active_model'
1
+ # frozen_string_literal: true
4
2
 
5
- module PAK
6
- module ValidatesHostname
7
- autoload :VERSION, 'validates_hostname/version'
3
+ require 'active_model'
4
+ require 'set'
5
+ require_relative 'validates_hostname/version'
8
6
 
7
+ module ValidatesHostname
8
+ # Handles TLD list loading and management.
9
+ module Tld
9
10
  # List from IANA: http://www.iana.org/domains/root/db/
10
11
  # http://data.iana.org/TLD/tlds-alpha-by-domain.txt
11
- ALLOWED_TLDS = %w(
12
- . aaa aarp abarth abb abbott abbvie abc able abogado abudhabi ac academy
13
- accenture accountant accountants aco actor ad adac ads adult ae aeg aero
14
- aetna af afamilycompany afl africa ag agakhan agency ai aig aigo airbus
15
- airforce airtel akdn al alfaromeo alibaba alipay allfinanz allstate ally
16
- alsace alstom am americanexpress americanfamily amex amfam amica amsterdam
17
- analytics android anquan anz ao aol apartments app apple aq aquarelle ar
18
- arab aramco archi army arpa art arte as asda asia associates at athleta
19
- attorney au auction audi audible audio auspost author auto autos avianca aw
20
- aws ax axa az azure ba baby baidu banamex bananarepublic band bank bar
21
- barcelona barclaycard barclays barefoot bargains baseball basketball bauhaus
22
- bayern bb bbc bbt bbva bcg bcn bd be beats beauty beer bentley berlin best
23
- bestbuy bet bf bg bh bharti bi bible bid bike bing bingo bio biz bj black
24
- blackfriday blockbuster blog bloomberg blue bm bms bmw bn bnpparibas bo
25
- boats boehringer bofa bom bond boo book booking bosch bostik boston bot
26
- boutique box br bradesco bridgestone broadway broker brother brussels bs bt
27
- budapest bugatti build builders business buy buzz bv bw by bz bzh ca cab
28
- cafe cal call calvinklein cam camera camp cancerresearch canon capetown
29
- capital capitalone car caravan cards care career careers cars casa case
30
- caseih cash casino cat catering catholic cba cbn cbre cbs cc cd ceb center
31
- ceo cern cf cfa cfd cg ch chanel channel charity chase chat cheap chintai
32
- christmas chrome church ci cipriani circle cisco citadel citi citic city
33
- cityeats ck cl claims cleaning click clinic clinique clothing cloud club
34
- clubmed cm cn co coach codes coffee college cologne com comcast commbank
35
- community company compare computer comsec condos construction consulting
36
- contact contractors cooking cookingchannel cool coop corsica country coupon
37
- coupons courses cpa cr credit creditcard creditunion cricket crown crs
38
- cruise cruises csc cu cuisinella cv cw cx cy cymru cyou cz dabur dad dance
39
- data date dating datsun day dclk dds de deal dealer deals degree delivery
40
- dell deloitte delta democrat dental dentist desi design dev dhl diamonds
41
- diet digital direct directory discount discover dish diy dj dk dm dnp do
42
- docs doctor dog domains dot download drive dtv dubai duck dunlop dupont
43
- durban dvag dvr dz earth eat ec eco edeka edu education ee eg email emerck
44
- energy engineer engineering enterprises epson equipment er ericsson erni
45
- es esq estate esurance et etisalat eu eurovision eus events exchange
46
- expert exposed express extraspace fage fail fairwinds faith family fan
47
- fans farm farmers fashion fast fedex feedback ferrari ferrero fi fiat
48
- fidelity fido film final finance financial fire firestone firmdale fish
49
- fishing fit fitness fj fk flickr flights flir florist flowers fly fm fo
50
- foo food foodnetwork football ford forex forsale forum foundation fox fr
51
- free fresenius frl frogans frontdoor frontier ftr fujitsu fujixerox fun
52
- fund furniture futbol fyi ga gal gallery gallo gallup game games gap
53
- garden gay gb gbiz gd gdn ge gea gent genting george gf gg ggee gh gi gift
54
- gifts gives giving gl glade glass gle global globo gm gmail gmbh gmo gmx
55
- gn godaddy gold goldpoint golf goo goodyear goog google gop got gov gp gq
56
- gr grainger graphics gratis green gripe grocery group gs gt gu guardian
57
- gucci guge guide guitars guru gw gy hair hamburg hangout haus hbo hdfc
58
- hdfcbank health healthcare help helsinki here hermes hgtv hiphop hisamitsu
59
- hitachi hiv hk hkt hm hn hockey holdings holiday homedepot homegoods homes
60
- homesense honda horse hospital host hosting hot hoteles hotels hotmail
61
- house how hr hsbc ht hu hughes hyatt hyundai ibm icbc ice icu id ie ieee
62
- ifm ikano il im imamat imdb immo immobilien in inc industries infiniti
63
- info ing ink institute insurance insure int intel international intuit
64
- investments io ipiranga iq ir irish is ismaili ist istanbul it itau itv
65
- iveco jaguar java jcb jcp je jeep jetzt jewelry jio jll jm jmp jnj jo jobs
66
- joburg jot joy jp jpmorgan jprs juegos juniper kaufen kddi ke kerryhotels
67
- kerrylogistics kerryproperties kfh kg kh ki kia kim kinder kindle kitchen
68
- kiwi km kn koeln komatsu kosher kp kpmg kpn kr krd kred kuokgroup kw ky
69
- kyoto kz la lacaixa lamborghini lamer lancaster lancia land landrover
70
- lanxess lasalle lat latino latrobe law lawyer lb lc lds lease leclerc
71
- lefrak legal lego lexus lgbt li lidl life lifeinsurance lifestyle lighting
72
- like lilly limited limo lincoln linde link lipsy live living lixil lk llc
73
- llp loan loans locker locus loft lol london lotte lotto love lpl
74
- lplfinancial lr ls lt ltd ltda lu lundbeck lupin luxe luxury lv ly ma
75
- macys madrid maif maison makeup man management mango map market marketing
76
- markets marriott marshalls maserati mattel mba mc mckinsey md me med media
77
- meet melbourne meme memorial men menu merckmsd metlife mg mh miami
78
- microsoft mil mini mint mit mitsubishi mk ml mlb mls mm mma mn mo mobi
79
- mobile moda moe moi mom monash money monster mormon mortgage moscow moto
80
- motorcycles mov movie mp mq mr ms msd mt mtn mtr mu museum mutual mv mw mx
81
- my mz na nab nadex nagoya name nationwide natura navy nba nc ne nec net
82
- netbank netflix network neustar new newholland news next nextdirect nexus
83
- nf nfl ng ngo nhk ni nico nike nikon ninja nissan nissay nl no nokia
84
- northwesternmutual norton now nowruz nowtv np nr nra nrw ntt nu nyc nz obi
85
- observer off office okinawa olayan olayangroup oldnavy ollo om omega one
86
- ong onl online onyourside ooo open oracle orange org organic origins osaka
87
- otsuka ott ovh pa page panasonic paris pars partners parts party passagens
88
- pay pccw pe pet pf pfizer pg ph pharmacy phd philips phone photo
89
- photography photos physio pics pictet pictures pid pin ping pink pioneer
90
- pizza pk pl place play playstation plumbing plus pm pn pnc pohl poker
91
- politie porn post pr pramerica praxi press prime pro prod productions prof
92
- progressive promo properties property protection pru prudential ps pt pub
93
- pw pwc py qa qpon quebec quest qvc racing radio raid re read realestate
94
- realtor realty recipes red redstone redumbrella rehab reise reisen reit
95
- reliance ren rent rentals repair report republican rest restaurant review
96
- reviews rexroth rich richardli ricoh rightathome ril rio rip rmit ro
97
- rocher rocks rodeo rogers room rs rsvp ru rugby ruhr run rw rwe ryukyu sa
98
- saarland safe safety sakura sale salon samsclub samsung sandvik
99
- sandvikcoromant sanofi sap sarl sas save saxo sb sbi sbs sc sca scb
100
- schaeffler schmidt scholarships school schule schwarz science scjohnson
101
- scor scot sd se search seat secure security seek select sener services ses
102
- seven sew sex sexy sfr sg sh shangrila sharp shaw shell shia shiksha shoes
103
- shop shopping shouji show showtime shriram si silk sina singles site sj sk
104
- ski skin sky skype sl sling sm smart smile sn sncf so soccer social
105
- softbank software sohu solar solutions song sony soy space sport spot
106
- spreadbetting sr srl ss st stada staples star statebank statefarm stc
107
- stcgroup stockholm storage store stream studio study style su sucks
108
- supplies supply support surf surgery suzuki sv swatch swiftcover swiss sx
109
- sy sydney symantec systems sz tab taipei talk taobao target tatamotors
110
- tatar tattoo tax taxi tc tci td tdk team tech technology tel temasek
111
- tennis teva tf tg th thd theater theatre tiaa tickets tienda tiffany tips
112
- tires tirol tj tjmaxx tjx tk tkmaxx tl tm tmall tn to today tokyo tools
113
- top toray toshiba total tours town toyota toys tr trade trading training
114
- travel travelchannel travelers travelersinsurance trust trv tt tube tui
115
- tunes tushu tv tvs tw tz ua ubank ubs ug uk unicom university uno uol ups
116
- us uy uz va vacations vana vanguard vc ve vegas ventures verisign
117
- versicherung vet vg vi viajes video vig viking villas vin vip virgin visa
118
- vision vistaprint viva vivo vlaanderen vn vodka volkswagen volvo vote
119
- voting voto voyage vu vuelos wales walmart walter wang wanggou watch
120
- watches weather weatherchannel webcam weber website wed wedding weibo weir
121
- wf whoswho wien wiki williamhill win windows wine winners wme
122
- wolterskluwer woodside work works world wow ws wtc wtf xbox xerox xfinity
123
- xihuan xin xn--11b4c3d xn--1ck2e1b xn--1qqw23a xn--2scrj9c xn--30rr7y
124
- xn--3bst00m xn--3ds443g xn--3e0b707e xn--3hcrj9c xn--3oq18vl8pn36a
125
- xn--3pxu8k xn--42c2d9a xn--45br5cyl xn--45brj9c xn--45q11c xn--4gbrim
126
- xn--54b7fta0cc xn--55qw42g xn--55qx5d xn--5su34j936bgsg xn--5tzm5g
127
- xn--6frz82g xn--6qq986b3xl xn--80adxhks xn--80ao21a xn--80aqecdr1a
128
- xn--80asehdb xn--80aswg xn--8y0a063a xn--90a3ac xn--90ae xn--90ais
129
- xn--9dbq2a xn--9et52u xn--9krt00a xn--b4w605ferd xn--bck1b9a5dre4c
130
- xn--c1avg xn--c2br7g xn--cck2b3b xn--cg4bki xn--clchc0ea0b2g2a9gcd
131
- xn--czr694b xn--czrs0t xn--czru2d xn--d1acj3b xn--d1alf xn--e1a4c
132
- xn--eckvdtc9d xn--efvy88h xn--estv75g xn--fct429k xn--fhbei xn--fiq228c5hs
133
- xn--fiq64b xn--fiqs8s xn--fiqz9s xn--fjq720a xn--flw351e xn--fpcrj9c3d
134
- xn--fzc2c9e2c xn--fzys8d69uvgm xn--g2xx48c xn--gckr3f0f xn--gecrj9c
135
- xn--gk3at1e xn--h2breg3eve xn--h2brj9c xn--h2brj9c8c xn--hxt814e
136
- xn--i1b6b1a6a2e xn--imr513n xn--io0a7i xn--j1aef xn--j1amh xn--j6w193g
137
- xn--jlq61u9w7b xn--jvr189m xn--kcrx77d1x4a xn--kprw13d xn--kpry57d
138
- xn--kpu716f xn--kput3i xn--l1acc xn--lgbbat1ad8j xn--mgb9awbf
139
- xn--mgba3a3ejt xn--mgba3a4f16a xn--mgba7c0bbn0a xn--mgbaakc7dvf
140
- xn--mgbaam7a8h xn--mgbab2bd xn--mgbah1a3hjkrd xn--mgbai9azgqp6j
141
- xn--mgbayh7gpa xn--mgbbh1a xn--mgbbh1a71e xn--mgbc0a9azcg xn--mgbca7dzdo
142
- xn--mgbcpq6gpa1a xn--mgberp4a5d4ar xn--mgbgu82a xn--mgbi4ecexp
143
- xn--mgbpl2fh xn--mgbt3dhd xn--mgbtx2b xn--mgbx4cd0ab xn--mix891f
144
- xn--mk1bu44c xn--mxtq1m xn--ngbc5azd xn--ngbe9e0a xn--ngbrx xn--node
145
- xn--nqv7f xn--nqv7fs00ema xn--nyqy26a xn--o3cw4h xn--ogbpf8fl xn--otu796d
146
- xn--p1acf xn--p1ai xn--pbt977c xn--pgbs0dh xn--pssy2u xn--q7ce6a
147
- xn--q9jyb4c xn--qcka1pmc xn--qxa6a xn--qxam xn--rhqv96g xn--rovu88b
148
- xn--rvc1e0am3e xn--s9brj9c xn--ses554g xn--t60b56a xn--tckwe xn--tiq49xqyj
149
- xn--unup4y xn--vermgensberater-ctb xn--vermgensberatung-pwb xn--vhquv
150
- xn--vuq861b xn--w4r85el8fhu5dnra xn--w4rs40l xn--wgbh1c xn--wgbl6a
151
- xn--xhq521b xn--xkc2al3hye2a xn--xkc2dl3a5ee0h xn--y9a3aq xn--yfro4i67o
152
- xn--ygbi2ammx xn--zfr164b xxx xyz yachts yahoo yamaxun yandex ye yodobashi
153
- yoga yokohama you youtube yt yun za zappos zara zero zip zm zone zuerich
154
- zw
155
- )
156
-
157
- DEFAULT_ERROR_MSG = {
158
- :invalid_hostname_length => 'must be between 1 and 255 characters long',
159
- :invalid_label_length => 'must be between 1 and 63 characters long',
160
- :label_begins_or_ends_with_hyphen => 'begins or ends with hyphen',
161
- :label_contains_invalid_characters => "contains invalid characters (valid characters: [%{valid_chars}])",
162
- :hostname_label_is_numeric => 'unqualified hostname part cannot consist of numeric values only',
163
- :hostname_is_not_fqdn => 'is not a fully qualified domain name',
164
- :single_numeric_hostname_label => 'cannot consist of a single numeric label',
165
- :hostname_contains_consecutive_dots => 'must not contain consecutive dots',
166
- :hostname_ends_with_dot => 'must not end with a dot'
167
- }.freeze
168
-
169
- class HostnameValidator < ActiveModel::EachValidator
170
- def initialize(options)
171
- opts = {
172
- :allow_underscore => false,
173
- :require_valid_tld => false,
174
- :valid_tlds => ALLOWED_TLDS,
175
- :allow_numeric_hostname => false,
176
- :allow_wildcard_hostname => false,
177
- :allow_root_label => false
178
- }.merge(options)
179
- super(opts)
180
- end
12
+ def self.load_tlds
13
+ tlds_file_path = File.expand_path('../data/tlds.txt', __dir__)
14
+ Set.new(File.readlines(tlds_file_path)
15
+ .map(&:strip)
16
+ .map(&:downcase)
17
+ .reject { |line| line.start_with?('#') || line.empty? })
18
+ .add('.')
19
+ .freeze
20
+ end
181
21
 
182
- def validate_each(record, attribute, value)
183
- value ||= ''
184
-
185
- # maximum hostname length: 255 characters
186
- add_error(record, attribute, :invalid_hostname_length) unless value.length.between?(1, 255)
187
-
188
- # split each hostname into labels and do various checks
189
- if value.is_a?(String)
190
- labels = value.split '.'
191
- labels.each_with_index do |label, index|
192
- # CHECK 1: hostname label cannot be longer than 63 characters
193
- add_error(record, attribute, :invalid_label_length) unless label.length.between?(1, 63)
194
-
195
- # CHECK 2: hostname label cannot begin or end with hyphen
196
- add_error(record, attribute, :label_begins_or_ends_with_hyphen) if label =~ /^[-]/i or label =~ /[-]$/
197
-
198
- # Take care of wildcard first label
199
- next if options[:allow_wildcard_hostname] and label == '*' and index == 0
200
-
201
- # CHECK 3: hostname can only contain characters:
202
- # a-z, 0-9, hyphen, optional underscore, optional asterisk
203
- valid_chars = 'a-z0-9\-'
204
- valid_chars << '_' if options[:allow_underscore] == true
205
- add_error(record, attribute, :label_contains_invalid_characters, :valid_chars => valid_chars) unless label =~ /^[#{valid_chars}]+$/i
206
- end
207
-
208
- # CHECK 4: the unqualified hostname portion cannot consist of
209
- # numeric values only
210
- if options[:allow_numeric_hostname] == false and labels.length > 0
211
- is_numeric_only = labels[0] =~ /\A\d+\z/
212
- add_error(record, attribute, :hostname_label_is_numeric) if is_numeric_only
213
- end
214
-
215
- # CHECK 5: in order to be fully qualified, the full hostname's
216
- # TLD must be valid
217
- require_valid_tld = options[:require_valid_tld]
218
- require_valid_tld = record.send(require_valid_tld) if require_valid_tld.is_a?(Symbol)
219
- if require_valid_tld
220
- my_tld = value == '.' ? value : labels.last
221
- my_tld ||= ''
222
- has_tld = options[:valid_tlds].select {
223
- |tld| tld =~ /^#{Regexp.escape(my_tld)}$/i
224
- }.empty? ? false : true
225
- add_error(record, attribute, :hostname_is_not_fqdn) unless has_tld
226
- end
227
-
228
- # CHECK 6: hostname may not contain consecutive dots
229
- if value =~ /\.\./
230
- add_error(record, attribute, :hostname_contains_consecutive_dots)
231
- end
232
-
233
- # CHECK 7: do not allow trailing dot unless option is set
234
- if options[:allow_root_label] == false
235
- if value =~ /\.$/
236
- add_error(record, attribute, :hostname_ends_with_dot)
237
- end
238
- end
239
- end
240
- end
22
+ ALLOWED_TLDS = load_tlds
23
+ end
24
+ end
241
25
 
242
- def add_error(record, attr_name, message, *interpolators)
243
- args = {
244
- :default => [DEFAULT_ERROR_MSG[message], options[:message]],
245
- :scope => [:errors, :messages]
246
- }.merge(interpolators.last.is_a?(Hash) ? interpolators.pop : {})
247
- record.errors.add(attr_name, I18n.t( message, **args ))
248
- end
249
- end
26
+ ALLOWED_TLDS = ValidatesHostname::Tld::ALLOWED_TLDS
27
+
28
+ # Validates hostnames.
29
+ class HostnameValidator < ActiveModel::EachValidator
30
+ # @param [Hash] options
31
+ # @option options [Boolean] :allow_underscore (false) Allows underscores in hostname labels.
32
+ # @option options [Boolean] :require_valid_tld (false) Requires the hostname to have a valid TLD.
33
+ # @option options [Array<String>] :valid_tlds (ALLOWED_TLDS) A list of valid TLDs.
34
+ # @option options [Boolean] :allow_numeric_hostname (false) Allows numeric-only hostname labels.
35
+ # @option options [Boolean] :allow_wildcard_hostname (false) Allows wildcard hostnames.
36
+ # @option options [Boolean] :allow_root_label (false) Allows a trailing dot.
37
+ def initialize(options)
38
+ super({
39
+ allow_underscore: false,
40
+ require_valid_tld: false,
41
+ valid_tlds: ALLOWED_TLDS,
42
+ allow_numeric_hostname: false,
43
+ allow_wildcard_hostname: false,
44
+ allow_root_label: false
45
+ }.merge(options))
46
+ end
250
47
 
251
- class DomainnameValidator < HostnameValidator
252
- def initialize(options)
253
- opts = {
254
- :require_valid_tld => true,
255
- :allow_numeric_hostname => true
256
- }.merge(options)
257
- super(opts)
258
- end
48
+ # Validates the hostname.
49
+ #
50
+ # @param [ActiveModel::Base] record The record to validate.
51
+ # @param [Symbol] attribute The attribute to validate.
52
+ # @param [String] value The value to validate.
53
+ def validate_each(record, attribute, value)
54
+ value = value.to_s
55
+ labels = value.split('.')
56
+
57
+ validate_hostname_length(record, attribute, value)
58
+ # CHECK 1: hostname label cannot be longer than 63 characters
59
+ validate_label_length(record, attribute, labels)
60
+ # CHECK 2: hostname label cannot begin or end with hyphen
61
+ validate_label_hyphens(record, attribute, labels)
62
+ # CHECK 3: hostname can only contain valid characters
63
+ validate_label_characters(record, attribute, labels)
64
+ # CHECK 4: the unqualified hostname portion cannot consist of numeric values only
65
+ validate_numeric_hostname(record, attribute, labels)
66
+ # CHECK 5: TLD must be valid if required
67
+ handle_tld_validation(record, attribute, value, labels)
68
+ # CHECK 6: hostname may not contain consecutive dots
69
+ validate_consecutive_dots(record, attribute, value)
70
+ # CHECK 7: do not allow trailing dot unless option is set
71
+ validate_trailing_dot(record, attribute, value)
72
+ end
259
73
 
260
- def validate_each(record, attribute, value)
261
- super
262
-
263
- if value.is_a?(String)
264
- labels = value.split '.'
265
- # CHECK 1: if there is only one label it cannot be numeric even
266
- # though numeric hostnames are allowed
267
- if options[:allow_numeric_hostname] == true
268
- is_numeric_only = labels[0] =~ /\A\d+\z/
269
- if is_numeric_only and labels.size == 1
270
- add_error(record, attribute, :single_numeric_hostname_label)
271
- end
272
- end
273
- end
274
- end
74
+ private
275
75
 
276
- def add_error(record, attr_name, message, *interpolators)
277
- args = {
278
- :default => [DEFAULT_ERROR_MSG[message], options[:message]],
279
- :scope => [:errors, :messages]
280
- }.merge(interpolators.last.is_a?(Hash) ? interpolators.pop : {})
281
- record.errors.add(attr_name, I18n.t( message, **args ))
282
- end
76
+ # Validates the length of the hostname.
77
+ #
78
+ # @param [ActiveModel::Base] record The record to validate.
79
+ # @param [Symbol] attribute The attribute to validate.
80
+ # @param [String] value The value to validate.
81
+ def validate_hostname_length(record, attribute, value)
82
+ # maximum hostname length: 255 characters
83
+ return if value.length.between?(1, 255)
84
+
85
+ add_error(record, attribute, :invalid_hostname_length)
86
+ end
87
+
88
+ # Validates the length of each label in the hostname.
89
+ #
90
+ # @param [ActiveModel::Base] record The record to validate.
91
+ # @param [Symbol] attribute The attribute to validate.
92
+ # @param [Array<String>] labels The labels to validate.
93
+ def validate_label_length(record, attribute, labels)
94
+ labels.each do |label|
95
+ add_error(record, attribute, :invalid_label_length) unless label.length.between?(1, 63)
283
96
  end
97
+ end
284
98
 
285
- class FqdnValidator < HostnameValidator
286
- def initialize(options)
287
- opts = {
288
- :require_valid_tld => true,
289
- }.merge(options)
290
- super(opts)
291
- end
99
+ # Validates that no label begins or ends with a hyphen.
100
+ #
101
+ # @param [ActiveModel::Base] record The record to validate.
102
+ # @param [Symbol] attribute The attribute to validate.
103
+ # @param [Array<String>] labels The labels to validate.
104
+ def validate_label_hyphens(record, attribute, labels)
105
+ labels.each do |label|
106
+ add_error(record, attribute, :label_begins_or_ends_with_hyphen) if label.start_with?('-') || label.end_with?('-')
292
107
  end
108
+ end
293
109
 
294
- class WildcardValidator < HostnameValidator
295
- def initialize(options)
296
- opts = {
297
- :allow_wildcard_hostname => true,
298
- }.merge(options)
299
- super(opts)
110
+ # Validates the characters in each label of the hostname.
111
+ #
112
+ # @param [ActiveModel::Base] record The record to validate.
113
+ # @param [Symbol] attribute The attribute to validate.
114
+ # @param [Array<String>] labels The labels to validate.
115
+ def validate_label_characters(record, attribute, labels)
116
+ labels.each_with_index do |label, index|
117
+ next if options[:allow_wildcard_hostname] && label == '*' && index.zero?
118
+
119
+ valid_chars = 'a-z0-9\-'
120
+ valid_chars += '_' if options[:allow_underscore]
121
+ unless label.match?(/^[#{valid_chars}]+$/i)
122
+ add_error(record, attribute, :label_contains_invalid_characters, valid_chars: valid_chars.delete('\\'))
300
123
  end
301
124
  end
302
125
  end
126
+
127
+ # Validates that the hostname is not numeric.
128
+ #
129
+ # @param [ActiveModel::Base] record The record to validate.
130
+ # @param [Symbol] attribute The attribute to validate.
131
+ # @param [Array<String>] labels The labels to validate.
132
+ def validate_numeric_hostname(record, attribute, labels)
133
+ return unless !options[:allow_numeric_hostname] && !labels.empty? && labels.first.match?(/\A\d+\z/)
134
+
135
+ add_error(record, attribute, :hostname_label_is_numeric)
136
+ end
137
+
138
+ # Validates that the hostname does not contain consecutive dots.
139
+ #
140
+ # @param [ActiveModel::Base] record The record to validate.
141
+ # @param [Symbol] attribute The attribute to validate.
142
+ # @param [String] value The value to validate.
143
+ def validate_consecutive_dots(record, attribute, value)
144
+ return unless value.include?('..')
145
+
146
+ add_error(record, attribute, :hostname_contains_consecutive_dots)
147
+ end
148
+
149
+ # Validates that the hostname does not end with a dot.
150
+ #
151
+ # @param [ActiveModel::Base] record The record to validate.
152
+ # @param [Symbol] attribute The attribute to validate.
153
+ # @param [String] value The value to validate.
154
+ def validate_trailing_dot(record, attribute, value)
155
+ return unless !options[:allow_root_label] && value.end_with?('.')
156
+
157
+ add_error(record, attribute, :hostname_ends_with_dot)
158
+ end
159
+
160
+ # Handles the TLD validation.
161
+ #
162
+ # @param [ActiveModel::Base] record The record to validate.
163
+ # @param [Symbol] attribute The attribute to validate.
164
+ # @param [String] value The value to validate.
165
+ # @param [Array<String>] labels The labels to validate.
166
+ def handle_tld_validation(record, attribute, value, labels)
167
+ require_valid_tld = options[:require_valid_tld]
168
+ require_valid_tld = record.send(require_valid_tld) if require_valid_tld.is_a?(Symbol)
169
+
170
+ return unless require_valid_tld
171
+
172
+ tld = (value == '.' ? value : labels.last) || ''
173
+ return if options[:valid_tlds].any? { |v| v.casecmp?(tld) }
174
+
175
+ add_error(record, attribute, :hostname_is_not_fqdn)
176
+ end
177
+
178
+ # Adds an error to the record.
179
+ #
180
+ # @param [ActiveModel::Base] record The record to add the error to.
181
+ # @param [Symbol] attr_name The attribute to add the error to.
182
+ # @param [Symbol] message The error message.
183
+ # @param [Hash] interpolators The interpolators for the error message.
184
+ def add_error(record, attr_name, message, interpolators = {})
185
+ # Use the custom message if provided in the options, otherwise use the
186
+ # symbolic message key for I18n lookup.
187
+ custom_message = options[:message]
188
+ record.errors.add(attr_name, custom_message || message, **interpolators)
189
+ end
190
+ end
191
+
192
+ # Validates domain names.
193
+ class DomainnameValidator < HostnameValidator
194
+ def initialize(options)
195
+ # The :domainname validator intentionally sets allow_numeric_hostname to true.
196
+ # This behavior cannot be overridden by the user.
197
+ super({ require_valid_tld: true, allow_numeric_hostname: true }.merge(options))
198
+ end
199
+
200
+ def validate_each(record, attribute, value)
201
+ super
202
+
203
+ return unless value.is_a?(String)
204
+
205
+ labels = value.split('.')
206
+ # CHECK 1: if there is only one label it cannot be numeric
207
+ return unless labels.first.match?(/\A\d+\z/) && labels.size == 1
208
+
209
+ add_error(record, attribute, :single_numeric_hostname_label)
210
+ end
211
+ end
212
+
213
+ # Validates fully qualified domain names.
214
+ class FqdnValidator < HostnameValidator
215
+ def initialize(options)
216
+ super({ require_valid_tld: true }.merge(options))
217
+ end
218
+ end
219
+
220
+ # Validates wildcard hostnames.
221
+ class WildcardValidator < HostnameValidator
222
+ def initialize(options)
223
+ super({ allow_wildcard_hostname: true }.merge(options))
224
+ end
303
225
  end
304
226
 
305
- ActiveRecord::Base.send(:include, PAK::ValidatesHostname)
227
+ if defined?(Rails)
228
+ module ValidatesHostname
229
+ # Railtie to automatically load I18n translations.
230
+ class Railtie < Rails::Railtie
231
+ initializer 'validates_hostname.i18n' do
232
+ I18n.load_path += Dir[File.expand_path('../config/locales/*.yml', __dir__)]
233
+ end
234
+ end
235
+ end
236
+ end
@@ -1,44 +1,23 @@
1
- # -*- encoding: utf-8 -*-
2
- # stub: validates_hostname 1.0.13 ruby lib
1
+ # frozen_string_literal: true
3
2
 
4
- Gem::Specification.new do |s|
5
- s.name = "validates_hostname".freeze
6
- s.version = "1.0.13"
3
+ # stub: validates_hostname 2.0.0 ruby lib
7
4
 
8
- s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
- s.require_paths = ["lib".freeze]
10
- s.authors = ["Kim Nøgaard".freeze]
11
- s.date = "2022-10-14"
12
- s.description = "Extension to ActiveRecord::Base for validating hostnames".freeze
13
- s.email = "jasen@jasen.dk".freeze
14
- s.extra_rdoc_files = ["README.rdoc".freeze, "CHANGELOG.rdoc".freeze, "MIT-LICENSE".freeze]
15
- s.files = ["CHANGELOG.rdoc".freeze, "MIT-LICENSE".freeze, "README.rdoc".freeze, "Rakefile".freeze, "lib/validates_hostname".freeze, "lib/validates_hostname.rb".freeze, "lib/validates_hostname/version.rb".freeze, "validates_hostname.gemspec".freeze]
16
- s.homepage = "https://github.com/KimNorgaard/validates_hostname".freeze
17
- s.licenses = ["MIT".freeze]
18
- s.rubygems_version = "3.1.4".freeze
19
- s.summary = "Checks for valid hostnames".freeze
5
+ Gem::Specification.new do |s|
6
+ s.name = 'validates_hostname'
7
+ s.version = '2.0.0'
8
+ s.required_ruby_version = '>= 3.0.0'
20
9
 
21
- if s.respond_to? :specification_version then
22
- s.specification_version = 4
23
- end
10
+ s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
11
+ s.require_paths = ['lib']
12
+ s.authors = ['Kim Nøgaard']
13
+ s.description = 'Extension to ActiveModel for validating hostnames'
14
+ s.email = 'jasen@jasen.dk'
15
+ s.extra_rdoc_files = ['README.md', 'CHANGELOG.md', 'LICENSE']
16
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ s.homepage = 'https://github.com/KimNorgaard/validates_hostname'
18
+ s.licenses = ['MIT']
19
+ s.summary = 'Checks for valid hostnames'
24
20
 
25
- if s.respond_to? :add_runtime_dependency then
26
- s.add_runtime_dependency(%q<activerecord>.freeze, [">= 3.0"])
27
- s.add_runtime_dependency(%q<activesupport>.freeze, [">= 3.0"])
28
- s.add_development_dependency(%q<rspec>.freeze, ["~> 3.0"])
29
- s.add_development_dependency(%q<rspec-rails>.freeze, [">= 0"])
30
- s.add_development_dependency(%q<rails>.freeze, [">= 0"])
31
- s.add_development_dependency(%q<sqlite3>.freeze, [">= 0"])
32
- s.add_development_dependency(%q<pry-byebug>.freeze, [">= 0"])
33
- s.add_development_dependency(%q<rspec-collection_matchers>.freeze, [">= 0"])
34
- else
35
- s.add_dependency(%q<activerecord>.freeze, [">= 3.0"])
36
- s.add_dependency(%q<activesupport>.freeze, [">= 3.0"])
37
- s.add_dependency(%q<rspec>.freeze, ["~> 3.0"])
38
- s.add_dependency(%q<rspec-rails>.freeze, [">= 0"])
39
- s.add_dependency(%q<rails>.freeze, [">= 0"])
40
- s.add_dependency(%q<sqlite3>.freeze, [">= 0"])
41
- s.add_dependency(%q<pry-byebug>.freeze, [">= 0"])
42
- s.add_dependency(%q<rspec-collection_matchers>.freeze, [">= 0"])
43
- end
21
+ s.add_dependency('activemodel', ['>= 6.0', '< 8'])
22
+ s.metadata['rubygems_mfa_required'] = 'true'
44
23
  end