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.
@@ -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