valid_hostname 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ValidHostname
4
+ VERSION = '2.0.0'
5
+ 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