roda 3.106.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 +50 -6
- 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/csrf.rb +3 -0
- data/lib/roda/plugins/delay_build.rb +2 -0
- 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/middleware.rb +1 -0
- 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_block_args.rb +6 -0
- 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 +9 -4
- data/lib/roda/plugins/shape_friendly.rb +4 -4
- data/lib/roda/plugins/static.rb +14 -0
- data/lib/roda/plugins/type_routing.rb +1 -1
- data/lib/roda/plugins/url_escape.rb +39 -0
- data/lib/roda/plugins/view_options.rb +3 -5
- data/lib/roda/plugins.rb +4 -4
- data/lib/roda/request.rb +27 -10
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +17 -12
- metadata +4 -2
|
@@ -17,7 +17,19 @@ class Roda
|
|
|
17
17
|
module JsonParser
|
|
18
18
|
DEFAULT_ERROR_HANDLER = proc{|r| r.halt [400, {}, []]}
|
|
19
19
|
|
|
20
|
+
# simplecov:disable
|
|
21
|
+
MATCH_METHOD = RUBY_VERSION >= '2.4' ? :match? : :match
|
|
22
|
+
# simplecov:enable
|
|
23
|
+
private_constant :MATCH_METHOD
|
|
24
|
+
|
|
20
25
|
# Handle options for the json_parser plugin:
|
|
26
|
+
# :content_type_regexp :: A regexp used to determine if the request's
|
|
27
|
+
# content type is JSON. For backwards compatibility,
|
|
28
|
+
# uses the insecure /json/ if not provided. Warns
|
|
29
|
+
# if this option is not provided. The recommended
|
|
30
|
+
# value to use is /\Aapplication\/json\b/i or
|
|
31
|
+
# /\Aapplication\/(?:vnd\.api\+)?json\b/i. Roda 4
|
|
32
|
+
# will default to the latter.
|
|
21
33
|
# :error_handler :: A proc to call if an exception is raised when
|
|
22
34
|
# parsing a JSON request body. The proc is called
|
|
23
35
|
# with the request object, and should probably call
|
|
@@ -37,6 +49,13 @@ class Roda
|
|
|
37
49
|
app.opts[:json_parser_error_handler] = opts[:error_handler] || app.opts[:json_parser_error_handler] || DEFAULT_ERROR_HANDLER
|
|
38
50
|
app.opts[:json_parser_parser] = opts[:parser] || app.opts[:json_parser_parser] || app.opts[:json_parser] || JSON.method(:parse)
|
|
39
51
|
app.opts[:json_parser_include_request] = opts[:include_request] if opts.has_key?(:include_request)
|
|
52
|
+
app.opts[:json_parser_content_type_regexp] = opts[:content_type_regexp] || app.opts[:json_parser_content_type_regexp]
|
|
53
|
+
|
|
54
|
+
unless app.opts[:json_parser_content_type_regexp]
|
|
55
|
+
# RODA4: Switch to /\Aapplication\/(?:vnd\.api\+)?json\b/i by default
|
|
56
|
+
RodaPlugins.warn(':content_type_regexp option not provided to json_parser plugin, using /json/ for backwards compatibility, which may be insecure, using /\Aapplication\/json\b/i or /\Aapplication\/(?:vnd\.api\+)?json\b/i is recommended')
|
|
57
|
+
app.opts[:json_parser_content_type_regexp] = /json/
|
|
58
|
+
end
|
|
40
59
|
|
|
41
60
|
case opts[:wrap]
|
|
42
61
|
when :unless_hash, :always
|
|
@@ -57,7 +76,7 @@ class Roda
|
|
|
57
76
|
return post_params
|
|
58
77
|
end
|
|
59
78
|
|
|
60
|
-
unless (input = env["rack.input"]) && (content_type = self.content_type) &&
|
|
79
|
+
unless (input = env["rack.input"]) && (content_type = self.content_type) && roda_class.opts[:json_parser_content_type_regexp].send(MATCH_METHOD, content_type)
|
|
61
80
|
return super
|
|
62
81
|
end
|
|
63
82
|
|
|
@@ -84,7 +103,6 @@ class Roda
|
|
|
84
103
|
args << self if roda_class.opts[:json_parser_include_request]
|
|
85
104
|
roda_class.opts[:json_parser_parser].call(*args)
|
|
86
105
|
end
|
|
87
|
-
|
|
88
106
|
|
|
89
107
|
# Rack 3 dropped requirement that input be rewindable
|
|
90
108
|
if Rack.release >= '3'
|
|
@@ -510,11 +510,11 @@ class Roda
|
|
|
510
510
|
a1.casecmp?(a2)
|
|
511
511
|
end
|
|
512
512
|
else
|
|
513
|
-
# :
|
|
513
|
+
# simplecov:disable
|
|
514
514
|
def address_match?(a1, a2)
|
|
515
515
|
a1.downcase == a2.downcase
|
|
516
516
|
end
|
|
517
|
-
# :
|
|
517
|
+
# simplecov:enable
|
|
518
518
|
end
|
|
519
519
|
|
|
520
520
|
# Match if any of the given addresses match the given val, which
|
data/lib/roda/plugins/mailer.rb
CHANGED
|
@@ -153,9 +153,9 @@ class Roda
|
|
|
153
153
|
mail
|
|
154
154
|
end
|
|
155
155
|
end
|
|
156
|
-
# :
|
|
156
|
+
# simplecov:disable
|
|
157
157
|
ruby2_keywords(:mail) if respond_to?(:ruby2_keywords, true)
|
|
158
|
-
# :
|
|
158
|
+
# simplecov:enable
|
|
159
159
|
|
|
160
160
|
# Calls +mail+ with given arguments and immediately sends the resulting mail.
|
|
161
161
|
def sendmail(*args)
|
|
@@ -163,9 +163,9 @@ class Roda
|
|
|
163
163
|
m.deliver
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
|
-
# :
|
|
166
|
+
# simplecov:disable
|
|
167
167
|
ruby2_keywords(:sendmail) if respond_to?(:ruby2_keywords, true)
|
|
168
|
-
# :
|
|
168
|
+
# simplecov:enable
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
module RequestMethods
|
|
@@ -125,6 +125,7 @@ class Roda
|
|
|
125
125
|
app.opts[:middleware_configure] = block if block
|
|
126
126
|
app.opts[:middleware_handle_result] = opts[:handle_result]
|
|
127
127
|
app.opts[:middleware_forward_response_headers] = opts[:forward_response_headers]
|
|
128
|
+
# RODA4: Make next_if_not_found default to true
|
|
128
129
|
app.opts[:middleware_next_if_not_found] = opts[:next_if_not_found]
|
|
129
130
|
end
|
|
130
131
|
|
|
@@ -27,11 +27,16 @@ class Roda
|
|
|
27
27
|
rp = @remaining_path
|
|
28
28
|
if rp.getbyte(0) == 47
|
|
29
29
|
if last = rp.index('/', 1)
|
|
30
|
-
|
|
31
|
-
always
|
|
30
|
+
return false if last == 1
|
|
31
|
+
always do
|
|
32
|
+
@remaining_path = rp[last, rp.length]
|
|
33
|
+
yield rp[1, last-1]
|
|
34
|
+
end
|
|
32
35
|
elsif (len = rp.length) > 1
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
always do
|
|
37
|
+
@remaining_path = ""
|
|
38
|
+
yield rp[1, len]
|
|
39
|
+
end
|
|
35
40
|
end
|
|
36
41
|
end
|
|
37
42
|
end
|
|
@@ -41,8 +46,10 @@ class Roda
|
|
|
41
46
|
def is_segment
|
|
42
47
|
rp = @remaining_path
|
|
43
48
|
if rp.getbyte(0) == 47 && !rp.index('/', 1) && (len = rp.length) > 1
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
always do
|
|
50
|
+
@remaining_path = ""
|
|
51
|
+
yield rp[1, len]
|
|
52
|
+
end
|
|
46
53
|
end
|
|
47
54
|
end
|
|
48
55
|
end
|
|
@@ -28,7 +28,11 @@ class Roda
|
|
|
28
28
|
module RequestMethods
|
|
29
29
|
# Optimized version of +on+ that only supports a single string.
|
|
30
30
|
def on_branch(s)
|
|
31
|
-
|
|
31
|
+
rp = @remaining_path
|
|
32
|
+
if _match_string(s)
|
|
33
|
+
always{yield}
|
|
34
|
+
@remaining_path = rp
|
|
35
|
+
end
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
# Optimized version of +is+ that only supports a single string.
|
|
@@ -37,9 +41,8 @@ class Roda
|
|
|
37
41
|
if _match_string(s)
|
|
38
42
|
if @remaining_path.empty?
|
|
39
43
|
always{yield}
|
|
40
|
-
else
|
|
41
|
-
@remaining_path = rp
|
|
42
44
|
end
|
|
45
|
+
@remaining_path = rp
|
|
43
46
|
end
|
|
44
47
|
end
|
|
45
48
|
end
|
|
@@ -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
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
|
|
3
|
+
# RODA4: Remove plugin
|
|
4
|
+
|
|
3
5
|
#
|
|
4
6
|
class Roda
|
|
5
7
|
module RodaPlugins
|
|
@@ -22,6 +24,10 @@ class Roda
|
|
|
22
24
|
# end
|
|
23
25
|
# end
|
|
24
26
|
# end
|
|
27
|
+
#
|
|
28
|
+
# This plugin will be no longer ship with Roda starting in Roda 4. Users
|
|
29
|
+
# are encouraged to switch to defining local variables at the top of the
|
|
30
|
+
# route block instead of using this plugin.
|
|
25
31
|
module RouteBlockArgs
|
|
26
32
|
def self.configure(app, &block)
|
|
27
33
|
app.instance_exec do
|
|
@@ -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
|
|