roda 3.107.0 → 3.108.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/roda/plugins/_base64.rb +2 -2
- data/lib/roda/plugins/_optimized_matching.rb +1 -0
- data/lib/roda/plugins/all_verbs.rb +2 -2
- data/lib/roda/plugins/assets.rb +4 -4
- data/lib/roda/plugins/bearer_token.rb +2 -2
- data/lib/roda/plugins/break.rb +9 -1
- data/lib/roda/plugins/caching.rb +12 -12
- data/lib/roda/plugins/common_logger.rb +4 -4
- data/lib/roda/plugins/cookie_flags.rb +2 -2
- data/lib/roda/plugins/each_part.rb +4 -4
- data/lib/roda/plugins/error_email.rb +2 -2
- data/lib/roda/plugins/exception_page.rb +2 -2
- data/lib/roda/plugins/h.rb +4 -4
- data/lib/roda/plugins/hash_branches.rb +8 -4
- data/lib/roda/plugins/hash_paths.rb +4 -2
- data/lib/roda/plugins/hash_public.rb +2 -2
- data/lib/roda/plugins/hmac_paths.rb +27 -29
- data/lib/roda/plugins/host_routing.rb +29 -4
- data/lib/roda/plugins/invalid_request_body.rb +2 -0
- data/lib/roda/plugins/json_parser.rb +20 -2
- data/lib/roda/plugins/mail_processor.rb +2 -2
- data/lib/roda/plugins/mailer.rb +4 -4
- data/lib/roda/plugins/optimized_segment_matchers.rb +13 -6
- data/lib/roda/plugins/optimized_string_matchers.rb +6 -3
- data/lib/roda/plugins/params_capturing.rb +16 -7
- data/lib/roda/plugins/params_capturing_restore.rb +72 -0
- data/lib/roda/plugins/part.rb +4 -4
- data/lib/roda/plugins/pass.rb +4 -1
- data/lib/roda/plugins/path.rb +6 -6
- data/lib/roda/plugins/path_matchers.rb +5 -1
- data/lib/roda/plugins/public.rb +4 -4
- data/lib/roda/plugins/render.rb +24 -12
- data/lib/roda/plugins/render_coverage.rb +7 -6
- data/lib/roda/plugins/render_each.rb +2 -2
- data/lib/roda/plugins/route_csrf.rb +35 -3
- data/lib/roda/plugins/run_append_slash.rb +8 -4
- data/lib/roda/plugins/sec_fetch_site_csrf.rb +5 -2
- data/lib/roda/plugins/sessions.rb +2 -2
- data/lib/roda/plugins/shape_friendly.rb +4 -4
- data/lib/roda/plugins/static.rb +3 -0
- data/lib/roda/plugins/type_routing.rb +1 -1
- data/lib/roda/plugins/url_escape.rb +2 -2
- data/lib/roda/plugins/view_options.rb +3 -5
- data/lib/roda/plugins.rb +4 -4
- data/lib/roda/request.rb +25 -9
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +7 -7
- metadata +2 -1
|
@@ -54,14 +54,20 @@ class Roda
|
|
|
54
54
|
# Also note that this plugin will not work correctly if you are using
|
|
55
55
|
# the symbol_matchers plugin with custom symbol matching and are using
|
|
56
56
|
# symbols that capture multiple values or no values.
|
|
57
|
+
#
|
|
58
|
+
# If you are using the pass or break plugin along with this plugin,
|
|
59
|
+
# it is recommended you load the params_capturing_restore plugin, as that
|
|
60
|
+
# allows +r.pass+ or +break+ inside param capturing blocking to work
|
|
61
|
+
# correctly.
|
|
57
62
|
module ParamsCapturing
|
|
58
63
|
REQUEST_INSTANCE_VARIABLES = [:@_params_captures].freeze
|
|
59
64
|
|
|
60
65
|
module RequestMethods
|
|
61
66
|
# Lazily initialize captures entry when params is called.
|
|
62
67
|
def params
|
|
68
|
+
return @params if @params
|
|
63
69
|
ret = super
|
|
64
|
-
ret['captures']
|
|
70
|
+
ret['captures'] = []
|
|
65
71
|
ret
|
|
66
72
|
end
|
|
67
73
|
|
|
@@ -100,16 +106,19 @@ class Roda
|
|
|
100
106
|
end
|
|
101
107
|
|
|
102
108
|
super do |*a|
|
|
103
|
-
if pc
|
|
104
|
-
@_params_captures = nil
|
|
105
|
-
pc.zip(a).each do |k,v|
|
|
106
|
-
params[k] = v
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
+
_set_params_captures(a) if pc
|
|
109
110
|
params['captures'].concat(a)
|
|
110
111
|
yield(*a)
|
|
111
112
|
end
|
|
112
113
|
end
|
|
114
|
+
|
|
115
|
+
# Set the captures into the parameters.
|
|
116
|
+
def _set_params_captures(a)
|
|
117
|
+
@_params_captures.zip(a).each do |k,v|
|
|
118
|
+
params[k] = v
|
|
119
|
+
end
|
|
120
|
+
@_params_captures = nil
|
|
121
|
+
end
|
|
113
122
|
end
|
|
114
123
|
end
|
|
115
124
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The params_capturing_restore plugin allows the params_capturing plugin
|
|
7
|
+
# to work with the use of +r.pass+ (pass plugin) or +break+ (break plugin)
|
|
8
|
+
# inside a block that uses param capturing, restoring the previous state of
|
|
9
|
+
# the params.
|
|
10
|
+
#
|
|
11
|
+
# This is not done by the params_capturing plugin as it requires significant
|
|
12
|
+
# extra work for every param capturing block, and most users of the
|
|
13
|
+
# params_capturing plugin are not using the pass or break plugins.
|
|
14
|
+
module ParamsCapturingRestore
|
|
15
|
+
REQUEST_INSTANCE_VARIABLES = [:@_params_captures_restore].freeze
|
|
16
|
+
|
|
17
|
+
ABSENT = Object.new.freeze
|
|
18
|
+
private_constant :ABSENT
|
|
19
|
+
|
|
20
|
+
def self.load_dependencies(app)
|
|
21
|
+
app.plugin :params_capturing
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module RequestMethods
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Restore previous state of the params before capturing when
|
|
28
|
+
# the block exits.
|
|
29
|
+
def if_match(args)
|
|
30
|
+
params = self.params
|
|
31
|
+
num_captures = params['captures'].length
|
|
32
|
+
old_params = nil
|
|
33
|
+
|
|
34
|
+
super do |*a|
|
|
35
|
+
old_params = @_params_captures_restore
|
|
36
|
+
@_params_captures_restore = nil
|
|
37
|
+
yield(*a)
|
|
38
|
+
end
|
|
39
|
+
ensure
|
|
40
|
+
if old_params
|
|
41
|
+
old_params.each_slice(2) do |k, v|
|
|
42
|
+
if v == ABSENT
|
|
43
|
+
params.delete(k)
|
|
44
|
+
else
|
|
45
|
+
params[k] = v
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
captures = params['captures']
|
|
51
|
+
if num_captures != captures.length
|
|
52
|
+
captures.slice!(num_captures, 1000000)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Record the original value of the params before calling super
|
|
57
|
+
# to update the params.
|
|
58
|
+
def _set_params_captures(a)
|
|
59
|
+
restore = @_params_captures_restore = []
|
|
60
|
+
@_params_captures.zip(a).each do |k,v|
|
|
61
|
+
restore << k << params.fetch(k, ABSENT)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
super
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
register_plugin(:params_capturing_restore, ParamsCapturingRestore)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
data/lib/roda/plugins/part.rb
CHANGED
|
@@ -44,19 +44,19 @@ class Roda
|
|
|
44
44
|
render(template, :locals=>locals, &block)
|
|
45
45
|
end
|
|
46
46
|
RUBY
|
|
47
|
-
# :
|
|
47
|
+
# simplecov:disable
|
|
48
48
|
else
|
|
49
49
|
def part(template, locals=OPTS, &block)
|
|
50
50
|
render(template, :locals=>locals, &block)
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
|
-
# :
|
|
53
|
+
# simplecov:enable
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
module AssumeFixedLocalsInstanceMethods
|
|
57
|
-
# :
|
|
57
|
+
# simplecov:disable
|
|
58
58
|
if RUBY_VERSION >= '3.0'
|
|
59
|
-
# :
|
|
59
|
+
# simplecov:enable
|
|
60
60
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
|
61
61
|
def part(template, ...)
|
|
62
62
|
if optimized_method = _optimized_render_method_for_locals(template, OPTS)
|
data/lib/roda/plugins/pass.rb
CHANGED
data/lib/roda/plugins/path.rb
CHANGED
|
@@ -169,17 +169,17 @@ class Roda
|
|
|
169
169
|
# Allow calling private _method to get path
|
|
170
170
|
relative_path(request.script_name.to_s + send(_meth, *a, &blk))
|
|
171
171
|
end
|
|
172
|
-
# :
|
|
172
|
+
# simplecov:disable
|
|
173
173
|
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
|
174
|
-
# :
|
|
174
|
+
# simplecov:enable
|
|
175
175
|
elsif add_script_name
|
|
176
176
|
define_method(meth) do |*a, &blk|
|
|
177
177
|
# Allow calling private _method to get path
|
|
178
178
|
request.script_name.to_s + send(_meth, *a, &blk)
|
|
179
179
|
end
|
|
180
|
-
# :
|
|
180
|
+
# simplecov:disable
|
|
181
181
|
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
|
182
|
-
# :
|
|
182
|
+
# simplecov:enable
|
|
183
183
|
else
|
|
184
184
|
define_method(meth, &block)
|
|
185
185
|
end
|
|
@@ -198,9 +198,9 @@ class Roda
|
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
define_method(url_meth, &url_block)
|
|
201
|
-
# :
|
|
201
|
+
# simplecov:disable
|
|
202
202
|
ruby2_keywords(url_meth) if respond_to?(:ruby2_keywords, true)
|
|
203
|
-
# :
|
|
203
|
+
# simplecov:enable
|
|
204
204
|
end
|
|
205
205
|
|
|
206
206
|
nil
|
|
@@ -29,12 +29,16 @@ class Roda
|
|
|
29
29
|
# # Matches '/foo.bar', yielding 'foo'
|
|
30
30
|
# # Does not match bar.foo
|
|
31
31
|
# end
|
|
32
|
+
#
|
|
33
|
+
# Note that all of these matchers treat a given string as regexp source,
|
|
34
|
+
# so if you are including regexp metacharacters but want a literal string
|
|
35
|
+
# match, use +Regexp.escape+.
|
|
32
36
|
module PathMatchers
|
|
33
37
|
module RequestMethods
|
|
34
38
|
# Match when the current segment ends with the given extension.
|
|
35
39
|
# request path end with the extension.
|
|
36
40
|
def match_extension(ext)
|
|
37
|
-
match_suffix("
|
|
41
|
+
match_suffix("\\.#{ext}")
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
# Match when the current path segment starts with the given prefix.
|
data/lib/roda/plugins/public.rb
CHANGED
|
@@ -46,10 +46,10 @@ class Roda
|
|
|
46
46
|
ENCODING_MAP = {:zstd=>'zstd', :brotli=>'br', :gzip=>'gzip'}.freeze
|
|
47
47
|
ENCODING_EXTENSIONS = {'br'=>'.br', 'gzip'=>'.gz', 'zstd'=>'.zst'}.freeze
|
|
48
48
|
|
|
49
|
-
# :
|
|
49
|
+
# simplecov:disable
|
|
50
50
|
PARSER = defined?(::URI::RFC2396_PARSER) ? ::URI::RFC2396_PARSER : ::URI::DEFAULT_PARSER
|
|
51
51
|
MATCH_METHOD = RUBY_VERSION >= '2.4' ? :match? : :match
|
|
52
|
-
# :
|
|
52
|
+
# simplecov:enable
|
|
53
53
|
|
|
54
54
|
# Use options given to setup a Rack::File instance for serving files. Options:
|
|
55
55
|
# :brotli :: Whether to serve already brotli-compressed files with a .br extension
|
|
@@ -114,9 +114,9 @@ class Roda
|
|
|
114
114
|
def public_file_readable?(path)
|
|
115
115
|
::File.file?(path) && ::File.readable?(path)
|
|
116
116
|
rescue SystemCallError
|
|
117
|
-
# :
|
|
117
|
+
# simplecov:disable
|
|
118
118
|
false
|
|
119
|
-
# :
|
|
119
|
+
# simplecov:enable
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
def public_serve_with(server)
|
data/lib/roda/plugins/render.rb
CHANGED
|
@@ -46,8 +46,11 @@ class Roda
|
|
|
46
46
|
#
|
|
47
47
|
# The following plugin options are supported:
|
|
48
48
|
#
|
|
49
|
-
# :allowed_paths :: Set the template
|
|
50
|
-
# of these
|
|
49
|
+
# :allowed_paths :: Set the template directories to allow. Attempts to render paths outside
|
|
50
|
+
# of these directories will raise an error. Defaults to the +:views+ option,
|
|
51
|
+
# if it is a directory. For backwards compatibility, if a non-directory is
|
|
52
|
+
# given, it will be used as a prefix match. Roda 4 will raise if a non-directory
|
|
53
|
+
# is given as an allowed path and +:check_paths+ is true.
|
|
51
54
|
# :assume_fixed_locals :: Set if you are sure all templates in your application use fixed locals
|
|
52
55
|
# to allow for additional optimization. This is ignored unless both
|
|
53
56
|
# compiled methods and fixed locals are not supported.
|
|
@@ -298,24 +301,24 @@ class Roda
|
|
|
298
301
|
def self.tilt_template_fixed_locals?(template)
|
|
299
302
|
template.fixed_locals?
|
|
300
303
|
end
|
|
301
|
-
# :
|
|
304
|
+
# simplecov:disable
|
|
302
305
|
else
|
|
303
306
|
def self.tilt_template_fixed_locals?(template)
|
|
304
307
|
false
|
|
305
308
|
end
|
|
306
309
|
end
|
|
307
|
-
# :
|
|
310
|
+
# simplecov:enable
|
|
308
311
|
|
|
309
312
|
if compiled_method_arity == -2
|
|
310
313
|
def self.tilt_template_compiled_method(template, locals_keys, scope_class)
|
|
311
314
|
template.send(:compiled_method, locals_keys, scope_class)
|
|
312
315
|
end
|
|
313
|
-
# :
|
|
316
|
+
# simplecov:disable
|
|
314
317
|
else
|
|
315
318
|
def self.tilt_template_compiled_method(template, locals_keys, scope_class)
|
|
316
319
|
template.send(:compiled_method, locals_keys)
|
|
317
320
|
end
|
|
318
|
-
# :
|
|
321
|
+
# simplecov:enable
|
|
319
322
|
end
|
|
320
323
|
|
|
321
324
|
# Setup default rendering options. See Render for details.
|
|
@@ -331,9 +334,18 @@ class Roda
|
|
|
331
334
|
opts = app.opts[:render]
|
|
332
335
|
opts[:engine] = (opts[:engine] || "erb").dup.freeze
|
|
333
336
|
opts[:views] = app.expand_path(opts[:views]||"views").freeze
|
|
334
|
-
opts[:allowed_paths] ||= [opts[:views]].freeze
|
|
335
|
-
opts[:allowed_paths] = opts[:allowed_paths].map{|f| app.expand_path(f, nil)}.uniq.freeze
|
|
336
337
|
opts[:check_paths] = true unless opts.has_key?(:check_paths)
|
|
338
|
+
opts[:allowed_paths] ||= [opts[:views]]
|
|
339
|
+
opts[:allowed_paths] = opts[:allowed_paths].map do |f|
|
|
340
|
+
path = app.expand_path(f, nil)
|
|
341
|
+
if File.directory?(path) || f == opts[:views]
|
|
342
|
+
path << "/"
|
|
343
|
+
elsif opts[:check_paths]
|
|
344
|
+
# RODA4: raise
|
|
345
|
+
RodaPlugins.warn("allowed path #{f.inspect} is not a directory in the file system. For backwards compatibility, will allow prefix matches, but this could result in security issues. Roda 4 will raise if an allowed path is not a directory.")
|
|
346
|
+
end
|
|
347
|
+
path.freeze
|
|
348
|
+
end.uniq.freeze
|
|
337
349
|
opts[:assume_fixed_locals] &&= FIXED_LOCALS_COMPILED_METHOD_SUPPORT
|
|
338
350
|
|
|
339
351
|
unless opts.has_key?(:check_template_mtime)
|
|
@@ -763,7 +775,7 @@ class Roda
|
|
|
763
775
|
end
|
|
764
776
|
end
|
|
765
777
|
RUBY
|
|
766
|
-
# :
|
|
778
|
+
# simplecov:disable
|
|
767
779
|
elsif RUBY_VERSION >= '2'
|
|
768
780
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
|
769
781
|
def _call_optimized_template_method((meth, fixed_locals), locals, &block)
|
|
@@ -787,7 +799,7 @@ class Roda
|
|
|
787
799
|
send(meth, locals, &block)
|
|
788
800
|
end
|
|
789
801
|
end
|
|
790
|
-
# :
|
|
802
|
+
# simplecov:enable
|
|
791
803
|
else
|
|
792
804
|
def _cached_template_method(_)
|
|
793
805
|
nil
|
|
@@ -981,9 +993,9 @@ class Roda
|
|
|
981
993
|
end
|
|
982
994
|
|
|
983
995
|
module AssumeFixedLocalsInstanceMethods
|
|
984
|
-
# :
|
|
996
|
+
# simplecov:disable
|
|
985
997
|
if RUBY_VERSION >= '3.0'
|
|
986
|
-
# :
|
|
998
|
+
# simplecov:enable
|
|
987
999
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
|
988
1000
|
def _call_optimized_template_method((meth,_), locals, &block)
|
|
989
1001
|
send(meth, **locals, &block)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
|
|
3
3
|
require 'tilt'
|
|
4
|
-
# :
|
|
4
|
+
# simplecov:disable
|
|
5
5
|
raise 'Tilt version does not support coverable templates' unless Tilt::Template.method_defined?(:compiled_path=)
|
|
6
|
-
# :
|
|
6
|
+
# simplecov:enable
|
|
7
7
|
|
|
8
8
|
#
|
|
9
9
|
class Roda
|
|
@@ -55,8 +55,9 @@ class Roda
|
|
|
55
55
|
path = File.expand_path(opts[:path])
|
|
56
56
|
compiled_path = nil
|
|
57
57
|
(self.opts[:render_coverage_strip_paths] || render_opts[:allowed_paths]).each do |dir|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
dir += "/" unless dir.end_with?("/")
|
|
59
|
+
if path.start_with?(dir)
|
|
60
|
+
compiled_path = File.join(self.opts[:render_coverage_dir], path[dir.length, 10000000].tr('/', '-'))
|
|
60
61
|
break
|
|
61
62
|
end
|
|
62
63
|
end
|
|
@@ -76,9 +77,9 @@ class Roda
|
|
|
76
77
|
template = super
|
|
77
78
|
|
|
78
79
|
# Set compiled path for template when using older tilt versions.
|
|
79
|
-
# :
|
|
80
|
+
# simplecov:disable
|
|
80
81
|
template.compiled_path = compiled_path if compiled_path
|
|
81
|
-
# :
|
|
82
|
+
# simplecov:enable
|
|
82
83
|
|
|
83
84
|
template
|
|
84
85
|
end
|
|
@@ -171,12 +171,12 @@ class Roda
|
|
|
171
171
|
|
|
172
172
|
File.basename(template).sub(/\..+\z/, '').to_sym
|
|
173
173
|
end
|
|
174
|
-
# :
|
|
174
|
+
# simplecov:disable
|
|
175
175
|
else
|
|
176
176
|
def render_each_default_local(template)
|
|
177
177
|
File.basename(template.to_s).sub(/\..+\z/, '').to_sym
|
|
178
178
|
end
|
|
179
|
-
# :
|
|
179
|
+
# simplecov:enable
|
|
180
180
|
end
|
|
181
181
|
|
|
182
182
|
if Render::COMPILED_METHOD_SUPPORT
|
|
@@ -57,13 +57,18 @@ class Roda
|
|
|
57
57
|
# :csrf_failure :: The action to taken if a request fails the CSRF check (default: :raise). Options:
|
|
58
58
|
# :raise :: raise a Roda::RodaPlugins::RouteCsrf::InvalidToken exception
|
|
59
59
|
# :empty_403 :: return a blank 403 page (rack_csrf's default behavior)
|
|
60
|
-
# :clear_session :: Clear the current session
|
|
60
|
+
# :clear_session :: Clear the current session (deprecated)
|
|
61
61
|
# Proc :: Treated as a routing block, called with request object
|
|
62
62
|
# :check_header :: Whether the HTTP header should be checked for the token value (default: false).
|
|
63
63
|
# If true, checks the HTTP header after checking for the form input parameter.
|
|
64
64
|
# If :only, only checks the HTTP header and doesn't check the form input parameter.
|
|
65
65
|
# :check_request_methods :: Which request methods require CSRF protection
|
|
66
66
|
# (default: <tt>['POST', 'DELETE', 'PATCH', 'PUT']</tt>)
|
|
67
|
+
# :exempt_request_methods :: If given, specifies which request methods do not require CSRF protection.
|
|
68
|
+
# It is recommended to set this so an allow list approach is used, as
|
|
69
|
+
# :check_request_methods is the equivalent of a deny list
|
|
70
|
+
# (recommended: <tt>['GET', 'HEAD', 'OPTIONS', 'QUERY']</tt>, which
|
|
71
|
+
# will be the default in Roda 4)
|
|
67
72
|
# :upgrade_from_rack_csrf_key :: If provided, the session key that should be checked for the
|
|
68
73
|
# rack_csrf raw token. If the session key is present, the value
|
|
69
74
|
# will be checked against the submitted token, and if it matches,
|
|
@@ -155,6 +160,19 @@ class Roda
|
|
|
155
160
|
#
|
|
156
161
|
# By including random data in the HMAC for all tokens, different tokens are generated
|
|
157
162
|
# each time, mitigating compression ratio attacks such as BREACH.
|
|
163
|
+
#
|
|
164
|
+
# == REQUEST_METHOD modifications
|
|
165
|
+
#
|
|
166
|
+
# It's possible to use Rack middleware that modify REQUEST_METHOD in the env hash,
|
|
167
|
+
# such that it does not reflect that actual request method. One example of this is
|
|
168
|
+
# Rack::MethodOverride. Using such middleware is generally bad from a security perspective,
|
|
169
|
+
# but if you want to do so anyway, you need to override the request method that the
|
|
170
|
+
# route_csrf plugin will use so it reflects the actual request method. If you are using
|
|
171
|
+
# Rack::MethodOverride, you can use:
|
|
172
|
+
#
|
|
173
|
+
# def csrf_request_method
|
|
174
|
+
# env['rack.methodoverride.original_method'] || super
|
|
175
|
+
# end
|
|
158
176
|
module RouteCsrf
|
|
159
177
|
# Default CSRF option values
|
|
160
178
|
DEFAULTS = {
|
|
@@ -165,6 +183,7 @@ class Roda
|
|
|
165
183
|
:require_request_specific_tokens => true,
|
|
166
184
|
:csrf_failure => :raise,
|
|
167
185
|
:check_header => false,
|
|
186
|
+
# RODA4: Remove and set :exempt_request_methods by default
|
|
168
187
|
:check_request_methods => %w'POST DELETE PATCH PUT'.freeze.each(&:freeze)
|
|
169
188
|
}.freeze
|
|
170
189
|
|
|
@@ -186,6 +205,9 @@ class Roda
|
|
|
186
205
|
options[:csrf_failure] = :csrf_failure_method
|
|
187
206
|
app.define_roda_method(:_roda_route_csrf_failure, 1, &app.send(:convert_route_block, block))
|
|
188
207
|
end
|
|
208
|
+
if opts[:csrf_failure] == :clear_session
|
|
209
|
+
RodaPlugins.warn "Passing :clear_session as the :csrf_failure option to the route_csrf plugin is deprecated"
|
|
210
|
+
end
|
|
189
211
|
options[:env_header] = "HTTP_#{options[:header].to_s.tr('-', '_').upcase}".freeze
|
|
190
212
|
options.freeze
|
|
191
213
|
end
|
|
@@ -212,6 +234,7 @@ class Roda
|
|
|
212
234
|
headers[RodaResponseHeaders::CONTENT_LENGTH] ='0'
|
|
213
235
|
throw :halt, @_response.finish_with_body([])
|
|
214
236
|
when :clear_session
|
|
237
|
+
RodaPlugins.warn "Passing :clear_session as the :csrf_failure option to check_csrf! is deprecated"
|
|
215
238
|
session.clear
|
|
216
239
|
when :csrf_failure_method
|
|
217
240
|
@_request.on{_roda_route_csrf_failure(@_request)}
|
|
@@ -297,13 +320,22 @@ class Roda
|
|
|
297
320
|
|
|
298
321
|
private
|
|
299
322
|
|
|
323
|
+
# The request method to use to determine whether to perform a CSRF check.
|
|
324
|
+
# defaults to REQUEST_METHOD in the environment, can be overridden if
|
|
325
|
+
# that isn't the actual request method.
|
|
326
|
+
def csrf_request_method
|
|
327
|
+
@_request.env['REQUEST_METHOD']
|
|
328
|
+
end
|
|
329
|
+
|
|
300
330
|
# Returns error message string if the CSRF token is not valid.
|
|
301
331
|
# Returns nil if the CSRF token is valid.
|
|
302
332
|
def csrf_invalid_message(opts)
|
|
303
333
|
opts = opts.empty? ? csrf_options : csrf_options.merge(opts)
|
|
304
|
-
method =
|
|
334
|
+
method = csrf_request_method
|
|
305
335
|
|
|
306
|
-
|
|
336
|
+
if exempt = opts[:exempt_request_methods]
|
|
337
|
+
return if exempt.include?(method)
|
|
338
|
+
elsif !opts[:check_request_methods].include?(method)
|
|
307
339
|
return
|
|
308
340
|
end
|
|
309
341
|
|
|
@@ -22,8 +22,11 @@ class Roda
|
|
|
22
22
|
# # GET /a/ => App gets "/" as PATH_INFO
|
|
23
23
|
module RunAppendSlash
|
|
24
24
|
# Set plugin specific options. Options:
|
|
25
|
-
# :use_redirects :: Whether to issue
|
|
26
|
-
#
|
|
25
|
+
# :use_redirects :: Whether to issue a redirect to the path with the slash
|
|
26
|
+
# appended, instead of calling the app directly with the
|
|
27
|
+
# trailing slash. Note that if the current path starts
|
|
28
|
+
# with +//+, it will call the app directly instead of
|
|
29
|
+
# redirecting.
|
|
27
30
|
def self.configure(app, opts=OPTS)
|
|
28
31
|
app.opts[:run_append_slash_redirect] = !!opts[:use_redirects]
|
|
29
32
|
end
|
|
@@ -32,10 +35,11 @@ class Roda
|
|
|
32
35
|
# Calls the given rack app. If the path matches the root of the app but
|
|
33
36
|
# does not contain a trailing slash, a trailing slash is appended to the
|
|
34
37
|
# path internally, or a redirect is issued when configured with
|
|
35
|
-
# <tt>use_redirects: true</tt>.
|
|
38
|
+
# <tt>use_redirects: true</tt>. If the path starts with +//+,
|
|
39
|
+
# the redirect approach will not be used.
|
|
36
40
|
def run(*)
|
|
37
41
|
if @remaining_path.empty?
|
|
38
|
-
if scope.opts[:run_append_slash_redirect]
|
|
42
|
+
if scope.opts[:run_append_slash_redirect] && !(path = self.path).start_with?('//')
|
|
39
43
|
redirect("#{path}/")
|
|
40
44
|
else
|
|
41
45
|
@remaining_path += '/'
|
|
@@ -54,7 +54,7 @@ class Roda
|
|
|
54
54
|
# :raise :: raise a Roda::RodaPlugins::SecFetchSiteCsrf::CsrfFailure
|
|
55
55
|
# exception
|
|
56
56
|
# :empty_403 :: return a blank 403 page
|
|
57
|
-
# :clear_session :: clear the current session
|
|
57
|
+
# :clear_session :: clear the current session (deprecated)
|
|
58
58
|
#
|
|
59
59
|
# The plugin also supports a block, in which case failures will call the block
|
|
60
60
|
# as a routing block (the block should accept the request object).
|
|
@@ -83,8 +83,10 @@ class Roda
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
case options[:csrf_failure]
|
|
86
|
-
when :raise, :empty_403, :
|
|
86
|
+
when :raise, :empty_403, :method
|
|
87
87
|
# nothing
|
|
88
|
+
when :clear_session
|
|
89
|
+
RodaPlugins.warn "Passing :clear_session as the :csrf_failure option to the sec_fetch_site_csrf plugin is deprecated"
|
|
88
90
|
else
|
|
89
91
|
raise RodaError, "Unsupported :csrf_failure plugin option: #{options[:csrf_failure].inspect}"
|
|
90
92
|
end
|
|
@@ -118,6 +120,7 @@ class Roda
|
|
|
118
120
|
headers[RodaResponseHeaders::CONTENT_LENGTH] ='0'
|
|
119
121
|
throw :halt, @_response.finish_with_body([])
|
|
120
122
|
when :clear_session
|
|
123
|
+
# RODA4: Remove
|
|
121
124
|
session.clear
|
|
122
125
|
else # when :method
|
|
123
126
|
@_request.on{_roda_sec_fetch_site_csrf_failure(@_request)}
|
|
@@ -5,9 +5,9 @@ require 'openssl'
|
|
|
5
5
|
begin
|
|
6
6
|
OpenSSL::Cipher.new("aes-256-ctr")
|
|
7
7
|
rescue OpenSSL::Cipher::CipherError
|
|
8
|
-
# :
|
|
8
|
+
# simplecov:disable
|
|
9
9
|
raise LoadError, "Roda sessions plugin requires the aes-256-ctr cipher"
|
|
10
|
-
# :
|
|
10
|
+
# simplecov:enable
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
require 'json'
|
|
@@ -90,9 +90,9 @@ class Roda
|
|
|
90
90
|
end
|
|
91
91
|
nil
|
|
92
92
|
end
|
|
93
|
-
# :
|
|
93
|
+
# simplecov:disable
|
|
94
94
|
ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
|
|
95
|
-
# :
|
|
95
|
+
# simplecov:enable
|
|
96
96
|
|
|
97
97
|
private
|
|
98
98
|
|
|
@@ -132,9 +132,9 @@ class Roda
|
|
|
132
132
|
end
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
-
# :
|
|
135
|
+
# simplecov:disable
|
|
136
136
|
if RUBY_VERSION >= '4.0'
|
|
137
|
-
# :
|
|
137
|
+
# simplecov:enable
|
|
138
138
|
module InstanceMethods
|
|
139
139
|
private
|
|
140
140
|
|
data/lib/roda/plugins/static.rb
CHANGED
|
@@ -21,6 +21,9 @@ class Roda
|
|
|
21
21
|
# uses a separate option for it.
|
|
22
22
|
#
|
|
23
23
|
# Users of this plugin may want to consider using the public plugin instead.
|
|
24
|
+
# As this only loads a middleware, it does not integrate with other Roda
|
|
25
|
+
# security features, such as the content_security_policy and host_authorization
|
|
26
|
+
# plugins.
|
|
24
27
|
#
|
|
25
28
|
# Examples:
|
|
26
29
|
#
|
|
@@ -198,11 +198,11 @@ class Roda
|
|
|
198
198
|
# The response type indicated by the Accept request header.
|
|
199
199
|
def accept_response_type
|
|
200
200
|
mimes = @scope.opts[:type_routing][:mimes]
|
|
201
|
+
response[RodaResponseHeaders::VARY] = (vary = response[RodaResponseHeaders::VARY]) ? "#{vary}, Accept" : 'Accept'
|
|
201
202
|
|
|
202
203
|
@env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do |part|
|
|
203
204
|
mime, _= part.split(/\s*;\s*/, 2)
|
|
204
205
|
if sym = mimes[mime]
|
|
205
|
-
response[RodaResponseHeaders::VARY] = (vary = response[RodaResponseHeaders::VARY]) ? "#{vary}, Accept" : 'Accept'
|
|
206
206
|
return sym
|
|
207
207
|
end
|
|
208
208
|
end
|
|
@@ -19,7 +19,7 @@ class Roda
|
|
|
19
19
|
if RUBY_VERSION >= '2'
|
|
20
20
|
define_method(:url_escape, Rack::Utils.instance_method(:escape))
|
|
21
21
|
define_method(:url_unescape, Rack::Utils.instance_method(:unescape))
|
|
22
|
-
# :
|
|
22
|
+
# simplecov:disable
|
|
23
23
|
else
|
|
24
24
|
def url_escape(v)
|
|
25
25
|
Rack::Utils.escape(v)
|
|
@@ -29,7 +29,7 @@ class Roda
|
|
|
29
29
|
Rack::Utils.unescape(v)
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
|
-
# :
|
|
32
|
+
# simplecov:enable
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -158,13 +158,11 @@ class Roda
|
|
|
158
158
|
# template isn't a layout template, merge the options
|
|
159
159
|
# and locals into the returned hash.
|
|
160
160
|
def parse_template_opts(template, opts)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if !t_opts[:_is_layout] && (v_opts = @_view_options)
|
|
164
|
-
t_opts.merge!(v_opts)
|
|
161
|
+
if (v_opts = @_view_options) && (!template.is_a?(Hash) || !template[:_is_layout])
|
|
162
|
+
opts = v_opts.merge(opts)
|
|
165
163
|
end
|
|
166
164
|
|
|
167
|
-
|
|
165
|
+
super
|
|
168
166
|
end
|
|
169
167
|
|
|
170
168
|
# If layout options or locals have been set,
|
data/lib/roda/plugins.rb
CHANGED
|
@@ -43,11 +43,11 @@ class Roda
|
|
|
43
43
|
# Deprecate the constant with the given name in the given module,
|
|
44
44
|
# if the ruby version supports it.
|
|
45
45
|
def self.deprecate_constant(mod, name)
|
|
46
|
-
# :
|
|
46
|
+
# simplecov:disable
|
|
47
47
|
if RUBY_VERSION >= '2.3'
|
|
48
48
|
mod.deprecate_constant(name)
|
|
49
49
|
end
|
|
50
|
-
# :
|
|
50
|
+
# simplecov:enable
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
if RUBY_VERSION >= '3.3'
|
|
@@ -57,12 +57,12 @@ class Roda
|
|
|
57
57
|
mod.set_temporary_name(yield)
|
|
58
58
|
mod
|
|
59
59
|
end
|
|
60
|
-
# :
|
|
60
|
+
# simplecov:disable
|
|
61
61
|
else
|
|
62
62
|
def self.set_temp_name(mod)
|
|
63
63
|
mod
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
|
-
# :
|
|
66
|
+
# simplecov:enable
|
|
67
67
|
end
|
|
68
68
|
end
|