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