valid_hostname 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.
- checksums.yaml +7 -0
- data/.github/workflows/rubocop.yml +29 -0
- data/.github/workflows/tests.yml +42 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +105 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +206 -0
- data/Rakefile +12 -0
- data/config/locales/de.yml +13 -0
- data/config/locales/en.yml +13 -0
- data/config/valid_hostname.yml +1491 -0
- data/lib/valid_hostname/domainname_validator.rb +21 -0
- data/lib/valid_hostname/hostname_validator.rb +58 -0
- data/lib/valid_hostname/validate_hostname.rb +132 -0
- data/lib/valid_hostname/version.rb +5 -0
- data/lib/valid_hostname.rb +14 -0
- data/spec/record_valid_hostname_spec.rb +529 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/test_model.rb +75 -0
- data/spec/validate_hostname_spec.rb +148 -0
- data/valid_hostname.gemspec +32 -0
- metadata +195 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'valid_hostname/hostname_validator'
|
4
|
+
|
5
|
+
class DomainnameValidator < HostnameValidator
|
6
|
+
def initialize(options)
|
7
|
+
super({ require_valid_tld: true,
|
8
|
+
allow_numeric_hostname: true }.merge(options))
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
super
|
13
|
+
return unless value.is_a? String
|
14
|
+
return if options[:allow_numeric_hostname] != true
|
15
|
+
|
16
|
+
labels = value.split '.'
|
17
|
+
is_numeric_only = labels[0] =~ /\A\d+\z/
|
18
|
+
|
19
|
+
add_error record, attribute, :single_numeric_hostname_label if is_numeric_only && labels.size == 1
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
require 'active_model/validations'
|
5
|
+
require 'valid_hostname/validate_hostname'
|
6
|
+
|
7
|
+
class HostnameValidator < ActiveModel::EachValidator
|
8
|
+
def initialize(options)
|
9
|
+
super(ValidateHostname.default_options.merge(options))
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate_each(record, attribute, value)
|
13
|
+
value ||= ''
|
14
|
+
|
15
|
+
if options[:verbose]
|
16
|
+
add_error record, attribute, :invalid_hostname_length unless ValidateHostname.valid_length? value
|
17
|
+
add_error record, attribute, :hostname_contains_consecutive_dots unless ValidateHostname.valid_dots? value
|
18
|
+
|
19
|
+
unless ValidateHostname.valid_trailing_dot?(value,
|
20
|
+
allow_root_label: options[:allow_root_label])
|
21
|
+
add_error record, attribute, :hostname_ends_with_dot
|
22
|
+
end
|
23
|
+
|
24
|
+
unless ValidateHostname.valid_tld? value, valid_tlds: options[:valid_tlds], require_valid_tld: options[:require_valid_tld]
|
25
|
+
add_error record, attribute, :hostname_is_not_fqdn
|
26
|
+
end
|
27
|
+
|
28
|
+
return unless value.is_a? String
|
29
|
+
|
30
|
+
add_error record, attribute, :invalid_label_length unless ValidateHostname.valid_label_length? value
|
31
|
+
add_error record, attribute, :label_begins_or_ends_with_hyphen unless ValidateHostname.valid_hyphens? value
|
32
|
+
|
33
|
+
unless ValidateHostname.valid_characters?(value,
|
34
|
+
allow_underscore: options[:allow_underscore],
|
35
|
+
allow_wildcard_hostname: options[:allow_wildcard_hostname])
|
36
|
+
add_error record,
|
37
|
+
attribute,
|
38
|
+
:label_contains_invalid_characters,
|
39
|
+
valid_chars: ValidateHostname.allowed_characters(allow_underscore: options[:allow_underscore])
|
40
|
+
end
|
41
|
+
|
42
|
+
unless ValidateHostname.valid_numeric_only? value, allow_numeric_hostname: options[:allow_numeric_hostname]
|
43
|
+
add_error record, attribute, :hostname_label_is_numeric
|
44
|
+
end
|
45
|
+
elsif !ValidateHostname.valid? value, **options
|
46
|
+
record.errors.add attribute, options[:message].nil? ? I18n.t(:invalid, scope: 'activerecord.errors.messages') : options[:message]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def add_error(record, attribute, message_key, *interpolators)
|
53
|
+
args = { default: [message_key, options[:message]],
|
54
|
+
scope: %i[errors messages hostname] }.merge(interpolators.last.is_a?(Hash) ? interpolators.pop : {})
|
55
|
+
|
56
|
+
record.errors.add attribute, I18n.t(message_key, **args)
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ValidateHostname
|
4
|
+
class << self
|
5
|
+
def allowed_tlds
|
6
|
+
@allowed_tlds ||= begin
|
7
|
+
config = File.expand_path '../../config/valid_hostname.yml', __dir__
|
8
|
+
YAML.load_file(config)['allowed_tlds']
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def allowed_characters(allow_underscore: false)
|
13
|
+
valid_chars = +'a-z0-9\-'
|
14
|
+
valid_chars << '_' if allow_underscore
|
15
|
+
valid_chars
|
16
|
+
end
|
17
|
+
|
18
|
+
# use for valid?
|
19
|
+
def default_options
|
20
|
+
{ allow_underscore: false,
|
21
|
+
require_valid_tld: false,
|
22
|
+
allow_numeric_hostname: false,
|
23
|
+
allow_wildcard_hostname: false,
|
24
|
+
allow_root_label: false,
|
25
|
+
verbose: true }
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?(value, user_options = nil)
|
29
|
+
value ||= ''
|
30
|
+
|
31
|
+
# works with ruby 2.7 and => 3
|
32
|
+
options = ValidateHostname.default_options.merge(user_options || {})
|
33
|
+
|
34
|
+
valid_length?(value) &&
|
35
|
+
valid_label_length?(value) &&
|
36
|
+
valid_hyphens?(value) &&
|
37
|
+
valid_dots?(value) &&
|
38
|
+
valid_trailing_dot?(value,
|
39
|
+
allow_root_label: options[:allow_root_label]) &&
|
40
|
+
valid_characters?(value,
|
41
|
+
allow_underscore: options[:allow_underscore],
|
42
|
+
allow_wildcard_hostname: options[:allow_wildcard_hostname]) &&
|
43
|
+
valid_numeric_only?(value,
|
44
|
+
allow_numeric_hostname: options[:allow_numeric_hostname]) &&
|
45
|
+
valid_tld?(value,
|
46
|
+
valid_tlds: nil, require_valid_tld: options[:require_valid_tld])
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid_length?(value)
|
50
|
+
return false unless check_string value
|
51
|
+
|
52
|
+
value.length.between? 1, 255
|
53
|
+
end
|
54
|
+
|
55
|
+
def valid_label_length?(value)
|
56
|
+
return false unless check_string value
|
57
|
+
|
58
|
+
labels = value.split '.'
|
59
|
+
labels.each do |label|
|
60
|
+
return false unless label.length.between? 1, 63
|
61
|
+
end
|
62
|
+
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def valid_hyphens?(value)
|
67
|
+
labels = value.split '.'
|
68
|
+
labels.each do |label|
|
69
|
+
return false if label =~ /^-/i || label =~ /-$/
|
70
|
+
end
|
71
|
+
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
# hostname can only contain characters:
|
76
|
+
# a-z, 0-9, hyphen, optional underscore, optional asterisk
|
77
|
+
def valid_characters?(value, allow_underscore: false, allow_wildcard_hostname: false)
|
78
|
+
return true unless check_string value
|
79
|
+
|
80
|
+
valid_chars = allowed_characters allow_underscore: allow_underscore
|
81
|
+
labels = value.split '.'
|
82
|
+
labels.each_with_index do |label, index|
|
83
|
+
# Take care of wildcard first label
|
84
|
+
next if allow_wildcard_hostname && label == '*' && index.zero?
|
85
|
+
next if /\A[#{valid_chars}]+\z/i.match?(label)
|
86
|
+
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
# the unqualified hostname portion cannot consist of numeric values only
|
94
|
+
def valid_numeric_only?(value, allow_numeric_hostname: false)
|
95
|
+
return true if allow_numeric_hostname == true
|
96
|
+
return true unless check_string value
|
97
|
+
|
98
|
+
first_label = value.split('.').first
|
99
|
+
first_label !~ /\A\d+\z/
|
100
|
+
end
|
101
|
+
|
102
|
+
# hostname may not contain consecutive dots
|
103
|
+
def valid_dots?(value)
|
104
|
+
!/\.\./.match?(value)
|
105
|
+
end
|
106
|
+
|
107
|
+
def valid_trailing_dot?(value, allow_root_label: false)
|
108
|
+
return true if allow_root_label != false
|
109
|
+
return true unless check_string value
|
110
|
+
|
111
|
+
value[-1] != '.'
|
112
|
+
end
|
113
|
+
|
114
|
+
def valid_tld?(value, valid_tlds: nil, require_valid_tld: true)
|
115
|
+
return true if !check_string(value) || value == '.'
|
116
|
+
return true if require_valid_tld != true
|
117
|
+
|
118
|
+
labels = value.split '.'
|
119
|
+
return true if labels.count.zero?
|
120
|
+
return false if valid_tlds && valid_tlds.empty?
|
121
|
+
|
122
|
+
valid_tlds = valid_tlds.nil? ? allowed_tlds : valid_tlds.map(&:downcase)
|
123
|
+
valid_tlds.include? labels.last.downcase
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def check_string(value)
|
129
|
+
value.is_a?(String) && !value.empty?
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'valid_hostname/version'
|
4
|
+
require 'valid_hostname/validate_hostname'
|
5
|
+
|
6
|
+
require 'valid_hostname/hostname_validator'
|
7
|
+
require 'valid_hostname/domainname_validator'
|
8
|
+
|
9
|
+
require 'debug' if ENV['ENABLE_DEBUG']
|
10
|
+
|
11
|
+
I18n.load_path += Dir.glob File.expand_path('../config/locales/**/*', __dir__)
|
12
|
+
|
13
|
+
module ValidHostname
|
14
|
+
end
|