roda 3.109.0 → 3.110.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/cache.rb +0 -2
- data/lib/roda/opts.rb +1 -1
- data/lib/roda/plugins/_symbol_class_matchers.rb +2 -4
- data/lib/roda/plugins/additional_view_directories.rb +2 -2
- data/lib/roda/plugins/assets.rb +173 -142
- data/lib/roda/plugins/assets_preloading.rb +26 -10
- data/lib/roda/plugins/backtracking_array.rb +2 -2
- data/lib/roda/plugins/branch_locals.rb +4 -4
- data/lib/roda/plugins/break.rb +2 -2
- data/lib/roda/plugins/caching.rb +27 -17
- data/lib/roda/plugins/capture_erb.rb +15 -12
- data/lib/roda/plugins/chunked.rb +32 -30
- data/lib/roda/plugins/class_level_routing.rb +5 -2
- data/lib/roda/plugins/class_matchers.rb +11 -6
- data/lib/roda/plugins/common_logger.rb +21 -3
- data/lib/roda/plugins/conditional_sessions.rb +3 -3
- data/lib/roda/plugins/content_for.rb +7 -6
- data/lib/roda/plugins/content_security_policy.rb +11 -12
- data/lib/roda/plugins/cookie_flags.rb +12 -6
- data/lib/roda/plugins/cookies.rb +7 -5
- data/lib/roda/plugins/csrf.rb +2 -1
- data/lib/roda/plugins/custom_block_results.rb +6 -7
- data/lib/roda/plugins/default_headers.rb +2 -2
- data/lib/roda/plugins/delay_build.rb +1 -1
- data/lib/roda/plugins/delegate.rb +7 -6
- data/lib/roda/plugins/direct_call.rb +5 -1
- data/lib/roda/plugins/disallow_file_uploads.rb +2 -3
- data/lib/roda/plugins/drop_body.rb +3 -6
- data/lib/roda/plugins/each_part.rb +4 -6
- data/lib/roda/plugins/early_hints.rb +4 -4
- data/lib/roda/plugins/empty_root.rb +5 -5
- data/lib/roda/plugins/environments.rb +8 -8
- data/lib/roda/plugins/erb_h.rb +4 -4
- data/lib/roda/plugins/error_email.rb +16 -17
- data/lib/roda/plugins/error_handler.rb +24 -32
- data/lib/roda/plugins/error_mail.rb +26 -15
- data/lib/roda/plugins/exception_page.rb +41 -35
- data/lib/roda/plugins/filter_common_logger.rb +6 -4
- data/lib/roda/plugins/flash.rb +16 -13
- data/lib/roda/plugins/freeze_request_response.rb +24 -0
- data/lib/roda/plugins/hash_routes.rb +1 -1
- data/lib/roda/plugins/permissions_policy.rb +14 -16
- data/lib/roda/plugins/public.rb +11 -0
- data/lib/roda/plugins/recheck_precompiled_assets.rb +30 -8
- data/lib/roda/plugins/render.rb +3 -0
- data/lib/roda/plugins/render_locals.rb +1 -1
- data/lib/roda/plugins/sessions.rb +158 -137
- data/lib/roda/plugins/symbol_matchers.rb +8 -5
- data/lib/roda/plugins/unescape_path.rb +22 -4
- data/lib/roda/plugins.rb +24 -0
- data/lib/roda/request.rb +8 -7
- data/lib/roda/response.rb +9 -7
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +12 -9
- metadata +2 -1
|
@@ -3,26 +3,19 @@
|
|
|
3
3
|
#
|
|
4
4
|
class Roda
|
|
5
5
|
module RodaPlugins
|
|
6
|
-
# The error_handler plugin adds an error handler to the routing,
|
|
6
|
+
# The error_handler plugin adds an error handler to the routing tree,
|
|
7
7
|
# so that if routing the request raises an error, a nice error
|
|
8
|
-
# message page can be returned to the user.
|
|
8
|
+
# message page (or API response) can be returned to the user.
|
|
9
9
|
#
|
|
10
|
-
# You
|
|
10
|
+
# You should provide the error handler as a block to the plugin:
|
|
11
11
|
#
|
|
12
12
|
# plugin :error_handler do |e|
|
|
13
13
|
# "Oh No!"
|
|
14
14
|
# end
|
|
15
15
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# error do |e|
|
|
21
|
-
# "Oh No!"
|
|
22
|
-
# end
|
|
23
|
-
#
|
|
24
|
-
# In both cases, the exception instance is passed into the block,
|
|
25
|
-
# and the block can return the request body via a string.
|
|
16
|
+
# If an error occurs, the exception instance is passed into the block,
|
|
17
|
+
# and the block can return the request body via a string (or another
|
|
18
|
+
# configured block result).
|
|
26
19
|
#
|
|
27
20
|
# If an exception is raised, a new response will be used, with the
|
|
28
21
|
# default status set to 500, before executing the error handler.
|
|
@@ -32,40 +25,40 @@ class Roda
|
|
|
32
25
|
# processing of that response occurs, except that an exception during
|
|
33
26
|
# after processing is logged to <tt>env['rack.errors']</tt> but
|
|
34
27
|
# otherwise ignored. This avoids recursive calls into the
|
|
35
|
-
#
|
|
28
|
+
# error handler. Note that if the error handler itself raises
|
|
36
29
|
# an exception, the exception will be raised without normal after
|
|
37
30
|
# processing. This can cause some after processing to run twice
|
|
38
|
-
# (once before the
|
|
31
|
+
# (once before the error handler is called and once after) if
|
|
39
32
|
# later after processing raises an exception.
|
|
40
33
|
#
|
|
41
34
|
# By default, this plugin handles StandardError and ScriptError.
|
|
42
|
-
# To override the exception classes it will handle, pass a
|
|
43
|
-
#
|
|
35
|
+
# To override the exception classes it will handle, pass a +classes+
|
|
36
|
+
# argument to the plugin:
|
|
44
37
|
#
|
|
45
38
|
# plugin :error_handler, classes: [StandardError, ScriptError, NoMemoryError]
|
|
46
39
|
module ErrorHandler
|
|
47
40
|
DEFAULT_ERROR_HANDLER_CLASSES = [StandardError, ScriptError].freeze
|
|
48
41
|
|
|
49
|
-
# If a block is given,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
app.opts[:error_handler_classes] = (
|
|
42
|
+
# If a block is given, use it to setup the error handler.
|
|
43
|
+
def self.configure(app, opts=(no_positional_opts = true; OPTS), classes: opts[:classes], **kw, &block)
|
|
44
|
+
RodaPlugins.check_keywords("the error_handler plugin", no_positional_opts, opts, kw)
|
|
45
|
+
app.opts[:error_handler_classes] = (classes || app.error_handler_classes || DEFAULT_ERROR_HANDLER_CLASSES).dup.freeze
|
|
53
46
|
|
|
54
47
|
if block
|
|
55
|
-
app.
|
|
48
|
+
app.class_exec do
|
|
49
|
+
define_method(:handle_error, &block)
|
|
50
|
+
alias_method(:handle_error, :handle_error)
|
|
51
|
+
private :handle_error
|
|
52
|
+
end
|
|
56
53
|
end
|
|
57
54
|
end
|
|
58
55
|
|
|
59
56
|
module ClassMethods
|
|
60
57
|
RodaPlugins.opt_attr_reader(self, :error_handler_classes)
|
|
61
58
|
|
|
62
|
-
# Install the given block as the error handler, so that if routing
|
|
63
|
-
# the request raises an exception, the block will be called with
|
|
64
|
-
# the exception in the scope of the Roda instance.
|
|
65
59
|
def error(&block)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
private :handle_error
|
|
60
|
+
RodaPlugins.warn("The error class method is deprecated, switch to passing the block when loading the error_handler plugin", uplevel: 1)
|
|
61
|
+
plugin(:error_handler, &block)
|
|
69
62
|
end
|
|
70
63
|
end
|
|
71
64
|
|
|
@@ -98,7 +91,7 @@ class Roda
|
|
|
98
91
|
private
|
|
99
92
|
|
|
100
93
|
# Default empty implementation of _roda_after, usually
|
|
101
|
-
# overridden by Roda.
|
|
94
|
+
# overridden by Roda.def_roda_after.
|
|
102
95
|
def _roda_after(res)
|
|
103
96
|
end
|
|
104
97
|
|
|
@@ -121,9 +114,8 @@ class Roda
|
|
|
121
114
|
res
|
|
122
115
|
end
|
|
123
116
|
|
|
124
|
-
# By default, have the error handler reraise the error, so
|
|
125
|
-
# the plugin without
|
|
126
|
-
# behavior.
|
|
117
|
+
# By default, have the error handler reraise the error, so loading
|
|
118
|
+
# the plugin without a block doesn't change behavior.
|
|
127
119
|
def handle_error(e)
|
|
128
120
|
raise e
|
|
129
121
|
end
|
|
@@ -5,8 +5,8 @@ require 'mail'
|
|
|
5
5
|
class Roda
|
|
6
6
|
module RodaPlugins
|
|
7
7
|
# The error_mail plugin adds an +error_mail+ instance method that
|
|
8
|
-
# send an email related to the exception. This is
|
|
9
|
-
#
|
|
8
|
+
# send an email related to the exception. This is designed to be used
|
|
9
|
+
# with the error_handler plugin:
|
|
10
10
|
#
|
|
11
11
|
# plugin :error_mail, to: 'to@example.com', from: 'from@example.com'
|
|
12
12
|
# plugin :error_handler do |e|
|
|
@@ -21,13 +21,13 @@ class Roda
|
|
|
21
21
|
#
|
|
22
22
|
# Options:
|
|
23
23
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
24
|
+
# +:filter+ :: Callable called with the key and value for each parameter, environment
|
|
25
|
+
# variable, and session value. If it returns true, the value of the
|
|
26
|
+
# parameter is filtered in the email.
|
|
27
|
+
# +:from+ :: The From address to use in the email (required)
|
|
28
|
+
# +:headers+ :: A hash of additional headers to use in the email (default: empty hash)
|
|
29
|
+
# +:prefix+ :: A prefix to use in the email's subject line (default: no prefix)
|
|
30
|
+
# +:to+ :: The To address to use in the email (required)
|
|
31
31
|
#
|
|
32
32
|
# The subject of the error email shows the exception class and message.
|
|
33
33
|
# The body of the error email shows the backtrace of the error and the
|
|
@@ -42,16 +42,27 @@ class Roda
|
|
|
42
42
|
DEFAULT_FILTER = lambda{|k,v| false}
|
|
43
43
|
private_constant :DEFAULT_FILTER
|
|
44
44
|
|
|
45
|
+
PLUGIN_ARGUMENTS = %i[to from headers prefix filter].freeze
|
|
46
|
+
private_constant :PLUGIN_ARGUMENTS
|
|
47
|
+
|
|
48
|
+
Config = Struct.new(*PLUGIN_ARGUMENTS)
|
|
49
|
+
private_constant :Config
|
|
50
|
+
|
|
45
51
|
# Set default opts for plugin. See ErrorEmail module RDoc for options.
|
|
46
52
|
def self.configure(app, opts=OPTS)
|
|
53
|
+
RodaPlugins.check_supported_opts("the error_mail plugin", opts, PLUGIN_ARGUMENTS)
|
|
54
|
+
|
|
47
55
|
app.opts[:error_mail] = email_opts = (app.error_mail_opts || {:filter=>DEFAULT_FILTER}).merge(opts).freeze
|
|
48
56
|
unless email_opts[:to] && email_opts[:from]
|
|
49
57
|
raise RodaError, "must provide :to and :from options to error_mail plugin"
|
|
50
58
|
end
|
|
59
|
+
|
|
60
|
+
app.opts[:error_mail_config] = Config.new(*email_opts.values_at(*Config.members)).freeze
|
|
51
61
|
end
|
|
52
62
|
|
|
53
63
|
module ClassMethods
|
|
54
64
|
RodaPlugins.opt_attr_reader(self, :error_mail, name: :error_mail_opts)
|
|
65
|
+
RodaPlugins.opt_attr_reader(self, :error_mail_config)
|
|
55
66
|
end
|
|
56
67
|
|
|
57
68
|
module InstanceMethods
|
|
@@ -71,14 +82,14 @@ class Roda
|
|
|
71
82
|
private
|
|
72
83
|
|
|
73
84
|
def _error_mail(e)
|
|
74
|
-
|
|
85
|
+
config = self.class.error_mail_config
|
|
75
86
|
subject = if e.respond_to?(:message)
|
|
76
87
|
"#{e.class}: #{e.message}"
|
|
77
88
|
else
|
|
78
89
|
e.to_s
|
|
79
90
|
end
|
|
80
|
-
subject = "#{
|
|
81
|
-
filter =
|
|
91
|
+
subject = "#{config.prefix}#{subject}"
|
|
92
|
+
filter = config.filter
|
|
82
93
|
|
|
83
94
|
format = lambda do |h|
|
|
84
95
|
h = h.map{|k, v| "#{k.inspect} => #{filter.call(k, v) ? 'FILTERED' : v.inspect}"}
|
|
@@ -133,12 +144,12 @@ END
|
|
|
133
144
|
|
|
134
145
|
|
|
135
146
|
Mail.new do
|
|
136
|
-
from
|
|
137
|
-
to
|
|
147
|
+
from config.from
|
|
148
|
+
to config.to
|
|
138
149
|
subject subject
|
|
139
150
|
body message
|
|
140
151
|
|
|
141
|
-
if headers =
|
|
152
|
+
if headers = config.headers
|
|
142
153
|
headers.each do |k,v|
|
|
143
154
|
header[k] = v
|
|
144
155
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
class Roda
|
|
5
5
|
module RodaPlugins
|
|
6
|
-
# The exception_page plugin provides an exception_page method that is designed
|
|
6
|
+
# The exception_page plugin provides an +exception_page+ method that is designed
|
|
7
7
|
# to be called inside the error handler to provide a page to the developer
|
|
8
8
|
# with debugging information. It should only be used in developer environments
|
|
9
9
|
# with trusted clients, as it can leak source code and other information that
|
|
@@ -21,29 +21,29 @@ class Roda
|
|
|
21
21
|
# differences:
|
|
22
22
|
#
|
|
23
23
|
# * Not a middleware, so it doesn't handle exceptions itself, and has no effect
|
|
24
|
-
# on the callstack unless the exception_page method is called.
|
|
24
|
+
# on the callstack unless the +exception_page+ method is called.
|
|
25
25
|
# * Supports external javascript and stylesheets, allowing context toggling to
|
|
26
26
|
# work in applications that use a content security policy to restrict inline
|
|
27
|
-
# javascript and stylesheets (
|
|
28
|
-
# * Has fewer dependencies (does not require
|
|
27
|
+
# javascript and stylesheets (+assets+, +css_file+, and +js_file+ arguments).
|
|
28
|
+
# * Has fewer dependencies (does not require erb).
|
|
29
29
|
# * Sets the Content-Type for the response, and returns the body string, but does
|
|
30
30
|
# not modify other headers or the response status.
|
|
31
|
-
# * Supports a configurable amount of context lines in backtraces (
|
|
32
|
-
# * Supports optional JSON formatted output, if used with the json plugin (
|
|
31
|
+
# * Supports a configurable amount of context lines in backtraces (+context+ argument).
|
|
32
|
+
# * Supports optional JSON formatted output, if used with the json plugin (+json+ argument).
|
|
33
33
|
#
|
|
34
|
-
# To
|
|
35
|
-
# in your routing tree:
|
|
34
|
+
# To handle requests for the external javascript and stylesheets, you can call
|
|
35
|
+
# +r.exception_page_assets+ in your routing tree:
|
|
36
36
|
#
|
|
37
37
|
# route do |r|
|
|
38
38
|
# # ...
|
|
39
39
|
#
|
|
40
40
|
# # serve GET /exception_page.{css,js} requests
|
|
41
|
-
# # Use with assets: true
|
|
41
|
+
# # Use with exception_page assets: true argument
|
|
42
42
|
# r.exception_page_assets
|
|
43
43
|
#
|
|
44
44
|
# r.on "static" do
|
|
45
45
|
# # serve GET /static/exception_page.{css,js} requests
|
|
46
|
-
# # Use with assets: '/static'
|
|
46
|
+
# # Use with exception_page assets: '/static' option
|
|
47
47
|
# r.exception_page_assets
|
|
48
48
|
# end
|
|
49
49
|
# end
|
|
@@ -175,29 +175,37 @@ END
|
|
|
175
175
|
# is not accepted, then just show a plain text page with the exception
|
|
176
176
|
# class, message, and backtrace.
|
|
177
177
|
#
|
|
178
|
-
#
|
|
178
|
+
# Arguments:
|
|
179
179
|
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
def exception_page(exception, opts=OPTS)
|
|
180
|
+
# +assets+ :: If +true+, sets :css_file to +/exception_page.css+ and :js_file to
|
|
181
|
+
# +/exception_page.js+, assuming that +r.exception_page_assets+ is called
|
|
182
|
+
# in the route block to serve the exception page assets. If a String,
|
|
183
|
+
# uses the string as a prefix, assuming that +r.exception_page_assets+
|
|
184
|
+
# is called in a nested block inside the route block. If false, doesn't
|
|
185
|
+
# use any CSS or JS.
|
|
186
|
+
# +context+ :: The number of context lines before and after each line in
|
|
187
|
+
# the backtrace (default: 7).
|
|
188
|
+
# +css_file+ :: A path to the external CSS file for the HTML exception page. If false,
|
|
189
|
+
# doesn't use any CSS.
|
|
190
|
+
# +js_file+ :: A path to the external javascript file for the HTML exception page. If
|
|
191
|
+
# false, doesn't use any JS.
|
|
192
|
+
# +json+ :: Return a hash of exception information. The hash will have
|
|
193
|
+
# a single key, <tt>"exception"</tt>, with a value being a hash with
|
|
194
|
+
# three keys, <tt>"class"</tt>, <tt>"message"</tt>, and <tt>"backtrace"</tt>, which
|
|
195
|
+
# contain information derived from the given exception.
|
|
196
|
+
# Designed to be used with the +json+ plugin, which will
|
|
197
|
+
# automatically convert the hash to JSON format.
|
|
198
|
+
def exception_page(exception, opts=(no_positional_opts = true; OPTS),
|
|
199
|
+
assets: opts[:assets],
|
|
200
|
+
context: opts[:context],
|
|
201
|
+
css_file: opts[:css_file],
|
|
202
|
+
js_file: opts[:js_file],
|
|
203
|
+
json: opts[:json],
|
|
204
|
+
**kw)
|
|
205
|
+
RodaPlugins.check_keywords(__callee__, no_positional_opts, opts, kw)
|
|
206
|
+
|
|
199
207
|
message = exception_page_exception_message(exception)
|
|
200
|
-
if
|
|
208
|
+
if json
|
|
201
209
|
@_response[RodaResponseHeaders::CONTENT_TYPE] = "application/json"
|
|
202
210
|
{
|
|
203
211
|
"exception"=>{
|
|
@@ -209,11 +217,9 @@ END
|
|
|
209
217
|
elsif env['HTTP_ACCEPT'] =~ /text\/html/
|
|
210
218
|
@_response[RodaResponseHeaders::CONTENT_TYPE] = "text/html"
|
|
211
219
|
|
|
212
|
-
context
|
|
213
|
-
css_file = opts[:css_file]
|
|
214
|
-
js_file = opts[:js_file]
|
|
220
|
+
context ||= 7
|
|
215
221
|
|
|
216
|
-
case prefix =
|
|
222
|
+
case prefix = assets
|
|
217
223
|
when false
|
|
218
224
|
css_file = false if css_file.nil?
|
|
219
225
|
js_file = false if js_file.nil?
|
|
@@ -26,15 +26,17 @@ class Roda
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def self.configure(app, &block)
|
|
29
|
-
app.
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
app.class_exec do
|
|
30
|
+
define_method(:_common_log_request?, &block)
|
|
31
|
+
private :_common_log_request?
|
|
32
|
+
alias _common_log_request? _common_log_request?
|
|
33
|
+
end
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
module InstanceMethods
|
|
35
37
|
private
|
|
36
38
|
|
|
37
|
-
#
|
|
39
|
+
# Do common logging only if configured to do so.
|
|
38
40
|
def _roda_after_90__common_logger(result)
|
|
39
41
|
super if result && _common_log_request?(result)
|
|
40
42
|
end
|
data/lib/roda/plugins/flash.rb
CHANGED
|
@@ -4,14 +4,12 @@ require 'delegate'
|
|
|
4
4
|
|
|
5
5
|
class Roda
|
|
6
6
|
module RodaPlugins
|
|
7
|
-
# The flash plugin adds a +flash+ instance method
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# request.
|
|
7
|
+
# The flash plugin adds a +flash+ instance method, for typical web
|
|
8
|
+
# application flash handling, where values set in the current flash
|
|
9
|
+
# hash are available in the next request loading the flash.
|
|
11
10
|
#
|
|
12
|
-
# With the example below, if a POST request is submitted,
|
|
13
|
-
#
|
|
14
|
-
# return 'b'.
|
|
11
|
+
# With the example below, if a +POST+ request is submitted, it will
|
|
12
|
+
# redirect and the resulting +GET+ request will return <tt>'b'</tt>.
|
|
15
13
|
#
|
|
16
14
|
# plugin :flash
|
|
17
15
|
#
|
|
@@ -23,7 +21,7 @@ class Roda
|
|
|
23
21
|
#
|
|
24
22
|
# r.post do
|
|
25
23
|
# flash['a'] = 'b'
|
|
26
|
-
# r.redirect
|
|
24
|
+
# r.redirect
|
|
27
25
|
# end
|
|
28
26
|
# end
|
|
29
27
|
# end
|
|
@@ -35,11 +33,16 @@ class Roda
|
|
|
35
33
|
# flash.now['a'] = 'b'
|
|
36
34
|
# flash['a'] # = >'b'
|
|
37
35
|
# end
|
|
36
|
+
#
|
|
37
|
+
# The flash is saved in the session, so it has the same restrictions
|
|
38
|
+
# as other data saved in the session. If the session does not support
|
|
39
|
+
# symbols (e.g. when using the sessions plugin), you should not use
|
|
40
|
+
# symbols as flash keys.
|
|
38
41
|
module Flash
|
|
39
42
|
SCOPE_INSTANCE_VARIABLES = [:@_flash].freeze
|
|
40
43
|
|
|
41
|
-
#
|
|
42
|
-
#
|
|
44
|
+
# Hash-like flash, where assigning to the object updates the data
|
|
45
|
+
# accessed in the following request.
|
|
43
46
|
class FlashHash < DelegateClass(Hash)
|
|
44
47
|
# The flash hash for the next request. This
|
|
45
48
|
# is what gets written to by #[]=.
|
|
@@ -48,8 +51,8 @@ class Roda
|
|
|
48
51
|
# The flash hash for the current request
|
|
49
52
|
alias now __getobj__
|
|
50
53
|
|
|
51
|
-
# Setup the next hash when initializing, and
|
|
52
|
-
# as a new empty hash.
|
|
54
|
+
# Setup the next hash when initializing, and treat nil
|
|
55
|
+
# argument as a new empty hash.
|
|
53
56
|
def initialize(hash={})
|
|
54
57
|
super(hash||{})
|
|
55
58
|
@next = {}
|
|
@@ -93,7 +96,7 @@ class Roda
|
|
|
93
96
|
# Access the flash hash for the current request, loading
|
|
94
97
|
# it from the session if it is not already loaded.
|
|
95
98
|
def flash
|
|
96
|
-
# :
|
|
99
|
+
# RODA4: Remove fallback to checking :_flash
|
|
97
100
|
@_flash ||= FlashHash.new(session['_flash'] || (session['_flash'] = session.delete(:_flash)))
|
|
98
101
|
end
|
|
99
102
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The freeze_request_response plugin freezes the RodaRequest and
|
|
7
|
+
# RodaResponse classes when freezing the Roda class. This will
|
|
8
|
+
# become the default behavior in Roda 4.
|
|
9
|
+
module FreezeRequestResponse
|
|
10
|
+
module ClassMethods
|
|
11
|
+
# Freeze the RodaRequest and RodaResponse classes when freezing the Roda class.
|
|
12
|
+
def freeze
|
|
13
|
+
super
|
|
14
|
+
# RODA4: make this behavior the default
|
|
15
|
+
self::RodaRequest.freeze
|
|
16
|
+
self::RodaResponse.freeze
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
register_plugin(:freeze_request_response, FreezeRequestResponse)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -241,7 +241,7 @@ class Roda
|
|
|
241
241
|
end
|
|
242
242
|
end
|
|
243
243
|
|
|
244
|
-
[
|
|
244
|
+
%i[get post delete head options link patch put trace unlink query].each do |meth|
|
|
245
245
|
define_method(meth) do |path, &block|
|
|
246
246
|
verb(meth, path, &block)
|
|
247
247
|
end
|
|
@@ -34,13 +34,13 @@ class Roda
|
|
|
34
34
|
# # ...
|
|
35
35
|
# end
|
|
36
36
|
#
|
|
37
|
-
# You can use the
|
|
37
|
+
# You can use the +:default+ plugin argument to set the default for all settings.
|
|
38
38
|
# For example, to disallow all access for each setting by default:
|
|
39
39
|
#
|
|
40
40
|
# plugin :permissions_policy, default: :none
|
|
41
41
|
#
|
|
42
42
|
# The following methods are available for configuring the permissions policy,
|
|
43
|
-
# which specify the setting (substituting _ with
|
|
43
|
+
# which specify the setting (substituting +_+ with +-+):
|
|
44
44
|
#
|
|
45
45
|
# * accelerometer
|
|
46
46
|
# * ambient_light_sensor
|
|
@@ -73,12 +73,12 @@ class Roda
|
|
|
73
73
|
# All of these methods support any number of arguments, and each argument should
|
|
74
74
|
# be one of the following values:
|
|
75
75
|
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
76
|
+
# +:all+ :: Grants permission to all domains (must be only argument)
|
|
77
|
+
# +:none+ :: Does not allow permission at all (must be only argument)
|
|
78
|
+
# +:self+ :: Allows feature in current document and any nested browsing contexts
|
|
79
|
+
# that use the same domain as the current document.
|
|
80
|
+
# +:src+ :: Allows feature in current document and any nested browsing contexts
|
|
81
|
+
# that use the same domain as the src of the iframe.
|
|
82
82
|
# String :: Specifies origin domain where access is allowed
|
|
83
83
|
#
|
|
84
84
|
# When calling a method with no arguments, the setting is removed from the policy instead
|
|
@@ -88,7 +88,7 @@ class Roda
|
|
|
88
88
|
# Calling the method overrides any previous setting. Each of the methods has +add_*+ and
|
|
89
89
|
# +get_*+ methods defined. The +add_*+ method appends to any existing setting, and the +get_*+ method
|
|
90
90
|
# returns the current value for the setting (this will be +:all+ if all domains are allowed, or
|
|
91
|
-
# any array of strings
|
|
91
|
+
# any array of strings/+:self+/+:src+).
|
|
92
92
|
#
|
|
93
93
|
# permissions_policy.fullscreen :self, 'https://example.com'
|
|
94
94
|
# # fullscreen (self "https://example.com")
|
|
@@ -99,7 +99,7 @@ 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. Empty policies
|
|
102
|
+
# The +clear+ method can be used to remove all settings from the policy. Empty policies
|
|
103
103
|
# do not set any headers. You can use +response.skip_permissions_policy!+ to skip
|
|
104
104
|
# setting a policy. This is faster than calling +permissions_policy.clear+, since
|
|
105
105
|
# it does not duplicate the default policy.
|
|
@@ -175,16 +175,13 @@ class Roda
|
|
|
175
175
|
end
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
-
def initialize
|
|
179
|
-
clear
|
|
180
|
-
end
|
|
181
|
-
|
|
182
178
|
# Clear all settings, useful to remove any inherited settings.
|
|
183
179
|
def clear
|
|
184
180
|
@opts = {}
|
|
185
181
|
@report_only = nil
|
|
186
182
|
@header_value = nil
|
|
187
183
|
end
|
|
184
|
+
alias initialize clear
|
|
188
185
|
|
|
189
186
|
# Do not allow future modifications to any settings.
|
|
190
187
|
def freeze
|
|
@@ -279,14 +276,15 @@ class Roda
|
|
|
279
276
|
end
|
|
280
277
|
|
|
281
278
|
# Yield the current Permissions Policy to the block.
|
|
282
|
-
def self.configure(app, opts=OPTS)
|
|
279
|
+
def self.configure(app, opts=(no_positional_opts = true; OPTS), default: opts[:default], **kw)
|
|
280
|
+
RodaPlugins.check_keywords("the permissions_policy plugin", no_positional_opts, opts, kw)
|
|
283
281
|
policy = app.opts[:permissions_policy] = if policy = app.permissions_policy
|
|
284
282
|
policy.dup
|
|
285
283
|
else
|
|
286
284
|
Policy.new
|
|
287
285
|
end
|
|
288
286
|
|
|
289
|
-
if default
|
|
287
|
+
if default
|
|
290
288
|
SUPPORTED_SETTINGS.each do |setting|
|
|
291
289
|
policy.send(setting.tr('-', '_'), *default)
|
|
292
290
|
end
|
data/lib/roda/plugins/public.rb
CHANGED
|
@@ -140,10 +140,20 @@ class Roda
|
|
|
140
140
|
s, h, b = public_serve(server, path)
|
|
141
141
|
headers = response.headers
|
|
142
142
|
headers.replace(h)
|
|
143
|
+
public_add_vary(headers)
|
|
143
144
|
halt [s, headers, b]
|
|
144
145
|
end
|
|
145
146
|
end
|
|
146
147
|
|
|
148
|
+
# When encodings are used, the response depends on the Accept-Encoding
|
|
149
|
+
# request header, so add it to the Vary header for caches.
|
|
150
|
+
def public_add_vary(headers)
|
|
151
|
+
unless roda_class.public_encodings.empty?
|
|
152
|
+
headers[RodaResponseHeaders::VARY] = (vary = headers[RodaResponseHeaders::VARY]) ? "#{vary}, Accept-Encoding" : 'Accept-Encoding'
|
|
153
|
+
end
|
|
154
|
+
nil
|
|
155
|
+
end
|
|
156
|
+
|
|
147
157
|
# Serve the compressed file if it exists. This should only
|
|
148
158
|
# be called if the client will accept the related encoding.
|
|
149
159
|
def public_serve_compressed(server, path, suffix, encoding)
|
|
@@ -158,6 +168,7 @@ class Roda
|
|
|
158
168
|
headers[RodaResponseHeaders::CONTENT_TYPE] = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
|
159
169
|
headers[RodaResponseHeaders::CONTENT_ENCODING] = encoding
|
|
160
170
|
end
|
|
171
|
+
public_add_vary(headers)
|
|
161
172
|
|
|
162
173
|
halt [s, headers, b]
|
|
163
174
|
end
|
|
@@ -48,27 +48,37 @@ class Roda
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def self.load_dependencies(app)
|
|
51
|
-
unless app.respond_to?(:
|
|
51
|
+
unless app.respond_to?(:assets_config) && app.assets_config.precompiled
|
|
52
52
|
raise RodaError, "must load assets plugin with precompiled option before loading recheck_precompiled_assets plugin"
|
|
53
53
|
end
|
|
54
|
+
# Set mutex before overriding a method that uses it
|
|
55
|
+
app::RodaRequest.instance_variable_set(:@_precompiled_assets_mutex, Mutex.new)
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def self.configure(app)
|
|
57
|
-
precompiled_file = app.
|
|
59
|
+
precompiled_file = app.assets_config.precompiled
|
|
58
60
|
prev_mtime = ::File.mtime(precompiled_file)
|
|
61
|
+
|
|
62
|
+
request_class = app::RodaRequest
|
|
63
|
+
mutex = request_class.instance_variable_get(:@_precompiled_assets_mutex)
|
|
64
|
+
# Unset the assets matchers, in case a frozen value is already set
|
|
65
|
+
request_class.instance_variable_set(:@assets_matchers, nil)
|
|
66
|
+
# Ensure the assets matchers is set and an unfrozen array, and get the underlying value
|
|
67
|
+
request_class.assets_matchers
|
|
68
|
+
assets_matchers = request_class.instance_variable_get(:@assets_matchers)
|
|
69
|
+
|
|
59
70
|
app.instance_exec do
|
|
60
|
-
|
|
71
|
+
_update_compiled_assets(_compiled_assets_initial_hash.replace(assets_config.compiled))
|
|
61
72
|
|
|
62
73
|
define_singleton_method(:recheck_precompiled_assets) do
|
|
63
74
|
new_mtime = ::File.mtime(precompiled_file)
|
|
64
75
|
if new_mtime != prev_mtime
|
|
65
76
|
prev_mtime = new_mtime
|
|
66
|
-
|
|
77
|
+
assets_config.compiled.replace(_precompiled_asset_metadata(precompiled_file))
|
|
67
78
|
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
app::RodaRequest.instance_variable_set(:@assets_matchers, nil)
|
|
79
|
+
# Replace the asset matchers, in case the new precompiled metadata uses different files.
|
|
80
|
+
new_assets_matchers = request_class.send(:_build_assets_matchers)
|
|
81
|
+
mutex.synchronize{assets_matchers.replace(new_assets_matchers)}
|
|
72
82
|
end
|
|
73
83
|
end
|
|
74
84
|
singleton_class.send(:alias_method, :recheck_precompiled_assets, :recheck_precompiled_assets)
|
|
@@ -91,8 +101,20 @@ class Roda
|
|
|
91
101
|
end
|
|
92
102
|
|
|
93
103
|
module RequestClassMethods
|
|
104
|
+
# Synchronize access and return a dup, so that replacements of the
|
|
105
|
+
# assets matchers at runtime doesn't cause thread safety issues.
|
|
106
|
+
def assets_matchers
|
|
107
|
+
@_precompiled_assets_mutex.synchronize{super.dup}
|
|
108
|
+
end
|
|
109
|
+
|
|
94
110
|
private
|
|
95
111
|
|
|
112
|
+
# Use a unfrozen array for the assets matchers, so they can be replaced
|
|
113
|
+
# at runtime.
|
|
114
|
+
def _build_assets_matchers
|
|
115
|
+
super.dup
|
|
116
|
+
end
|
|
117
|
+
|
|
96
118
|
# Use a regexp that matches any digest. When the precompiled asset metadata
|
|
97
119
|
# file is updated, this allows requests for a previous precompiled asset to
|
|
98
120
|
# still work.
|
data/lib/roda/plugins/render.rb
CHANGED
|
@@ -583,6 +583,9 @@ class Roda
|
|
|
583
583
|
opts = subclass.opts[:render] = subclass.render_opts.dup
|
|
584
584
|
if COMPILED_METHOD_SUPPORT
|
|
585
585
|
opts[:template_method_cache] = (opts[:cache_class] || RodaCache).new
|
|
586
|
+
compiled_templates_module = Module.new
|
|
587
|
+
subclass.send(:include, compiled_templates_module)
|
|
588
|
+
subclass.const_set(:RodaCompiledTemplates, compiled_templates_module)
|
|
586
589
|
end
|
|
587
590
|
opts[:cache] = opts[:cache].dup
|
|
588
591
|
opts.freeze
|