mini_defender 0.6.7 → 0.6.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0766463845ff12b982d3230cbd2d62a4506d5b470160256a2ec0684146f4e8bc'
4
- data.tar.gz: d24329e0981cb6dabe1d42a85e2c0bec719610a829dc2eb6d2dab480faace668
3
+ metadata.gz: 3bae6fdb0faacfd54fe463d30f80710e40d383bd868d64e65da7243bae0c5b1c
4
+ data.tar.gz: f9cf13d8acbc654b9c647c33b322937722b97f0a99b8d389d4311002a3a5a65e
5
5
  SHA512:
6
- metadata.gz: 3a232eba0c84024e5248cabcc3f324af4c86956eb494c5f8fb4c1531f76965a32de4b1b674584b1008d4f566b2752712498b600646bf1a1ed94770d5ddeacfa9
7
- data.tar.gz: 5afafaa73bd774d094540de2db462f1b2d0c840bf8fda34a5ff9c9959b0d1d6a9dfd0999343ac14d1ce31f1c94de2ff4a255f6650611fbe4d02cee63eb89f8e8
6
+ metadata.gz: 2c50350da6f341f0487357cf63cad4dbf070c5c8f89614344619182dffd9b7f345b3015466e185fe4114cadcd2a3f7159bbf87e7413bd0d887327cdb13cebbce
7
+ data.tar.gz: 8bb5e5e2702c985a7c3933147d0db5f59abde4e566f8747aa6158d21fb5bd440dcf6f1d8706ae0846e0532a14d918d2b36d04673f519d98c72412b480c25fef4
@@ -0,0 +1,64 @@
1
+ # RFC 2606 - Reserved Domain Names
2
+ # https://datatracker.ietf.org/doc/html/rfc2606
3
+ *.test
4
+ *.example
5
+ *.invalid
6
+ localhost
7
+ *.localhost
8
+ example.com
9
+ example.net
10
+ example.org
11
+
12
+ # RFC 6761 - Special-Use Domain Names
13
+ # https://datatracker.ietf.org/doc/html/rfc6761
14
+ *.local
15
+ *.onion
16
+ *.home.arpa
17
+
18
+ # RFC 1918 - Private Address Space
19
+ # https://datatracker.ietf.org/doc/html/rfc1918
20
+ 10.*
21
+ 172.(1[6-9]|2[0-9]|3[0-1]).*
22
+ 192.168.*
23
+
24
+ # RFC 3330 - Special-Use IPv4 Addresses
25
+ # https://datatracker.ietf.org/doc/html/rfc3330
26
+ 127.*
27
+ 169.254.*
28
+ 0.0.0.0
29
+
30
+ # RFC 4291 - IPv6 Addressing Architecture
31
+ # https://datatracker.ietf.org/doc/html/rfc4291
32
+ ::1
33
+ fe80:*
34
+ ::
35
+ ::ffff:*
36
+
37
+ # RFC 4193 - Unique Local IPv6 Unicast Addresses
38
+ # https://datatracker.ietf.org/doc/html/rfc4193
39
+ fc00:*
40
+ fd00:*
41
+
42
+ # Common Internal Network Patterns (based on RFC 2606 and 6761)
43
+ *.intranet
44
+ *.internal
45
+ *.corp
46
+ *.lan
47
+ intranet.*
48
+ internal.*
49
+ corp.*
50
+ lan.*
51
+
52
+ # Development Environments (based on RFC 2606)
53
+ *.dev.test
54
+ *.staging.test
55
+ *.qa.test
56
+ dev.example.*
57
+ staging.example.*
58
+ qa.example.*
59
+
60
+ # RFC 6052 - IPv6 Translation
61
+ 64:ff9b::*
62
+
63
+ # RFC 3986 - URI Encoded Forms
64
+ %6C%6F%63%61%6C%68%6F%73%74
@@ -1,17 +1,140 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'uri'
4
+ require 'ipaddr'
5
+ require 'public_suffix'
4
6
 
5
7
  class MiniDefender::Rules::Url < MiniDefender::Rule
8
+ ALLOWED_MODIFIERS = %w[https public not_ip not_private]
9
+
10
+ def initialize(modifiers = [])
11
+ @modifiers = Array(modifiers).map(&:to_s)
12
+
13
+ unless @modifiers.empty?
14
+ validate_modifiers!
15
+ end
16
+
17
+ @validation_error = nil
18
+ end
19
+
6
20
  def self.signature
7
21
  'url'
8
22
  end
9
23
 
24
+ def self.make(modifiers) # no need to raise an error when no modifier is entered; as 'url' rule checks URL structure on its own
25
+ new(modifiers)
26
+ end
27
+
10
28
  def passes?(attribute, value, validator)
11
- value.is_a?(String) && URI.regexp(%w[http https]).match?(value)
29
+ unless value.is_a?(String)
30
+ return false
31
+ end
32
+
33
+ begin
34
+ uri = URI.parse(value)
35
+
36
+ if uri.host.blank? || uri.scheme.blank?
37
+ return false
38
+ end
39
+
40
+ unless %w[http https].include?(uri.scheme)
41
+ @validation_error = 'URL protocol must be HTTP or HTTPS.'
42
+ return false
43
+ end
44
+
45
+ if @modifiers.empty?
46
+ return true
47
+ end
48
+
49
+ if @modifiers.include?('https') && uri.scheme != 'https'
50
+ @validation_error = 'The URL must use HTTPS.'
51
+ return false
52
+ end
53
+
54
+ if @modifiers.include?('public') && (!PublicSuffix.valid?(uri.host) || private_network?(uri.host))
55
+ @validation_error = 'The URL must use a valid public domain.'
56
+ return false
57
+ end
58
+
59
+ if @modifiers.include?('not_ip') && ip_address?(uri.host)
60
+ @validation_error = 'IP addresses are not allowed in URLs.'
61
+ return false
62
+ end
63
+
64
+ if @modifiers.include?('not_private') && private_network?(uri.host)
65
+ @validation_error = 'Private or reserved resources are not allowed.'
66
+ return false
67
+ end
68
+
69
+ true
70
+ rescue URI::InvalidURIError
71
+ @validation_error = 'The field must contain a valid URL.'
72
+ false
73
+ rescue PublicSuffix::Error
74
+ false
75
+ end
12
76
  end
13
77
 
14
78
  def message(attribute, value, validator)
15
- 'The field must contain a valid URL.'
79
+ @validation_error || 'The field must contain a valid URL.'
80
+ end
81
+
82
+ def private_network?(host)
83
+ unless host
84
+ return false
85
+ end
86
+
87
+ host = host.downcase
88
+
89
+ private_patterns.any? { |pattern| pattern.match?(host) }
90
+ end
91
+
92
+ private
93
+
94
+ def validate_modifiers!
95
+ invalid_modifiers = @modifiers - ALLOWED_MODIFIERS
96
+ if invalid_modifiers.empty?
97
+ return
98
+ end
99
+
100
+ raise ArgumentError, "Invalid URL modifiers: #{invalid_modifiers.join(', ')}"
101
+ end
102
+
103
+ def ip_address?(host)
104
+ unless host
105
+ return false
106
+ end
107
+
108
+ begin
109
+ IPAddr.new(host)
110
+ true
111
+ rescue IPAddr::InvalidAddressError
112
+ false
113
+ end
114
+ end
115
+
116
+ def private_patterns
117
+ # Cache the result in class variable to avoid loading again
118
+ # across multiple instances
119
+ @@private_patterns ||= begin
120
+ pattern_file = File.expand_path('../data/private_network_patterns.txt', __dir__)
121
+
122
+ File.readlines(pattern_file).filter_map do |line|
123
+ line = line.strip
124
+
125
+ if line.empty? || line.start_with?('#')
126
+ next
127
+ end
128
+
129
+ # Pattern => regex (once)
130
+ pattern = line
131
+ .gsub('.', '\.') # escape dots
132
+ .gsub('*', '.*') # wildcards => regex
133
+ .gsub('[0-9]+', '\d+') # convert number ranges
134
+ .gsub(/\[(.+?)\]/, '(\1)') # convert chars classes
135
+
136
+ Regexp.new("^#{pattern}$", Regexp::IGNORECASE)
137
+ end
138
+ end
16
139
  end
17
140
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniDefender
4
- VERSION = "0.6.7"
4
+ VERSION = "0.6.8"
5
5
  end
@@ -34,4 +34,5 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency 'countries'
35
35
  spec.add_runtime_dependency 'money'
36
36
  spec.add_runtime_dependency 'marcel'
37
+ spec.add_runtime_dependency 'public_suffix'
37
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_defender
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.7
4
+ version: 0.6.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ali Alhoshaiyan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-10 00:00:00.000000000 Z
11
+ date: 2024-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: public_suffix
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  description: A small and efficient validation library for Rails and anything that
98
112
  uses Ruby.
99
113
  email:
@@ -110,6 +124,7 @@ files:
110
124
  - RULES.md
111
125
  - Rakefile
112
126
  - lib/mini_defender.rb
127
+ - lib/mini_defender/data/private_network_patterns.txt
113
128
  - lib/mini_defender/extensions/enumerable.rb
114
129
  - lib/mini_defender/handles_validation_errors.rb
115
130
  - lib/mini_defender/rule.rb
@@ -181,7 +196,6 @@ files:
181
196
  - lib/mini_defender/rules/national_id.rb
182
197
  - lib/mini_defender/rules/not_ending_with.rb
183
198
  - lib/mini_defender/rules/not_in.rb
184
- - lib/mini_defender/rules/not_local_url.rb
185
199
  - lib/mini_defender/rules/not_regex.rb
186
200
  - lib/mini_defender/rules/not_starting_with.rb
187
201
  - lib/mini_defender/rules/numeric.rb
@@ -234,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
234
248
  - !ruby/object:Gem::Version
235
249
  version: '0'
236
250
  requirements: []
237
- rubygems_version: 3.5.11
251
+ rubygems_version: 3.5.3
238
252
  signing_key:
239
253
  specification_version: 4
240
254
  summary: A small and efficient validation library for Rails and anything that uses
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class MiniDefender::Rules::NotLocalURL < MiniDefender::Rule
4
- LOCALHOST_PATTERNS = [
5
- /^localhost$/i, # localhost, LOCALHOST
6
- /^127\./, # 127.x.x.x
7
- /^::1$/, # IPv6 localhost
8
- /^0\.0\.0\.0$/, # All interfaces IPv4
9
- /^::$/, # IPv6 unspecified
10
- /\.local$/i, # domain.local
11
- /^local\./i, # local.domain
12
- /^localhost\./i, # localhost.anything
13
- ]
14
-
15
- def self.signature
16
- 'not_local_url'
17
- end
18
-
19
- def passes?(attribute, value, validator)
20
- uri = URI.parse(value.to_s)
21
- host = uri.host.to_s.downcase
22
-
23
- !LOCALHOST_PATTERNS.any? { |pattern| host.match?(pattern) }
24
- rescue URI::InvalidURIError
25
- false
26
- end
27
-
28
- def message(attribute, value, validator)
29
- 'URL cannot point to localhost or local domain.'
30
- end
31
- end