ruby_routes 2.0.0 → 2.1.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 +4 -4
- data/lib/ruby_routes/constant.rb +1 -0
- data/lib/ruby_routes/radix_tree.rb +7 -2
- data/lib/ruby_routes/route.rb +29 -27
- data/lib/ruby_routes/route_set.rb +2 -3
- data/lib/ruby_routes/string_extensions.rb +3 -1
- data/lib/ruby_routes/url_helpers.rb +3 -2
- data/lib/ruby_routes/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb6b2a38cb8f31bb3e79fa66dbbb1a0620c6eec1f668680e152efe38e7926b42
|
4
|
+
data.tar.gz: db5d62093e15a422d85e6c7ec373327ca1f37fd24f7b851c12bdb2f70cf81622
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e84488c6309cea2dab99be9a350139f2042f3d9601e4a0d1d20eb3d66c5530934f2c4f0c5b384dec7fa3095802f4d710ab2cc4f1a0158486cd5bba618995801
|
7
|
+
data.tar.gz: 0fda0c79e0a935ba9227faba495030c9ff2144ebcc289969eed0a0eb95596d0d69289d1a12b160ef37c18d4021fd5ae172c12b7db48b3beeddb32435e6a2d7ad
|
data/lib/ruby_routes/constant.rb
CHANGED
@@ -36,8 +36,13 @@ module RubyRoutes
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def find(path, method, params_out = nil)
|
39
|
+
# Handle nil path and method cases
|
40
|
+
path ||= ''
|
41
|
+
method = method.to_s.upcase if method
|
42
|
+
# Strip query string before matching
|
43
|
+
clean_path = path.split('?', 2).first || ''
|
39
44
|
# Fast path: root route
|
40
|
-
if
|
45
|
+
if clean_path == '/' || clean_path.empty?
|
41
46
|
handler = @root.get_handler(method)
|
42
47
|
if @root.is_endpoint && handler
|
43
48
|
return [handler, params_out || {}]
|
@@ -46,7 +51,7 @@ module RubyRoutes
|
|
46
51
|
end
|
47
52
|
end
|
48
53
|
|
49
|
-
segments = split_path_cached(
|
54
|
+
segments = split_path_cached(clean_path)
|
50
55
|
current = @root
|
51
56
|
params = params_out || {}
|
52
57
|
params.clear if params_out
|
data/lib/ruby_routes/route.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'timeout'
|
3
3
|
require 'set'
|
4
|
+
require 'rack'
|
4
5
|
require_relative 'route/small_lru'
|
5
6
|
|
6
7
|
module RubyRoutes
|
@@ -33,6 +34,7 @@ module RubyRoutes
|
|
33
34
|
|
34
35
|
def extract_params(request_path, parsed_qp = nil)
|
35
36
|
path_params = extract_path_params_fast(request_path)
|
37
|
+
|
36
38
|
return EMPTY_HASH unless path_params
|
37
39
|
|
38
40
|
# Use optimized param building
|
@@ -103,7 +105,7 @@ module RubyRoutes
|
|
103
105
|
ROOT_PATH = '/'.freeze
|
104
106
|
UNRESERVED_RE = /\A[a-zA-Z0-9\-._~]+\z/.freeze
|
105
107
|
QUERY_CACHE_SIZE = 128
|
106
|
-
|
108
|
+
|
107
109
|
# Common HTTP methods - interned for performance
|
108
110
|
HTTP_GET = 'GET'.freeze
|
109
111
|
HTTP_POST = 'POST'.freeze
|
@@ -220,17 +222,14 @@ module RubyRoutes
|
|
220
222
|
return EMPTY_HASH if @compiled_segments.empty? && request_path == ROOT_PATH
|
221
223
|
return nil if @compiled_segments.empty?
|
222
224
|
|
223
|
-
# Fast path normalization
|
224
225
|
path_parts = split_path_fast(request_path)
|
225
|
-
|
226
|
-
# Check
|
226
|
+
|
227
|
+
# Check for wildcard/splat segment
|
227
228
|
has_splat = @compiled_segments.any? { |seg| seg[:type] == :splat }
|
228
|
-
|
229
|
+
|
229
230
|
if has_splat
|
230
|
-
# For wildcard routes, path can have more parts than segments
|
231
231
|
return nil if path_parts.size < @compiled_segments.size - 1
|
232
232
|
else
|
233
|
-
# For non-wildcard routes, size must match exactly
|
234
233
|
return nil if @compiled_segments.size != path_parts.size
|
235
234
|
end
|
236
235
|
|
@@ -238,8 +237,8 @@ module RubyRoutes
|
|
238
237
|
end
|
239
238
|
|
240
239
|
def split_path_fast(request_path)
|
241
|
-
#
|
242
|
-
path = request_path
|
240
|
+
# Remove query string before splitting
|
241
|
+
path = request_path.split('?', 2).first
|
243
242
|
path = path[1..-1] if path.start_with?('/')
|
244
243
|
path = path[0...-1] if path.end_with?('/') && path != ROOT_PATH
|
245
244
|
path.empty? ? [] : path.split('/')
|
@@ -268,7 +267,7 @@ module RubyRoutes
|
|
268
267
|
return @defaults if params.empty?
|
269
268
|
|
270
269
|
merged = get_thread_local_merged_hash
|
271
|
-
|
270
|
+
|
272
271
|
# Merge defaults first if they exist
|
273
272
|
merged.update(@defaults) unless @defaults.empty?
|
274
273
|
|
@@ -279,7 +278,7 @@ module RubyRoutes
|
|
279
278
|
# Fallback for older Ruby versions
|
280
279
|
params.each { |k, v| merged[k.to_s] = v }
|
281
280
|
end
|
282
|
-
|
281
|
+
|
283
282
|
merged
|
284
283
|
end
|
285
284
|
|
@@ -307,7 +306,7 @@ module RubyRoutes
|
|
307
306
|
|
308
307
|
# Pre-allocate array for parts to avoid string buffer operations
|
309
308
|
parts = []
|
310
|
-
|
309
|
+
|
311
310
|
@compiled_segments.each do |seg|
|
312
311
|
case seg[:type]
|
313
312
|
when :static
|
@@ -340,10 +339,13 @@ module RubyRoutes
|
|
340
339
|
# Fast segment encoding with caching for common values
|
341
340
|
def encode_segment_fast(str)
|
342
341
|
return str if UNRESERVED_RE.match?(str)
|
343
|
-
|
342
|
+
|
344
343
|
# Cache encoded segments to avoid repeated encoding
|
345
344
|
@encoding_cache ||= {}
|
346
|
-
@encoding_cache[str] ||=
|
345
|
+
@encoding_cache[str] ||= begin
|
346
|
+
# Use URI.encode_www_form_component but replace + with %20 for path segments
|
347
|
+
URI.encode_www_form_component(str).gsub('+', '%20')
|
348
|
+
end
|
347
349
|
end
|
348
350
|
|
349
351
|
# Optimized query params with caching
|
@@ -352,7 +354,7 @@ module RubyRoutes
|
|
352
354
|
return EMPTY_HASH unless query_start
|
353
355
|
|
354
356
|
query_string = path[(query_start + 1)..-1]
|
355
|
-
return EMPTY_HASH if query_string.empty?
|
357
|
+
return EMPTY_HASH if query_string.empty? || query_string.match?(/^\?+$/)
|
356
358
|
|
357
359
|
# Cache query param parsing
|
358
360
|
if (cached = @query_cache.get(query_string))
|
@@ -403,7 +405,7 @@ module RubyRoutes
|
|
403
405
|
when Proc
|
404
406
|
# DEPRECATED: Proc constraints are deprecated due to security risks
|
405
407
|
warn_proc_constraint_deprecation(param)
|
406
|
-
|
408
|
+
|
407
409
|
# For backward compatibility, still execute but with strict timeout
|
408
410
|
begin
|
409
411
|
Timeout.timeout(0.05) do # Reduced timeout for security
|
@@ -442,23 +444,23 @@ module RubyRoutes
|
|
442
444
|
|
443
445
|
def warn_proc_constraint_deprecation(param)
|
444
446
|
return if @proc_warnings_shown&.include?(param)
|
445
|
-
|
447
|
+
|
446
448
|
@proc_warnings_shown ||= Set.new
|
447
449
|
@proc_warnings_shown << param
|
448
|
-
|
450
|
+
|
449
451
|
warn <<~WARNING
|
450
452
|
[DEPRECATION] Proc constraints are deprecated due to security risks.
|
451
|
-
|
453
|
+
|
452
454
|
Parameter: #{param}
|
453
455
|
Route: #{@path}
|
454
|
-
|
456
|
+
|
455
457
|
Secure alternatives:
|
456
458
|
- Use regex: constraints: { #{param}: /\\A\\d+\\z/ }
|
457
459
|
- Use built-in types: constraints: { #{param}: :int }
|
458
460
|
- Use hash constraints: constraints: { #{param}: { min_length: 3, format: /\\A[a-z]+\\z/ } }
|
459
|
-
|
461
|
+
|
460
462
|
Available built-in types: :int, :uuid, :email, :slug, :alpha, :alphanumeric
|
461
|
-
|
463
|
+
|
462
464
|
This warning will become an error in a future version.
|
463
465
|
WARNING
|
464
466
|
end
|
@@ -468,23 +470,23 @@ module RubyRoutes
|
|
468
470
|
if constraint[:min_length] && value.length < constraint[:min_length]
|
469
471
|
raise RubyRoutes::ConstraintViolation, "Value too short (minimum #{constraint[:min_length]} characters)"
|
470
472
|
end
|
471
|
-
|
473
|
+
|
472
474
|
if constraint[:max_length] && value.length > constraint[:max_length]
|
473
475
|
raise RubyRoutes::ConstraintViolation, "Value too long (maximum #{constraint[:max_length]} characters)"
|
474
476
|
end
|
475
|
-
|
477
|
+
|
476
478
|
if constraint[:format] && !value.match?(constraint[:format])
|
477
479
|
raise RubyRoutes::ConstraintViolation, "Value does not match required format"
|
478
480
|
end
|
479
|
-
|
481
|
+
|
480
482
|
if constraint[:in] && !constraint[:in].include?(value)
|
481
483
|
raise RubyRoutes::ConstraintViolation, "Value not in allowed list"
|
482
484
|
end
|
483
|
-
|
485
|
+
|
484
486
|
if constraint[:not_in] && constraint[:not_in].include?(value)
|
485
487
|
raise RubyRoutes::ConstraintViolation, "Value in forbidden list"
|
486
488
|
end
|
487
|
-
|
489
|
+
|
488
490
|
if constraint[:range] && !constraint[:range].cover?(value.to_i)
|
489
491
|
raise RubyRoutes::ConstraintViolation, "Value not in allowed range"
|
490
492
|
end
|
@@ -73,7 +73,7 @@ module RubyRoutes
|
|
73
73
|
controller: route.controller,
|
74
74
|
action: route.action
|
75
75
|
}.freeze
|
76
|
-
|
76
|
+
|
77
77
|
insert_cache_entry(cache_key, result)
|
78
78
|
result
|
79
79
|
end
|
@@ -143,9 +143,8 @@ module RubyRoutes
|
|
143
143
|
|
144
144
|
# Optimized cache key building - avoid string interpolation
|
145
145
|
def build_cache_key(method, path)
|
146
|
-
# Use string interpolation which is faster than buffer + dup + freeze
|
147
146
|
# String interpolation creates a new string directly without intermediate allocations
|
148
|
-
"#{method}:#{path}"
|
147
|
+
"#{method}:#{path}"
|
149
148
|
end
|
150
149
|
|
151
150
|
# Get thread-local params hash, reusing when possible
|
@@ -4,6 +4,7 @@ module RubyRoutes
|
|
4
4
|
module UrlHelpers
|
5
5
|
def self.included(base)
|
6
6
|
base.extend(ClassMethods)
|
7
|
+
base.include(base.url_helpers)
|
7
8
|
end
|
8
9
|
|
9
10
|
module ClassMethods
|
@@ -49,11 +50,11 @@ module RubyRoutes
|
|
49
50
|
# HTML forms only support GET and POST
|
50
51
|
# For other methods, use POST with _method hidden field
|
51
52
|
form_method = (method == 'get') ? 'get' : 'post'
|
52
|
-
|
53
|
+
|
53
54
|
safe_path = CGI.escapeHTML(path.to_s)
|
54
55
|
safe_form_method = CGI.escapeHTML(form_method)
|
55
56
|
html = "<form action=\"#{safe_path}\" method=\"#{safe_form_method}\">"
|
56
|
-
|
57
|
+
|
57
58
|
# Add _method hidden field for non-GET/POST methods
|
58
59
|
if method != 'get' && method != 'post'
|
59
60
|
safe_method = CGI.escapeHTML(method)
|
data/lib/ruby_routes/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_routes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yosef Benny Widyokarsono
|
@@ -37,6 +37,20 @@ dependencies:
|
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '13.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rack
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.2'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.2'
|
40
54
|
- !ruby/object:Gem::Dependency
|
41
55
|
name: simplecov
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|