roda 3.84.0 → 3.86.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/roda/plugins/Integer_matcher_max.rb +9 -8
- data/lib/roda/plugins/_optimized_matching.rb +2 -2
- data/lib/roda/plugins/_symbol_class_matchers.rb +107 -0
- data/lib/roda/plugins/autoload_hash_branches.rb +1 -1
- data/lib/roda/plugins/autoload_named_routes.rb +1 -1
- data/lib/roda/plugins/capture_erb.rb +6 -5
- data/lib/roda/plugins/class_matchers.rb +91 -14
- data/lib/roda/plugins/conditional_sessions.rb +67 -0
- data/lib/roda/plugins/content_security_policy.rb +12 -2
- data/lib/roda/plugins/early_hints.rb +1 -2
- data/lib/roda/plugins/permissions_policy.rb +12 -2
- data/lib/roda/plugins/placeholder_string_matchers.rb +4 -0
- data/lib/roda/plugins/public.rb +1 -1
- data/lib/roda/plugins/render.rb +1 -1
- data/lib/roda/plugins/symbol_matchers.rb +70 -15
- data/lib/roda/request.rb +16 -13
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +7 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1851a201539b728f1af90ea1b85b2b33a9026f71986cb65c1aabdd079f653ad
|
4
|
+
data.tar.gz: f360e0bfeb3442df2fa1f96e28362eee9850c0f54d160bfbfc2b11d62d33ba39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2bef4abc3d5e08ddb5a1c9c27f8b626b631a5951cec7cee062c497074cb7f68e6c8b36e75261cc913a580a87d5111438e8a21488669812c9a3b227bf9609e0b
|
7
|
+
data.tar.gz: e668a47039e529aa026e21ddfd6346b3e1fa224f432b46085b33242c9551ced88278328f2d2d471b2593f841da0a882d8f85dd2468277484409f679485a219df
|
@@ -23,27 +23,28 @@ class Roda
|
|
23
23
|
module IntegerMatcherMax
|
24
24
|
def self.configure(app, max=nil)
|
25
25
|
if max
|
26
|
-
app
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
app.class_eval do
|
27
|
+
meth = :_max_value_convert_class_Integer
|
28
|
+
define_method(meth){max}
|
29
|
+
alias_method meth, meth
|
30
|
+
private meth
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
|
-
module
|
35
|
+
module InstanceMethods
|
35
36
|
private
|
36
37
|
|
37
38
|
# Do not have the Integer matcher max when over the maximum
|
38
39
|
# configured Integer value.
|
39
|
-
def
|
40
|
+
def _convert_class_Integer(value)
|
40
41
|
value = super
|
41
|
-
value if value <=
|
42
|
+
value if value <= _max_value_convert_class_Integer
|
42
43
|
end
|
43
44
|
|
44
45
|
# Use 2**63-1 as the default maximum value for the Integer
|
45
46
|
# matcher.
|
46
|
-
def
|
47
|
+
def _max_value_convert_class_Integer
|
47
48
|
9223372036854775807
|
48
49
|
end
|
49
50
|
end
|
@@ -52,7 +52,7 @@ class Roda
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
elsif matcher == Integer
|
55
|
-
if (matchdata = /\A\/(\d{1,100})(?=\/|\z)/.match(@remaining_path)) && (value =
|
55
|
+
if (matchdata = /\A\/(\d{1,100})(?=\/|\z)/.match(@remaining_path)) && (value = scope.send(:_convert_class_Integer, matchdata[1]))
|
56
56
|
@remaining_path = matchdata.post_match
|
57
57
|
always{yield(value)}
|
58
58
|
end
|
@@ -151,7 +151,7 @@ class Roda
|
|
151
151
|
always{yield rp[1, len]}
|
152
152
|
end
|
153
153
|
elsif matcher == Integer
|
154
|
-
if (matchdata = /\A\/(\d{1,100})\z/.match(@remaining_path)) && (value =
|
154
|
+
if (matchdata = /\A\/(\d{1,100})\z/.match(@remaining_path)) && (value = scope.send(:_convert_class_Integer, matchdata[1]))
|
155
155
|
@remaining_path = ''
|
156
156
|
always{yield(value)}
|
157
157
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
module SymbolClassMatchers_
|
7
|
+
module ClassMethods
|
8
|
+
private
|
9
|
+
|
10
|
+
# Backend of symbol_matcher and class_matcher.
|
11
|
+
def _symbol_class_matcher(expected_class, obj, matcher, block, &request_class_block)
|
12
|
+
unless obj.is_a?(expected_class)
|
13
|
+
raise RodaError, "Invalid type passed to class_matcher or symbol_matcher: #{matcher.inspect}"
|
14
|
+
end
|
15
|
+
|
16
|
+
if obj.is_a?(Symbol)
|
17
|
+
type = "symbol"
|
18
|
+
meth = :"match_symbol_#{obj}"
|
19
|
+
else
|
20
|
+
type = "class"
|
21
|
+
meth = :"_match_class_#{obj}"
|
22
|
+
end
|
23
|
+
|
24
|
+
case matcher
|
25
|
+
when Regexp
|
26
|
+
regexp = matcher
|
27
|
+
consume_regexp = self::RodaRequest.send(:consume_pattern, regexp)
|
28
|
+
when Symbol
|
29
|
+
unless opts[:symbol_matchers]
|
30
|
+
raise RodaError, "cannot provide Symbol matcher to class_matcher unless using symbol_matchers plugin: #{matcher.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
regexp, consume_regexp, matcher_block = opts[:symbol_matchers][matcher]
|
34
|
+
|
35
|
+
unless regexp
|
36
|
+
raise RodaError, "unregistered symbol matcher given to #{type}_matcher: #{matcher.inspect}"
|
37
|
+
end
|
38
|
+
|
39
|
+
block = _merge_matcher_blocks(type, obj, block, matcher_block)
|
40
|
+
when Class
|
41
|
+
unless opts[:class_matchers]
|
42
|
+
raise RodaError, "cannot provide Class matcher to symbol_matcher unless using class_matchers plugin: #{matcher.inspect}"
|
43
|
+
end
|
44
|
+
|
45
|
+
regexp, consume_regexp, matcher_block = opts[:class_matchers][matcher]
|
46
|
+
unless regexp
|
47
|
+
raise RodaError, "unregistered class matcher given to #{type}_matcher: #{matcher.inspect}"
|
48
|
+
end
|
49
|
+
block = _merge_matcher_blocks(type, obj, block, matcher_block)
|
50
|
+
else
|
51
|
+
raise RodaError, "unsupported matcher given to #{type}_matcher: #{matcher.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
if block.is_a?(Symbol)
|
55
|
+
convert_meth = block
|
56
|
+
elsif block
|
57
|
+
convert_meth = :"_convert_#{type}_#{obj}"
|
58
|
+
define_method(convert_meth, &block)
|
59
|
+
private convert_meth
|
60
|
+
end
|
61
|
+
|
62
|
+
array = opts[:"#{type}_matchers"][obj] = [regexp, consume_regexp, convert_meth].freeze
|
63
|
+
|
64
|
+
self::RodaRequest.class_eval do
|
65
|
+
class_exec(meth, array, &request_class_block)
|
66
|
+
private meth
|
67
|
+
end
|
68
|
+
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# If both block and matche_meth are given,
|
73
|
+
# define a method for block, and then return a
|
74
|
+
# proc that calls matcher_meth first, and only calls
|
75
|
+
# the newly defined method with the return values of matcher_meth
|
76
|
+
# if matcher_method returns a truthy value.
|
77
|
+
# Otherwise, return matcher_meth or block.
|
78
|
+
def _merge_matcher_blocks(type, obj, block, matcher_meth)
|
79
|
+
if matcher_meth
|
80
|
+
if block
|
81
|
+
convert_meth = :"_convert_merge_#{type}_#{obj}"
|
82
|
+
define_method(convert_meth, &block)
|
83
|
+
private convert_meth
|
84
|
+
|
85
|
+
proc do |*a|
|
86
|
+
if captures = send(matcher_meth, *a)
|
87
|
+
if captures.is_a?(Array)
|
88
|
+
send(convert_meth, *captures)
|
89
|
+
else
|
90
|
+
send(convert_meth, captures)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
else
|
95
|
+
matcher_meth
|
96
|
+
end
|
97
|
+
else
|
98
|
+
block
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
register_plugin(:_symbol_class_matchers, SymbolClassMatchers_)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
@@ -68,7 +68,7 @@ class Roda
|
|
68
68
|
|
69
69
|
# Eagerly load all hash branches when freezing the application.
|
70
70
|
def freeze
|
71
|
-
opts.delete(:autoload_hash_branch_files).each{|file| require file}
|
71
|
+
opts.delete(:autoload_hash_branch_files).each{|file| require file} unless opts.frozen?
|
72
72
|
super
|
73
73
|
end
|
74
74
|
end
|
@@ -54,7 +54,7 @@ class Roda
|
|
54
54
|
|
55
55
|
# Eagerly load all autoloaded named routes when freezing the application.
|
56
56
|
def freeze
|
57
|
-
opts.delete(:autoload_named_route_files).each{|file| require file}
|
57
|
+
opts.delete(:autoload_named_route_files).each{|file| require file} unless opts.frozen?
|
58
58
|
super
|
59
59
|
end
|
60
60
|
end
|
@@ -16,10 +16,11 @@ class Roda
|
|
16
16
|
# to wrap template blocks with arbitrary output and then inject the
|
17
17
|
# wrapped output into the template.
|
18
18
|
#
|
19
|
-
# If the output buffer object responds to +capture+
|
20
|
-
# +erubi/capture_block+ is being
|
21
|
-
# this will call +capture+ on the
|
22
|
-
# of setting the output buffer object
|
19
|
+
# If the output buffer object responds to +capture+ and is not
|
20
|
+
# an instance of String (e.g. when +erubi/capture_block+ is being
|
21
|
+
# used as the template engine), this will call +capture+ on the
|
22
|
+
# output buffer object, instead of setting the output buffer object
|
23
|
+
# temporarily to a new object.
|
23
24
|
module CaptureERB
|
24
25
|
def self.load_dependencies(app)
|
25
26
|
app.plugin :render
|
@@ -34,7 +35,7 @@ class Roda
|
|
34
35
|
outvar = render_opts[:template_opts][:outvar]
|
35
36
|
buf_was = instance_variable_get(outvar)
|
36
37
|
|
37
|
-
if buf_was.respond_to?(:capture)
|
38
|
+
if buf_was.respond_to?(:capture) && !buf_was.instance_of?(String)
|
38
39
|
buf_was.capture(&block)
|
39
40
|
else
|
40
41
|
begin
|
@@ -12,11 +12,10 @@ class Roda
|
|
12
12
|
# # ...
|
13
13
|
# end
|
14
14
|
#
|
15
|
-
# You can register a Date class matcher for that regexp
|
16
|
-
# the block must return an array):
|
15
|
+
# You can register a Date class matcher for that regexp:
|
17
16
|
#
|
18
17
|
# class_matcher(Date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
|
19
|
-
#
|
18
|
+
# Date.new(y.to_i, m.to_i, d.to_i)
|
20
19
|
# end
|
21
20
|
#
|
22
21
|
# And then use the Date class as a matcher, and it will yield a Date object:
|
@@ -26,7 +25,8 @@ class Roda
|
|
26
25
|
# end
|
27
26
|
#
|
28
27
|
# This is useful to DRY up code if you are using the same type of pattern and
|
29
|
-
# type conversion in multiple places in your application.
|
28
|
+
# type conversion in multiple places in your application. You can have the
|
29
|
+
# block return an array to yield multiple captures.
|
30
30
|
#
|
31
31
|
# If you have a segment match the passed regexp, but decide during block
|
32
32
|
# processing that you do not want to treat it as a match, you can have the
|
@@ -37,22 +37,99 @@ class Roda
|
|
37
37
|
# y = y.to_i
|
38
38
|
# m = m.to_i
|
39
39
|
# d = d.to_i
|
40
|
-
#
|
40
|
+
# Date.new(y, m, d) if Date.valid_date?(y, m, d)
|
41
41
|
# end
|
42
42
|
#
|
43
|
+
# The second argument to class_matcher can be a class already registered
|
44
|
+
# as a class matcher. This can DRY up code that wants a conversion
|
45
|
+
# performed by an existing class matcher:
|
46
|
+
#
|
47
|
+
# class_matcher Employee, Integer do |id|
|
48
|
+
# Employee[id]
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# With the above example, the Integer matcher performs the conversion to
|
52
|
+
# integer, so +id+ is yielded as an integer. The block then looks up the
|
53
|
+
# employee with that id. If there is no employee with that id, then
|
54
|
+
# the Employee matcher will not match.
|
55
|
+
#
|
56
|
+
# If using the symbol_matchers plugin, you can provide a recognized symbol
|
57
|
+
# matcher as the second argument to class_matcher, and it will work in
|
58
|
+
# a similar manner:
|
59
|
+
#
|
60
|
+
# symbol_matcher(:employee_id, /E-(\d{6})/) do |employee_id|
|
61
|
+
# employee_id.to_i
|
62
|
+
# end
|
63
|
+
# class_matcher Employee, :employee_id do |id|
|
64
|
+
# Employee[id]
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# Blocks passed to the class_matchers plugin are evaluated in route
|
68
|
+
# block context.
|
69
|
+
#
|
43
70
|
# This plugin does not work with the params_capturing plugin, as it does not
|
44
71
|
# offer the ability to associate block arguments with named keys.
|
45
72
|
module ClassMatchers
|
73
|
+
def self.load_dependencies(app)
|
74
|
+
app.plugin :_symbol_class_matchers
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.configure(app)
|
78
|
+
app.opts[:class_matchers] ||= {
|
79
|
+
Integer=>[/(\d{1,100})/, /\A\/(\d{1,100})(?=\/|\z)/, :_convert_class_Integer].freeze,
|
80
|
+
String=>[/([^\/]+)/, nil, nil].freeze
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
46
84
|
module ClassMethods
|
47
|
-
# Set the
|
48
|
-
#
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
85
|
+
# Set the matcher and block to use for the given class.
|
86
|
+
# The matcher can be a regexp, registered class matcher, or registered symbol
|
87
|
+
# matcher (if using the symbol_matchers plugin).
|
88
|
+
#
|
89
|
+
# If providing a regexp, the block given will be called with all regexp captures.
|
90
|
+
# If providing a registered class or symbol, the block will be called with the
|
91
|
+
# captures returned by the block for the registered class or symbol, or the regexp
|
92
|
+
# captures if no block was registered with the class or symbol. In either case,
|
93
|
+
# if a block is given, it should return an array with the captures to yield to
|
94
|
+
# the match block.
|
95
|
+
def class_matcher(klass, matcher, &block)
|
96
|
+
_symbol_class_matcher(Class, klass, matcher, block) do |meth, (_, regexp, convert_meth)|
|
97
|
+
if regexp
|
98
|
+
define_method(meth){consume(regexp, convert_meth)}
|
99
|
+
else
|
100
|
+
define_method(meth){_consume_segment(convert_meth)}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Freeze the class_matchers hash when freezing the app.
|
106
|
+
def freeze
|
107
|
+
opts[:class_matchers].freeze
|
108
|
+
super
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
module RequestMethods
|
113
|
+
# Use faster approach for segment matching. This is used for
|
114
|
+
# matchers based on the String class matcher, and avoids the
|
115
|
+
# use of regular expressions for scanning.
|
116
|
+
def _consume_segment(convert_meth)
|
117
|
+
rp = @remaining_path
|
118
|
+
if _match_class_String
|
119
|
+
if convert_meth
|
120
|
+
if captures = scope.send(convert_meth, @captures.pop)
|
121
|
+
if captures.is_a?(Array)
|
122
|
+
@captures.concat(captures)
|
123
|
+
else
|
124
|
+
@captures << captures
|
125
|
+
end
|
126
|
+
else
|
127
|
+
@remaining_path = rp
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
else
|
131
|
+
true
|
132
|
+
end
|
56
133
|
end
|
57
134
|
end
|
58
135
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
class Roda
|
4
|
+
module RodaPlugins
|
5
|
+
# The conditional_sessions plugin loads the sessions plugin. However,
|
6
|
+
# it only allows sessions if the block passed to the plugin returns
|
7
|
+
# truthy. The block is evaluated in request context. This is designed for
|
8
|
+
# use in applications that want to use sessions for some requests,
|
9
|
+
# and want to be sure that sessions are not used for other requests.
|
10
|
+
# For example, if you want to make sure that sessions are not used for
|
11
|
+
# requests with paths starting with /static, you could do:
|
12
|
+
#
|
13
|
+
# plugin :conditional_sessions, secret: ENV["SECRET"] do
|
14
|
+
# !path_info.start_with?('/static')
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# The the request session, session_created_at, and session_updated_at methods
|
18
|
+
# raise a RodaError exception when sessions are not allowed. The request
|
19
|
+
# persist_session and route scope clear_session methods do nothing when
|
20
|
+
# sessions are not allowed.
|
21
|
+
module ConditionalSessions
|
22
|
+
# Pass all options to the sessions block, and use the block to define
|
23
|
+
# a request method for whether sessions are allowed.
|
24
|
+
def self.load_dependencies(app, opts=OPTS, &block)
|
25
|
+
app.plugin :sessions, opts
|
26
|
+
app::RodaRequest.class_eval do
|
27
|
+
define_method(:use_sessions?, &block)
|
28
|
+
alias use_sessions? use_sessions?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
# Do nothing if not using sessions.
|
34
|
+
def clear_session
|
35
|
+
super if @_request.use_sessions?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module RequestMethods
|
40
|
+
# Raise RodaError if not using sessions.
|
41
|
+
def session
|
42
|
+
raise RodaError, "session called on request not using sessions" unless use_sessions?
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
# Raise RodaError if not using sessions.
|
47
|
+
def session_created_at
|
48
|
+
raise RodaError, "session_created_at called on request not using sessions" unless use_sessions?
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
# Raise RodaError if not using sessions.
|
53
|
+
def session_updated_at
|
54
|
+
raise RodaError, "session_updated_at called on request not using sessions" unless use_sessions?
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
# Do nothing if not using sessions.
|
59
|
+
def persist_session(headers, session)
|
60
|
+
super if use_sessions?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
register_plugin(:conditional_sessions, ConditionalSessions)
|
66
|
+
end
|
67
|
+
end
|
@@ -92,7 +92,10 @@ class Roda
|
|
92
92
|
# content_security_policy.get_script_src
|
93
93
|
# # => [:self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']]
|
94
94
|
#
|
95
|
-
# The clear method can be used to remove all settings from the policy.
|
95
|
+
# The clear method can be used to remove all settings from the policy. Empty policies
|
96
|
+
# do not set any headers. You can use +response.skip_content_security_policy!+ to skip
|
97
|
+
# setting a policy. This is faster than calling +content_security_policy.clear+, since
|
98
|
+
# it does not duplicate the default policy.
|
96
99
|
#
|
97
100
|
# The following methods to set boolean settings are also defined:
|
98
101
|
#
|
@@ -304,12 +307,19 @@ class Roda
|
|
304
307
|
@content_security_policy ||= roda_class.opts[:content_security_policy].dup
|
305
308
|
end
|
306
309
|
|
310
|
+
# Do not set a content security policy header for this response.
|
311
|
+
def skip_content_security_policy!
|
312
|
+
@skip_content_security_policy = true
|
313
|
+
end
|
314
|
+
|
307
315
|
private
|
308
316
|
|
309
317
|
# Set the appropriate content security policy header.
|
310
318
|
def set_default_headers
|
311
319
|
super
|
312
|
-
|
320
|
+
unless @skip_content_security_policy
|
321
|
+
(@content_security_policy || roda_class.opts[:content_security_policy]).set_header(headers)
|
322
|
+
end
|
313
323
|
end
|
314
324
|
end
|
315
325
|
end
|
@@ -4,8 +4,7 @@
|
|
4
4
|
class Roda
|
5
5
|
module RodaPlugins
|
6
6
|
# The early_hints plugin allows sending 103 Early Hints responses
|
7
|
-
# using the rack.early_hints environment variable.
|
8
|
-
# is only supported by puma 3.11+, and on other servers this is a no-op.
|
7
|
+
# using the rack.early_hints environment variable.
|
9
8
|
# Early hints allow clients to preload necessary files before receiving
|
10
9
|
# the response.
|
11
10
|
module EarlyHints
|
@@ -99,7 +99,10 @@ class Roda
|
|
99
99
|
# permissions_policy.get_fullscreen
|
100
100
|
# # => [:self, "https://example.com", "https://*.example.com"]
|
101
101
|
#
|
102
|
-
# The clear method can be used to remove all settings from the policy.
|
102
|
+
# The clear method can be used to remove all settings from the policy. Empty policies
|
103
|
+
# do not set any headers. You can use +response.skip_permissions_policy!+ to skip
|
104
|
+
# setting a policy. This is faster than calling +permissions_policy.clear+, since
|
105
|
+
# it does not duplicate the default policy.
|
103
106
|
module PermissionsPolicy
|
104
107
|
SUPPORTED_SETTINGS = %w'
|
105
108
|
accelerometer
|
@@ -311,12 +314,19 @@ class Roda
|
|
311
314
|
@permissions_policy ||= roda_class.opts[:permissions_policy].dup
|
312
315
|
end
|
313
316
|
|
317
|
+
# Do not set a permissions policy header for this response.
|
318
|
+
def skip_permissions_policy!
|
319
|
+
@skip_permissions_policy = true
|
320
|
+
end
|
321
|
+
|
314
322
|
private
|
315
323
|
|
316
324
|
# Set the appropriate permissions policy header.
|
317
325
|
def set_default_headers
|
318
326
|
super
|
319
|
-
|
327
|
+
unless @skip_permissions_policy
|
328
|
+
(@permissions_policy || roda_class.opts[:permissions_policy]).set_header(headers)
|
329
|
+
end
|
320
330
|
end
|
321
331
|
end
|
322
332
|
end
|
@@ -21,6 +21,10 @@ class Roda
|
|
21
21
|
#
|
22
22
|
# r.is "foo", String
|
23
23
|
# r.is "foo", :bar
|
24
|
+
#
|
25
|
+
# If used with the symbol_matchers plugin, this plugin respects the regexps
|
26
|
+
# for the registered symbols, but it does not perform the conversions, the
|
27
|
+
# captures for the regexp are used directly as the captures for the match method.
|
24
28
|
module PlaceholderStringMatchers
|
25
29
|
def self.load_dependencies(app)
|
26
30
|
app.plugin :_symbol_regexp_matchers
|
data/lib/roda/plugins/public.rb
CHANGED
@@ -42,12 +42,12 @@ class Roda
|
|
42
42
|
# end
|
43
43
|
module Public
|
44
44
|
SPLIT = Regexp.union(*[File::SEPARATOR, File::ALT_SEPARATOR].compact)
|
45
|
-
PARSER = URI::DEFAULT_PARSER
|
46
45
|
RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
|
47
46
|
ENCODING_MAP = {:zstd=>'zstd', :brotli=>'br', :gzip=>'gzip'}.freeze
|
48
47
|
ENCODING_EXTENSIONS = {'br'=>'.br', 'gzip'=>'.gz', 'zstd'=>'.zst'}.freeze
|
49
48
|
|
50
49
|
# :nocov:
|
50
|
+
PARSER = defined?(::URI::RFC2396_PARSER) ? ::URI::RFC2396_PARSER : ::URI::DEFAULT_PARSER
|
51
51
|
MATCH_METHOD = RUBY_VERSION >= '2.4' ? :match? : :match
|
52
52
|
# :nocov:
|
53
53
|
|
data/lib/roda/plugins/render.rb
CHANGED
@@ -48,7 +48,7 @@ class Roda
|
|
48
48
|
#
|
49
49
|
# :allowed_paths :: Set the template paths to allow. Attempts to render paths outside
|
50
50
|
# of these paths will raise an error. Defaults to the +:views+ directory.
|
51
|
-
# :cache :: nil/false to explicitly disable
|
51
|
+
# :cache :: nil/false to explicitly disable permanent template caching. By default, permanent
|
52
52
|
# template caching is disabled by default if RACK_ENV is development. When permanent
|
53
53
|
# template caching is disabled, for templates with paths in the file system, the
|
54
54
|
# modification time of the file will be checked on every render, and if it has changed,
|
@@ -23,7 +23,7 @@ class Roda
|
|
23
23
|
#
|
24
24
|
# :d :: <tt>/(\d+)/</tt>, a decimal segment
|
25
25
|
# :rest :: <tt>/(.*)/</tt>, all remaining characters, if any
|
26
|
-
# :w :: <tt>/(\w+)/</tt>,
|
26
|
+
# :w :: <tt>/(\w+)/</tt>, an alphanumeric segment
|
27
27
|
#
|
28
28
|
# If the placeholder_string_matchers plugin is loaded, this feature also applies to
|
29
29
|
# placeholders in strings, so the following:
|
@@ -39,11 +39,10 @@ class Roda
|
|
39
39
|
# be loaded first.
|
40
40
|
#
|
41
41
|
# You can provide a block when calling +symbol_matcher+, and it will be called
|
42
|
-
# for all matches to allow for type conversion
|
43
|
-
# array:
|
42
|
+
# for all matches to allow for type conversion:
|
44
43
|
#
|
45
44
|
# symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
|
46
|
-
#
|
45
|
+
# Date.new(y.to_i, m.to_i, d.to_i)
|
47
46
|
# end
|
48
47
|
#
|
49
48
|
# route do |r|
|
@@ -61,29 +60,80 @@ class Roda
|
|
61
60
|
# y = y.to_i
|
62
61
|
# m = m.to_i
|
63
62
|
# d = d.to_i
|
64
|
-
#
|
63
|
+
# Date.new(y, m, d) if Date.valid_date?(y, m, d)
|
65
64
|
# end
|
66
65
|
#
|
67
|
-
#
|
68
|
-
#
|
66
|
+
# You can have the block return an array to yield multiple captures.
|
67
|
+
#
|
68
|
+
# The second argument to symbol_matcher can be a symbol already registered
|
69
|
+
# as a symbol matcher. This can DRY up code that wants a conversion
|
70
|
+
# performed by an existing class matcher or to use the same regexp:
|
71
|
+
#
|
72
|
+
# symbol_matcher :employee_id, :d do |id|
|
73
|
+
# id.to_i
|
74
|
+
# end
|
75
|
+
# symbol_matcher :employee, :employee_id do |id|
|
76
|
+
# Employee[id]
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# With the above example, the :d matcher matches only decimal strings, but
|
80
|
+
# yields them as string. The registered :employee_id matcher converts the
|
81
|
+
# decimal string to an integer. The registered :employee matcher builds
|
82
|
+
# on that and uses the integer to lookup the related employee. If there is
|
83
|
+
# no employee with that id, then the :employee matcher will not match.
|
84
|
+
#
|
85
|
+
# If using the class_matchers plugin, you can provide a recognized class
|
86
|
+
# matcher as the second argument to symbol_matcher, and it will work in
|
87
|
+
# a similar manner:
|
88
|
+
#
|
89
|
+
# symbol_matcher :employee, Integer do |id|
|
90
|
+
# Employee[id]
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# Blocks passed to the symbol matchers plugin are evaluated in route
|
94
|
+
# block context.
|
95
|
+
#
|
96
|
+
# If providing a block to the symbol_matchers plugin, the symbol may
|
97
|
+
# not work with the params_capturing plugin. Note that the use of
|
98
|
+
# symbol matchers inside strings when using the placeholder_string_matchers
|
99
|
+
# plugin only uses the regexp, it does not respect the conversion blocks
|
100
|
+
# registered with the symbols.
|
69
101
|
module SymbolMatchers
|
70
102
|
def self.load_dependencies(app)
|
71
103
|
app.plugin :_symbol_regexp_matchers
|
104
|
+
app.plugin :_symbol_class_matchers
|
72
105
|
end
|
73
106
|
|
74
107
|
def self.configure(app)
|
108
|
+
app.opts[:symbol_matchers] ||= {}
|
75
109
|
app.symbol_matcher(:d, /(\d+)/)
|
76
110
|
app.symbol_matcher(:w, /(\w+)/)
|
77
111
|
app.symbol_matcher(:rest, /(.*)/)
|
78
112
|
end
|
79
113
|
|
80
114
|
module ClassMethods
|
81
|
-
# Set the
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
115
|
+
# Set the matcher and block to use for the given class.
|
116
|
+
# The matcher can be a regexp, registered symbol matcher, or registered class
|
117
|
+
# matcher (if using the class_matchers plugin).
|
118
|
+
#
|
119
|
+
# If providing a regexp, the block given will be called with all regexp captures.
|
120
|
+
# If providing a registered symbol or class, the block will be called with the
|
121
|
+
# captures returned by the block for the registered symbol or class, or the regexp
|
122
|
+
# captures if no block was registered with the symbol or class. In either case,
|
123
|
+
# if a block is given, it should return an array with the captures to yield to
|
124
|
+
# the match block.
|
125
|
+
def symbol_matcher(s, matcher, &block)
|
126
|
+
_symbol_class_matcher(Symbol, s, matcher, block) do |meth, array|
|
127
|
+
define_method(meth){array}
|
128
|
+
end
|
129
|
+
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
# Freeze the class_matchers hash when freezing the app.
|
134
|
+
def freeze
|
135
|
+
opts[:symbol_matchers].freeze
|
136
|
+
super
|
87
137
|
end
|
88
138
|
end
|
89
139
|
|
@@ -97,8 +147,13 @@ class Roda
|
|
97
147
|
meth = :"match_symbol_#{s}"
|
98
148
|
if respond_to?(meth, true)
|
99
149
|
# Allow calling private match methods
|
100
|
-
re,
|
101
|
-
|
150
|
+
_, re, convert_meth = send(meth)
|
151
|
+
if re
|
152
|
+
consume(re, convert_meth)
|
153
|
+
else
|
154
|
+
# defined in class_matchers plugin
|
155
|
+
_consume_segment(convert_meth)
|
156
|
+
end
|
102
157
|
else
|
103
158
|
super
|
104
159
|
end
|
data/lib/roda/request.rb
CHANGED
@@ -443,16 +443,7 @@ class Roda
|
|
443
443
|
# Match integer segment of up to 100 decimal characters, and yield resulting value as an
|
444
444
|
# integer.
|
445
445
|
def _match_class_Integer
|
446
|
-
consume(/\A\/(\d{1,100})(?=\/|\z)
|
447
|
-
if i = _match_class_convert_Integer(i)
|
448
|
-
[i]
|
449
|
-
end
|
450
|
-
end
|
451
|
-
end
|
452
|
-
|
453
|
-
# Convert the segment matched by the Integer matcher to an integer.
|
454
|
-
def _match_class_convert_Integer(value)
|
455
|
-
value.to_i
|
446
|
+
consume(/\A\/(\d{1,100})(?=\/|\z)/, :_convert_class_Integer)
|
456
447
|
end
|
457
448
|
|
458
449
|
# Match only if all of the arguments in the given array match.
|
@@ -555,14 +546,26 @@ class Roda
|
|
555
546
|
# match, returns false without changes. Otherwise, modifies
|
556
547
|
# SCRIPT_NAME to include the matched path, removes the matched
|
557
548
|
# path from PATH_INFO, and updates captures with any regex captures.
|
558
|
-
def consume(pattern)
|
549
|
+
def consume(pattern, meth=nil)
|
559
550
|
if matchdata = pattern.match(@remaining_path)
|
560
551
|
captures = matchdata.captures
|
561
|
-
|
552
|
+
|
553
|
+
if meth
|
554
|
+
return unless captures = scope.send(meth, *captures)
|
555
|
+
# :nocov:
|
556
|
+
elsif defined?(yield)
|
557
|
+
# RODA4: Remove
|
562
558
|
return unless captures = yield(*captures)
|
559
|
+
# :nocov:
|
563
560
|
end
|
561
|
+
|
564
562
|
@remaining_path = matchdata.post_match
|
565
|
-
|
563
|
+
|
564
|
+
if captures.is_a?(Array)
|
565
|
+
@captures.concat(captures)
|
566
|
+
else
|
567
|
+
@captures << captures
|
568
|
+
end
|
566
569
|
end
|
567
570
|
end
|
568
571
|
|
data/lib/roda/version.rb
CHANGED
data/lib/roda.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roda
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.86.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -167,6 +167,7 @@ files:
|
|
167
167
|
- lib/roda/plugins/_base64.rb
|
168
168
|
- lib/roda/plugins/_before_hook.rb
|
169
169
|
- lib/roda/plugins/_optimized_matching.rb
|
170
|
+
- lib/roda/plugins/_symbol_class_matchers.rb
|
170
171
|
- lib/roda/plugins/_symbol_regexp_matchers.rb
|
171
172
|
- lib/roda/plugins/additional_render_engines.rb
|
172
173
|
- lib/roda/plugins/additional_view_directories.rb
|
@@ -185,6 +186,7 @@ files:
|
|
185
186
|
- lib/roda/plugins/class_level_routing.rb
|
186
187
|
- lib/roda/plugins/class_matchers.rb
|
187
188
|
- lib/roda/plugins/common_logger.rb
|
189
|
+
- lib/roda/plugins/conditional_sessions.rb
|
188
190
|
- lib/roda/plugins/content_for.rb
|
189
191
|
- lib/roda/plugins/content_security_policy.rb
|
190
192
|
- lib/roda/plugins/cookie_flags.rb
|
@@ -325,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
325
327
|
- !ruby/object:Gem::Version
|
326
328
|
version: '0'
|
327
329
|
requirements: []
|
328
|
-
rubygems_version: 3.5.
|
330
|
+
rubygems_version: 3.5.22
|
329
331
|
signing_key:
|
330
332
|
specification_version: 4
|
331
333
|
summary: Routing tree web toolkit
|