Documentation for Roda (v3.78.0)
+Documentation for Roda (v3.79.0)
README (Introduction to Roda, start here if new)
@@ -62,6 +62,7 @@Plugins that Ship with Roda
Release Notes
A Modular, Scalable Ruby Framework
Currently at version 3.78.0
+Currently at version 3.79.0
class lib/roda/plugins/heartbeat.rb
Constants
The minor version of Roda
, updated for new feature releases of Roda
.
module lib/roda/plugins/heartbeat.rb
Classes and Modules
module +Roda::RodaPlugins::HmacPaths +
+ +The hmac_paths plugin allows protection of paths using an HMAC. This can be used to prevent users enumerating paths, since only paths with valid HMACs will be respected.
+ +To use the plugin, you must provide a secret
option. This sets the secret for the HMACs. Make sure to keep this value secret, as this plugin does not provide protection against users who know the secret value. The secret must be at least 32 bytes.
plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes' ++ +
To generate a valid HMAC path, you call the hmac_path
method:
hmac_path('/widget/1') +# => "/0c2feaefdfc80cc73da19b060c713d4193c57022815238c6657ce2d99b5925eb/0/widget/1" ++ +
The first segment in the returned path is the HMAC. The second segment is flags for the type of paths (see below), and the rest of the path is as given.
+ +To protect a path or any subsection in the routing tree, you wrap the related code in an r.hmac_path
block.
route do |r| + r.hmac_path do + r.get 'widget', Integer do |widget_id| + # ... + end + end +end ++ +
If first segment of the remaining path contains a valid HMAC for the rest of the path (considering the flags), then r.hmac_path
will match and yield to the block, and routing continues inside the block with the HMAC and flags segments removed.
In the above example, if you provide a user a link for widget with ID 1, there is no way for them to guess the valid path for the widget with ID 2, preventing a user from enumerating widgets, without relying on custom access control. Users can only access paths that have been generated by the application and provided to them, either directly or indirectly.
+ +In the above example, r.hmac_path
is used at the root of the routing tree. If you would like to call it below the root of the routing tree, it works correctly, but you must pass hmac_path
the :root
option specifying where r.hmac_paths
will be called from. Consider this example:
route do |r| + r.on 'widget' do + r.hmac_path do + r.get Integer do |widget_id| + # ... + end + end + end + + r.on 'foobar' do + r.hmac_path do + r.get Integer do |foobar_id| + # ... + end + end + end +end ++ +
For security reasons, the hmac_path plugin does not allow an HMAC path designed for widgets to be a valid match in the r.hmac_path
call inside the r.on 'foobar'
block, preventing users who have a valid HMAC for a widget from looking at the page for a foobar with the same ID. When generating HMAC paths where the matching r.hmac_path
call is not at the root of the routing tree, you must pass the :root
option:
hmac_path('/1', root: '/widget') +# => "/widget/daccafce3ce0df52e5ce774626779eaa7286085fcbde1e4681c74175ff0bbacd/0/1" + +hmac_path('/1', root: '/foobar') +# => "/foobar/c5fdaf482771d4f9f38cc13a1b2832929026a4ceb05e98ed6a0cd5a00bf180b7/0/1" ++ +
Note how the HMAC changes even though the path is the same.
+ +In addition to the :root
option, there are additional options that further constrain use of the generated paths.
The :method
option creates a path that can only be called with a certain request method:
hmac_path('/widget/1', method: :get) +# => "/d38c1e634ecf9a3c0ab9d0832555b035d91b35069efcbf2670b0dfefd4b62fdd/m/widget/1" ++ +
Note how this results in a different HMAC than the original hmac_path('/widget/1')
call. This sets the flags segment to m
, which means r.hmac_path
will consider the request mehod when checking the HMAC, and will only match if the provided request method is GET. This allows you to provide a user the ability to submit a GET request for the underlying path, without providing them the ability to submit a POST request for the underlying path, with no other access control.
The :params
option accepts a hash of params, converts it into a query string, and includes the query string in the returned path. It sets the flags segment to p
, which means r.hmac_path
will check for that exact query string. Requests with an empty query string or a different string will not match.
hmac_path('/widget/1', params: {foo: 'bar'}) +# => "/fe8d03f9572d5af6c2866295bd3c12c2ea11d290b1cbd016c3b68ee36a678139/p/widget/1?foo=bar" ++ +
For GET requests, which cannot have request bodies, that is sufficient to ensure that the submitted params are exactly as specified. However, POST requests can have request bodies, and request body params override query string params in r.params
. So if you are using this for POST requests (or other HTTP verbs that can have request bodies), use r.GET
instead of r.params
to specifically check query string parameters.
You can use :root
, :method
, and :params
at the same time:
hmac_path('/1', root: '/widget', method: :get, params: {foo: 'bar'}) +# => "/widget/9169af1b8f40c62a1c2bb15b1b377c65bda681b8efded0e613a4176387468c15/mp/1?foo=bar" ++ +
This gives you a path only valid for a GET request with a root of /widget
and a query string of foo=bar
.
To handle secret rotation, you can provide an :old_secret
option when loading the plugin.
plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes', + old_secret: 'previous-secret-value-with-at-least-32-bytes' ++ +
This will use :secret
for constructing new paths, but will respect paths generated by :old_secret
.
Classes and Modules
+ +Public Class methods
+# File lib/roda/plugins/hmac_paths.rb +132 def self.configure(app, opts=OPTS) +133 hmac_secret = opts[:secret] +134 unless hmac_secret.is_a?(String) && hmac_secret.bytesize >= 32 +135 raise RodaError, "hmac_paths plugin :secret option must be a string containing at least 32 bytes" +136 end +137 +138 if hmac_old_secret = opts[:old_secret] +139 unless hmac_old_secret.is_a?(String) && hmac_old_secret.bytesize >= 32 +140 raise RodaError, "hmac_paths plugin :old_secret option must be a string containing at least 32 bytes if present" +141 end +142 end +143 +144 app.opts[:hmac_paths_secret] = hmac_secret +145 app.opts[:hmac_paths_old_secret] = hmac_old_secret +146 end+
module +Roda::RodaPlugins::HmacPaths::InstanceMethods +
+ +Public Instance methods
+Return a path with an HMAC. Designed to be used with r.hmac_path, to make sure users can only request paths that they have been provided by the application (directly or indirectly). This can prevent users of a site from enumerating valid paths. The given path should be a string starting with /
. Options:
:method |
+ Limits the returned path to only be valid for the given request method. + |
:params |
+ Includes parameters in the query string of the returned path, and limits the returned path to only be valid for that exact query string. + |
:root |
+ Should be an empty string or string starting with |
# File lib/roda/plugins/hmac_paths.rb +161 def hmac_path(path, opts=OPTS) +162 unless path.is_a?(String) && path.getbyte(0) == 47 +163 raise RodaError, "path must be a string starting with /" +164 end +165 +166 root = opts[:root] || '' +167 unless root.is_a?(String) && ((root_byte = root.getbyte(0)) == 47 || root_byte == nil) +168 raise RodaError, "root must be empty string or string starting with /" +169 end +170 +171 flags = String.new +172 path = path.dup +173 +174 if method = opts[:method] +175 flags << 'm' +176 end +177 +178 if params = opts[:params] +179 flags << 'p' +180 path << '?' << Rack::Utils.build_query(params) +181 end +182 +183 flags << '0' if flags.empty? +184 +185 hmac_path = if method +186 "#{method.to_s.upcase}:/#{flags}#{path}" +187 else +188 "/#{flags}#{path}" +189 end +190 +191 "#{root}/#{hmac_path_hmac(root, hmac_path)}/#{flags}#{path}" +192 end+
The HMAC to use in hmac_path
, for the given root, path, and options.
# File lib/roda/plugins/hmac_paths.rb +195 def hmac_path_hmac(root, path, opts=OPTS) +196 OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, hmac_path_hmac_secret(root, opts), path) +197 end+
module +Roda::RodaPlugins::HmacPaths::RequestMethods +
+ +Public Instance methods
+Looks at the first segment of the remaining path, and if it contains a valid HMAC for the rest of the path considering the flags in the second segment and the given options, the block matches and is yielded to, and the result of the block is returned. Otherwise, the block does not matches and routing continues after the call.
+ +# File lib/roda/plugins/hmac_paths.rb +214 def hmac_path(opts=OPTS, &block) +215 orig_path = remaining_path +216 mpath = matched_path +217 +218 on String do |submitted_hmac| +219 rpath = remaining_path +220 +221 if submitted_hmac.bytesize == 64 +222 on String do |flags| +223 if flags.bytesize >= 1 +224 if flags.include?('m') +225 rpath = "#{env['REQUEST_METHOD'].to_s.upcase}:#{rpath}" +226 end +227 +228 if flags.include?('p') +229 rpath = "#{rpath}?#{env["QUERY_STRING"]}" +230 end +231 +232 if hmac_path_valid?(mpath, rpath, submitted_hmac) +233 always(&block) +234 end +235 end +236 +237 # Return from method without matching +238 @remaining_path = orig_path +239 return +240 end +241 end +242 +243 # Return from method without matching +244 @remaining_path = orig_path +245 return +246 end +247 end+
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -441 def create_template(opts, template_opts) -442 opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block]) -443 end+445 def create_template(opts, template_opts) +446 opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block]) +447 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -426 def freeze -427 begin -428 _freeze_layout_method -429 rescue -430 # This is only for optimization, if any errors occur, they can be ignored. -431 # One possibility for error is the app doesn't use a layout, but doesn't -432 # specifically set the :layout=>false plugin option. -433 nil -434 end -435 -436 super -437 end+430 def freeze +431 begin +432 _freeze_layout_method +433 rescue +434 # This is only for optimization, if any errors occur, they can be ignored. +435 # One possibility for error is the app doesn't use a layout, but doesn't +436 # specifically set the :layout=>false plugin option. +437 nil +438 end +439 +440 super +441 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -454 def inherited(subclass) -455 super -456 opts = subclass.opts[:render] = subclass.opts[:render].dup -457 if COMPILED_METHOD_SUPPORT -458 opts[:template_method_cache] = (opts[:cache_class] || RodaCache).new -459 end -460 opts[:cache] = opts[:cache].dup -461 opts.freeze -462 end+458 def inherited(subclass) +459 super +460 opts = subclass.opts[:render] = subclass.opts[:render].dup +461 if COMPILED_METHOD_SUPPORT +462 opts[:template_method_cache] = (opts[:cache_class] || RodaCache).new +463 end +464 opts[:cache] = opts[:cache].dup +465 opts.freeze +466 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -447 def inline_template_block(content) -448 Proc.new{content} -449 end+451 def inline_template_block(content) +452 Proc.new{content} +453 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -465 def render_opts -466 opts[:render] -467 end+469 def render_opts +470 opts[:render] +471 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -493 def render(template, opts = (no_opts = true; optimized_template = _cached_template_method(template); OPTS), &block) -494 if optimized_template -495 send(optimized_template, OPTS, &block) -496 elsif !no_opts && opts.length == 1 && (locals = opts[:locals]) && (optimized_template = _optimized_render_method_for_locals(template, locals)) -497 send(optimized_template, locals, &block) -498 else -499 opts = render_template_opts(template, opts) -500 retrieve_template(opts).render((opts[:scope]||self), (opts[:locals]||OPTS), &block) -501 end -502 end+497 def render(template, opts = (no_opts = true; optimized_template = _cached_template_method(template); OPTS), &block) +498 if optimized_template +499 send(optimized_template, OPTS, &block) +500 elsif !no_opts && opts.length == 1 && (locals = opts[:locals]) && (optimized_template = _optimized_render_method_for_locals(template, locals)) +501 send(optimized_template, locals, &block) +502 else +503 opts = render_template_opts(template, opts) +504 retrieve_template(opts).render((opts[:scope]||self), (opts[:locals]||OPTS), &block) +505 end +506 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -505 def render_opts -506 self.class.render_opts -507 end+509 def render_opts +510 self.class.render_opts +511 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -514 def view(template, opts = (content = _optimized_view_content(template) unless defined?(yield); OPTS), &block) -515 if content -516 # First, check if the optimized layout method has already been created, -517 # and use it if so. This way avoids the extra conditional and local variable -518 # assignments in the next section. -519 if layout_method = _layout_method -520 return send(layout_method, OPTS){content} -521 end -522 -523 # If we have an optimized template method but no optimized layout method, create the -524 # optimized layout method if possible and use it. If you can't create the optimized -525 # layout method, fall through to the slower approach. -526 if layout_template = self.class.opts[:render][:optimize_layout] -527 retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout) -528 if layout_method = _layout_method -529 return send(layout_method, OPTS){content} -530 end -531 end -532 else -533 opts = parse_template_opts(template, opts) -534 content = opts[:content] || render_template(opts, &block) -535 end -536 -537 if layout_opts = view_layout_opts(opts) -538 content = render_template(layout_opts){content} +518 def view(template, opts = (content = _optimized_view_content(template) unless defined?(yield); OPTS), &block) +519 if content +520 # First, check if the optimized layout method has already been created, +521 # and use it if so. This way avoids the extra conditional and local variable +522 # assignments in the next section. +523 if layout_method = _layout_method +524 return send(layout_method, OPTS){content} +525 end +526 +527 # If we have an optimized template method but no optimized layout method, create the +528 # optimized layout method if possible and use it. If you can't create the optimized +529 # layout method, fall through to the slower approach. +530 if layout_template = self.class.opts[:render][:optimize_layout] +531 retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout) +532 if layout_method = _layout_method +533 return send(layout_method, OPTS){content} +534 end +535 end +536 else +537 opts = parse_template_opts(template, opts) +538 content = opts[:content] || render_template(opts, &block) 539 end 540 -541 content -542 end+541 if layout_opts = view_layout_opts(opts) +542 content = render_template(layout_opts){content} +543 end +544 +545 content +546 end
Public Instance
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -393 def compiled_method(locals_keys=EMPTY_ARRAY, roda_class=nil) -394 Render.tilt_template_compiled_method(@template, locals_keys, roda_class) -395 end+396 def compiled_method(locals_keys=EMPTY_ARRAY, roda_class=nil) +397 Render.tilt_template_compiled_method(@template, locals_keys, roda_class) +398 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -400 def compiled_method_lambda(template, roda_class, method_name, locals_keys=EMPTY_ARRAY) -401 mod = roda_class::RodaCompiledTemplates -402 lambda do |locals, &block| -403 if template.modified? -404 mod.send(:define_method, method_name, Render.tilt_template_compiled_method(template, locals_keys, roda_class)) -405 mod.send(:private, method_name) -406 end -407 -408 send(method_name, locals, &block) -409 end -410 end+403 def compiled_method_lambda(roda_class, method_name, locals_keys=EMPTY_ARRAY) +404 mod = roda_class::RodaCompiledTemplates +405 template = self +406 lambda do |locals, &block| +407 template.if_modified do +408 mod.send(:define_method, method_name, Render.tilt_template_compiled_method(template, locals_keys, roda_class)) +409 mod.send(:private, method_name) +410 end +411 +412 send(method_name, locals, &block) +413 end +414 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -374 def define_compiled_method(roda_class, method_name, locals_keys=EMPTY_ARRAY) -375 mod = roda_class::RodaCompiledTemplates -376 internal_method_name = :"_#{method_name}" -377 begin -378 mod.send(:define_method, internal_method_name, send(:compiled_method, locals_keys, roda_class)) -379 rescue ::NotImplementedError -380 return false -381 end -382 -383 mod.send(:private, internal_method_name) -384 mod.send(:define_method, method_name, &compiled_method_lambda(self, roda_class, internal_method_name, locals_keys)) -385 mod.send(:private, method_name) -386 -387 method_name -388 end-
Public Instance methods
# File lib/roda/plugins/render.rb -355 def modified? -356 begin -357 mtime = template_last_modified -358 rescue -359 # ignore errors -360 else -361 if mtime != @mtime -362 @mtime = mtime -363 reset_template -364 return true -365 end -366 end -367 -368 false -369 end+
# File lib/roda/plugins/render.rb +360 def if_modified +361 begin +362 mtime = template_last_modified +363 rescue +364 # ignore errors +365 else +366 if mtime != @mtime +367 reset_template +368 yield +369 @mtime = mtime +370 end +371 end +372 end
Public Instance methods
# File lib/roda/plugins/render.rb 337 def render(*args, &block) -338 modified? -339 @template.render(*args, &block) -340 end+338 res = nil +339 modified = false +340 if_modified do +341 res = @template.render(*args, &block) +342 modified = true +343 end +344 modified ? res : @template.render(*args, &block) +345 end
Public Instance methods
[show source]# File lib/roda/plugins/render.rb -345 def template_last_modified -346 if deps = @dependencies -347 deps.map{|f| File.mtime(f)}.max -348 else -349 File.mtime(@path) -350 end -351 end+350 def template_last_modified +351 if deps = @dependencies +352 deps.map{|f| File.mtime(f)}.max +353 else +354 File.mtime(@path) +355 end +356 end
CHANGELOG
Last Update:
-2024-03-13 10:26:39 -0700
+2024-04-12 07:38:02 -0700
+3.79.0 (2024-04-12)¶ ↑
+-
+
Do not update template mtime when there is an error reloading templates in the render plugin (jeremyevans)
+ -
+
Add hmac_paths plugin for preventing path enumeration and supporting access control (jeremyevans)
+
+
3.78.0 (2024-03-13)¶ ↑
-
Add permissions_policy plugin for setting Permissions-Policy header (jeremyevans)
diff --git a/rdoc/files/doc/release_notes/3_79_0_txt.html b/rdoc/files/doc/release_notes/3_79_0_txt.html
new file mode 100644
index 00000000..f7ddbeb6
--- /dev/null
+++ b/rdoc/files/doc/release_notes/3_79_0_txt.html
@@ -0,0 +1,163 @@
+
+
+
+3.79.0.txt
+
+
+
+
+
+
+
+
+3.79.0.txt
+
+
+doc/release_notes/3.79.0.txt
+
+
+Last Update:
+2024-04-12 07:38:02 -0700
+
+
+
+
+
+New Features¶ ↑
+-
+
The hmac_paths plugin allows protection of paths using an HMAC. This can be used to prevent users enumerating paths, since only paths with valid HMACs will be respected.
+
+To use the plugin, you must provide a :secret option. This sets the secret for the HMACs. Make sure to keep this value secret, as this plugin does not provide protection against users who know the secret value. The secret must be at least 32 bytes.
+
+plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes'
+
+
+To generate a valid HMAC path, you call the hmac_path method:
+
+hmac_path('/widget/1')
+# => "/0c2feaefdfc80cc73da19b060c713d4193c57022815238c6657ce2d99b5925eb/0/widget/1"
+
+
+The first segment in the returned path is the HMAC. The second segment is flags for the type of paths (see below), and the rest of the path is as given.
+
+To protect a path or any subsection in the routing tree, you wrap the related code in an r.hmac_path
block.
+
+route do |r|
+ r.hmac_path do
+ r.get 'widget', Integer do |widget_id|
+ # ...
+ end
+ end
+end
+
+
+If first segment of the remaining path contains a valid HMAC for the rest of the path (considering the flags), then r.hmac_path will match and yield to the block, and routing continues inside the block with the HMAC and flags segments removed.
+
+In the above example, if you provide a user a link for widget with ID 1, there is no way for them to guess the valid path for the widget with ID 2, preventing a user from enumerating widgets, without relying on custom access control. Users can only access paths that have been generated by the application and provided to them, either directly or indirectly.
+
+In the above example, r.hmac_path is used at the root of the routing tree. If you would like to call it below the root of the routing tree, it works correctly, but you must pass hmac_path the :root option specifying where r.hmac_paths will be called from. Consider this example:
+
+route do |r|
+ r.on 'widget' do
+ r.hmac_path do
+ r.get Integer do |widget_id|
+ # ...
+ end
+ end
+ end
+
+ r.on 'foobar' do
+ r.hmac_path do
+ r.get Integer do |foobar_id|
+ # ...
+ end
+ end
+ end
+end
+
+
+For security reasons, the hmac_path plugin does not allow an HMAC path designed for widgets to be a valid match in the r.hmac_path call inside the “r.on ‘foobar’” block, preventing users who have a valid HMAC for a widget from looking at the page for a foobar with the same ID. When generating HMAC paths where the matching r.hmac_path call is not at the root of the routing tree, you must pass the :root option:
+
+hmac_path('/1', root: '/widget')
+# => "/widget/daccafce3ce0df52e5ce774626779eaa7286085fcbde1e4681c74175ff0bbacd/0/1"
+
+hmac_path('/1', root: '/foobar')
+# => "/foobar/c5fdaf482771d4f9f38cc13a1b2832929026a4ceb05e98ed6a0cd5a00bf180b7/0/1"
+
+
+Note how the HMAC changes even though the path is the same.
+
+In addition to the :root
option, there are additional options that further constrain use of the generated paths.
+
+The :method option creates a path that can only be called with a certain request method:
+
+hmac_path('/widget/1', method: :get)
+# => "/d38c1e634ecf9a3c0ab9d0832555b035d91b35069efcbf2670b0dfefd4b62fdd/m/widget/1"
+
+
+Note how this results in a different HMAC than the original hmac_path(‘/widget/1’) call. This sets the flags segment to “m”, which means r.hmac_path will consider the request mehod when checking the HMAC, and will only match if the provided request method is GET. This allows you to provide a user the ability to submit a GET request for the underlying path, without providing them the ability to submit a POST request for the underlying path, with no other access control.
+
+The :params option accepts a hash of params, converts it into a query string, and includes the query string in the returned path. It sets the flags segment to p
, which means r.hmac_path will check for that exact query string. Requests with an empty query string or a different string will not match.
+
+hmac_path('/widget/1', params: {foo: 'bar'})
+# => "/fe8d03f9572d5af6c2866295bd3c12c2ea11d290b1cbd016c3b68ee36a678139/p/widget/1?foo=bar"
+
+
+For GET requests, which cannot have request bodies, that is sufficient to ensure that the submitted params are exactly as specified. However, POST requests can have request bodies, and request body params override query string params in r.params. So if you are using this for POST requests (or other HTTP verbs that can have request bodies), use r.GET instead of r.params to specifically check query string parameters.
+
+You can use :root
, :method
, and :params
at the same time:
+
+hmac_path('/1', root: '/widget', method: :get, params: {foo: 'bar'})
+# => "/widget/9169af1b8f40c62a1c2bb15b1b377c65bda681b8efded0e613a4176387468c15/mp/1?foo=bar"
+
+
+This gives you a path only valid for a GET request with a root of “/widget” and a query string of “foo=bar”.
+
+To handle secret rotation, you can provide an :old_secret option when loading the plugin.
+
+plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes',
+ old_secret: 'previous-secret-value-with-at-least-32-bytes'
+
+
+This will use :secret for constructing new paths, but will respect paths generated by :old_secret.
+
+
+Other Improvements¶ ↑
+-
+
When not using cached templates in the render plugin, the render plugin now has better handling when a template is modified and results in an error. Previously, the error would be raised on the first request after the template modification, but subsequent requests would use the previous template value. The render plugin will no longer update the last modified time in this case, so if a template is modified and introduces an error (e.g. SyntaxError in an erb template), all future requests that use the template will result in the error being raised, until the template is fixed.
+
+
+Backwards Compatibility¶ ↑
+-
+
The internal TemplateMtimeWrapper API has been modified. As documented, this is an internal class and the API can change in any Roda
version. However, if any code was relying on the previous implementation of TemplateMtimeWrapper#modified?, it will need to be modified, as that method has been replaced with TemplateMtimeWrapper#if_modified.
+
+Additionally, the TemplateMtimeWrapper#compiled_method_lambda API has also changed.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rdoc/files/lib/roda/plugins/hmac_paths_rb.html b/rdoc/files/lib/roda/plugins/hmac_paths_rb.html
new file mode 100644
index 00000000..f6f12460
--- /dev/null
+++ b/rdoc/files/lib/roda/plugins/hmac_paths_rb.html
@@ -0,0 +1,58 @@
+
+
+
+hmac_paths.rb
+
+
+
+
+
+
+
+
+hmac_paths.rb
+
+
+lib/roda/plugins/hmac_paths.rb
+
+
+Last Update:
+2024-04-12 07:38:02 -0700
+
+
+
+
+
+
+
+Required files
+
+- openssl
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rdoc/files/lib/roda/plugins/render_rb.html b/rdoc/files/lib/roda/plugins/render_rb.html
index d7de771a..657d96cc 100644
--- a/rdoc/files/lib/roda/plugins/render_rb.html
+++ b/rdoc/files/lib/roda/plugins/render_rb.html
@@ -31,7 +31,7 @@ render.rb
Last Update:
-2023-03-10 10:19:46 -0800
+2024-04-03 15:44:47 -0700
diff --git a/rdoc/files/lib/roda/version_rb.html b/rdoc/files/lib/roda/version_rb.html
index c306bfa8..518f2010 100644
--- a/rdoc/files/lib/roda/version_rb.html
+++ b/rdoc/files/lib/roda/version_rb.html
@@ -31,7 +31,7 @@ version.rb
Last Update:
-2024-03-13 10:26:39 -0700
+2024-04-12 07:38:02 -0700
diff --git a/rdoc/fr_class_index.html b/rdoc/fr_class_index.html
index a7f92380..3b5a2d04 100644
--- a/rdoc/fr_class_index.html
+++ b/rdoc/fr_class_index.html
@@ -148,6 +148,9 @@
Roda::RodaPlugins::HeaderMatchers::RequestMethods
Roda::RodaPlugins::HmacPaths
+Roda::RodaPlugins::HmacPaths::InstanceMethods
+Roda::RodaPlugins::HmacPaths::RequestMethods
Roda::RodaPlugins::Hooks
Roda::RodaPlugins::Hooks::ClassMethods
Roda::RodaPlugins::Hooks::InstanceMethods
diff --git a/rdoc/fr_file_index.html b/rdoc/fr_file_index.html
index 6428ea25..43c28112 100644
--- a/rdoc/fr_file_index.html
+++ b/rdoc/fr_file_index.html
@@ -128,6 +128,7 @@
3.76.0.txt
3.77.0.txt
3.78.0.txt
+3.79.0.txt
3.8.0.txt
3.9.0.txt
roda.rb
@@ -190,6 +191,7 @@
head.rb
header_matchers.rb
heartbeat.rb
+hmac_paths.rb
hooks.rb
host_authorization.rb
indifferent_params.rb
diff --git a/rdoc/fr_method_index.html b/rdoc/fr_method_index.html
index 599e0f37..c940b889 100644
--- a/rdoc/fr_method_index.html
+++ b/rdoc/fr_method_index.html
@@ -37,154 +37,155 @@
#route_block (Roda::RodaPlugins::Base::ClassMethods)
#scope (Roda::RodaPlugins::Base::RequestMethods)
#status (Roda::RodaPlugins::Base::ResponseMethods)
-::configure (Roda::RodaPlugins::DefaultStatus)
-::configure (Roda::RodaPlugins::JsonParser)
-::configure (Roda::RodaPlugins::Mailer)
-::configure (Roda::RodaPlugins::Json)
-::configure (Roda::RodaPlugins::ClassLevelRouting)
-::configure (Roda::RodaPlugins::InvalidRequestBody)
-::configure (Roda::RodaPlugins::ErrorHandler)
-::configure (Roda::RodaPlugins::RouteCsrf)
-::configure (Roda::RodaPlugins::AdditionalRenderEngines)
::configure (Roda::RodaPlugins::CommonLogger)
-::configure (Roda::RodaPlugins::AdditionalViewDirectories)
+::configure (Roda::RodaPlugins::PermissionsPolicy)
+::configure (Roda::RodaPlugins::Path)
+::configure (Roda::RodaPlugins::ClassLevelRouting)
::configure (Roda::RodaPlugins::ContentFor)
-::configure (Roda::RodaPlugins::Assets)
-::configure (Roda::RodaPlugins::IntegerMatcherMax)
::configure (Roda::RodaPlugins::ContentSecurityPolicy)
-::configure (Roda::RodaPlugins::RouteBlockArgs)
-::configure (Roda::RodaPlugins::MatchAffix)
-::configure (Roda::RodaPlugins::HostAuthorization)
-::configure (Roda::RodaPlugins::RequestAref)
-::configure (Roda::RodaPlugins::Hooks)
-::configure (Roda::RodaPlugins::Heartbeat)
-::configure (Roda::RodaPlugins::RenderLocals)
-::configure (Roda::RodaPlugins::MatchHookArgs)
-::configure (Roda::RodaPlugins::Middleware)
+::configure (Roda::RodaPlugins::SymbolMatchers)
::configure (Roda::RodaPlugins::CookieFlags)
-::configure (Roda::RodaPlugins::AutoloadHashBranches)
-::configure (Roda::RodaPlugins::HashRoutes)
+::configure (Roda::RodaPlugins::AdditionalRenderEngines)
::configure (Roda::RodaPlugins::Cookies)
-::configure (Roda::RodaPlugins::Chunked)
-::configure (Roda::RodaPlugins::RunAppendSlash)
-::configure (Roda::RodaPlugins::AutoloadNamedRoutes)
-::configure (Roda::RodaPlugins::MultiPublic)
-::configure (Roda::RodaPlugins::HashPaths)
-::configure (Roda::RodaPlugins::ErrorMail)
+::configure (Roda::RodaPlugins::AdditionalViewDirectories)
+::configure (Roda::RodaPlugins::StripPathPrefix)
+::configure (Roda::RodaPlugins::Assets)
+::configure (Roda::RodaPlugins::SymbolViews)
+::configure (Roda::RodaPlugins::TimestampPublic)
+::configure (Roda::RodaPlugins::Public)
::configure (Roda::RodaPlugins::Csrf)
-::configure (Roda::RodaPlugins::MultiRoute)
-::configure (Roda::RodaPlugins::MultiRun)
+::configure (Roda::RodaPlugins::RecheckPrecompiledAssets)
+::configure (Roda::RodaPlugins::NotFound)
::configure (Roda::RodaPlugins::CustomBlockResults)
-::configure (Roda::RodaPlugins::HashBranches)
-::configure (Roda::RodaPlugins::Sessions)
-::configure (Roda::RodaPlugins::RenderCoverage)
+::configure (Roda::RodaPlugins::RedirectHttpToHttps)
+::configure (Roda::RodaPlugins::Chunked)
::configure (Roda::RodaPlugins::CustomMatchers)
-::configure (Roda::RodaPlugins::NamedRoutes)
-::configure (Roda::RodaPlugins::SinatraHelpers)
-::configure (Roda::RodaPlugins::HashBranchViewSubdir)
+::configure (Roda::RodaPlugins::Render)
::configure (Roda::RodaPlugins::DefaultHeaders)
+::configure (Roda::RodaPlugins::AutoloadHashBranches)
::configure (Roda::RodaPlugins::NamedTemplates)
-::configure (Roda::RodaPlugins::Render)
-::configure (Roda::RodaPlugins::RedirectHttpToHttps)
-::configure (Roda::RodaPlugins::NotFound)
+::configure (Roda::RodaPlugins::DefaultStatus)
+::configure (Roda::RodaPlugins::NamedRoutes)
::configure (Roda::RodaPlugins::DirectCall)
-::configure (Roda::RodaPlugins::RecheckPrecompiledAssets)
-::configure (Roda::RodaPlugins::FilterCommonLogger)
+::configure (Roda::RodaPlugins::AutoloadNamedRoutes)
::configure (Roda::RodaPlugins::Environments)
-::configure (Roda::RodaPlugins::SymbolViews)
-::configure (Roda::RodaPlugins::Public)
+::configure (Roda::RodaPlugins::TypeRouting)
+::configure (Roda::RodaPlugins::TypecastParams)
::configure (Roda::RodaPlugins::ErrorEmail)
-::configure (Roda::RodaPlugins::Path)
+::configure (Roda::RodaPlugins::RenderCoverage)
+::configure (Roda::RodaPlugins::ErrorHandler)
+::configure (Roda::RodaPlugins::ErrorMail)
+::configure (Roda::RodaPlugins::RenderLocals)
+::configure (Roda::RodaPlugins::MultiRun)
+::configure (Roda::RodaPlugins::RequestAref)
::configure (Roda::RodaPlugins::PathRewriter)
-::configure (Roda::RodaPlugins::PermissionsPolicy)
-::configure (Roda::RodaPlugins::StripPathPrefix)
+::configure (Roda::RodaPlugins::MultiRoute)
+::configure (Roda::RodaPlugins::HostAuthorization)
+::configure (Roda::RodaPlugins::MultiPublic)
+::configure (Roda::RodaPlugins::FilterCommonLogger)
+::configure (Roda::RodaPlugins::RouteBlockArgs)
+::configure (Roda::RodaPlugins::HashBranchViewSubdir)
+::configure (Roda::RodaPlugins::RouteCsrf)
+::configure (Roda::RodaPlugins::Middleware)
+::configure (Roda::RodaPlugins::MatchHookArgs)
+::configure (Roda::RodaPlugins::HashBranches)
+::configure (Roda::RodaPlugins::MatchAffix)
+::configure (Roda::RodaPlugins::HashPaths)
+::configure (Roda::RodaPlugins::RunAppendSlash)
+::configure (Roda::RodaPlugins::Mailer)
+::configure (Roda::RodaPlugins::HashRoutes)
+::configure (Roda::RodaPlugins::Sessions)
::configure (Roda::RodaPlugins::StatusHandler)
-::configure (Roda::RodaPlugins::SymbolMatchers)
-::configure (Roda::RodaPlugins::TypecastParams)
-::configure (Roda::RodaPlugins::TypeRouting)
-::configure (Roda::RodaPlugins::TimestampPublic)
+::configure (Roda::RodaPlugins::SinatraHelpers)
::configure (Roda::RodaPlugins::Static)
+::configure (Roda::RodaPlugins::JsonParser)
+::configure (Roda::RodaPlugins::Json)
+::configure (Roda::RodaPlugins::InvalidRequestBody)
+::configure (Roda::RodaPlugins::IntegerMatcherMax)
+::configure (Roda::RodaPlugins::Heartbeat)
+::configure (Roda::RodaPlugins::HmacPaths)
+::configure (Roda::RodaPlugins::Hooks)
::create (Roda::RodaPlugins::TypecastParams::Error)
::css (Roda::RodaPlugins::ExceptionPage)
::decode64 (Roda::RodaPlugins::Base64_)
::deprecate_constant (Roda::RodaPlugins)
::handle_type (Roda::RodaPlugins::TypecastParams::Params)
::js (Roda::RodaPlugins::ExceptionPage)
-::load_dependencies (Roda::RodaPlugins::LinkTo)
-::load_dependencies (Roda::RodaPlugins::PrecompileTemplates)
+::load_dependencies (Roda::RodaPlugins::MultiView)
+::load_dependencies (Roda::RodaPlugins::Sessions)
::load_dependencies (Roda::RodaPlugins::ExceptionPage)
-::load_dependencies (Roda::RodaPlugins::SymbolMatchers)
-::load_dependencies (Roda::RodaPlugins::StaticRouting)
-::load_dependencies (Roda::RodaPlugins::ErbH)
+::load_dependencies (Roda::RodaPlugins::ContentFor)
+::load_dependencies (Roda::RodaPlugins::HashRoutes)
+::load_dependencies (Roda::RodaPlugins::MatchAffix)
+::load_dependencies (Roda::RodaPlugins::MatchHook)
::load_dependencies (Roda::RodaPlugins::RouteCsrf)
-::load_dependencies (Roda::RodaPlugins::RecheckPrecompiledAssets)
-::load_dependencies (Roda::RodaPlugins::AdditionalRenderEngines)
-::load_dependencies (Roda::RodaPlugins::FilterCommonLogger)
-::load_dependencies (Roda::RodaPlugins::Partials)
-::load_dependencies (Roda::RodaPlugins::PadrinoRender)
-::load_dependencies (Roda::RodaPlugins::NotFound)
-::load_dependencies (Roda::RodaPlugins::NotAllowed)
-::load_dependencies (Roda::RodaPlugins::PlaceholderStringMatchers)
::load_dependencies (Roda::RodaPlugins::HashBranchViewSubdir)
-::load_dependencies (Roda::RodaPlugins::NamedTemplates)
-::load_dependencies (Roda::RodaPlugins::TypecastParamsSizedIntegers)
-::load_dependencies (Roda::RodaPlugins::RenderCoverage)
-::load_dependencies (Roda::RodaPlugins::BranchLocals)
+::load_dependencies (Roda::RodaPlugins::MultiPublic)
+::load_dependencies (Roda::RodaPlugins::FilterCommonLogger)
+::load_dependencies (Roda::RodaPlugins::InjectERB)
+::load_dependencies (Roda::RodaPlugins::StaticRouting)
+::load_dependencies (Roda::RodaPlugins::LinkTo)
+::load_dependencies (Roda::RodaPlugins::MultiRoute)
+::load_dependencies (Roda::RodaPlugins::RenderLocals)
+::load_dependencies (Roda::RodaPlugins::RenderEach)
::load_dependencies (Roda::RodaPlugins::SinatraHelpers)
+::load_dependencies (Roda::RodaPlugins::BranchLocals)
+::load_dependencies (Roda::RodaPlugins::Partials)
::load_dependencies (Roda::RodaPlugins::ViewOptions)
-::load_dependencies (Roda::RodaPlugins::AdditionalViewDirectories)
-::load_dependencies (Roda::RodaPlugins::Sessions)
-::load_dependencies (Roda::RodaPlugins::MultiView)
-::load_dependencies (Roda::RodaPlugins::MultiRoute)
+::load_dependencies (Roda::RodaPlugins::RenderCoverage)
+::load_dependencies (Roda::RodaPlugins::MultibyteStringMatcher)
::load_dependencies (Roda::RodaPlugins::AutoloadNamedRoutes)
+::load_dependencies (Roda::RodaPlugins::NamedTemplates)
+::load_dependencies (Roda::RodaPlugins::AutoloadHashBranches)
::load_dependencies (Roda::RodaPlugins::CaptureERB)
-::load_dependencies (Roda::RodaPlugins::RenderEach)
+::load_dependencies (Roda::RodaPlugins::NotAllowed)
::load_dependencies (Roda::RodaPlugins::Chunked)
-::load_dependencies (Roda::RodaPlugins::MultiPublic)
-::load_dependencies (Roda::RodaPlugins::RenderLocals)
-::load_dependencies (Roda::RodaPlugins::HashRoutes)
-::load_dependencies (Roda::RodaPlugins::AutoloadHashBranches)
::load_dependencies (Roda::RodaPlugins::AssetsPreloading)
-::load_dependencies (Roda::RodaPlugins::MatchHook)
-::load_dependencies (Roda::RodaPlugins::MatchAffix)
-::load_dependencies (Roda::RodaPlugins::InjectERB)
+::load_dependencies (Roda::RodaPlugins::NotFound)
+::load_dependencies (Roda::RodaPlugins::RecheckPrecompiledAssets)
+::load_dependencies (Roda::RodaPlugins::PadrinoRender)
+::load_dependencies (Roda::RodaPlugins::PrecompileTemplates)
::load_dependencies (Roda::RodaPlugins::Assets)
-::load_dependencies (Roda::RodaPlugins::ContentFor)
-::load_dependencies (Roda::RodaPlugins::MultibyteStringMatcher)
+::load_dependencies (Roda::RodaPlugins::AdditionalViewDirectories)
+::load_dependencies (Roda::RodaPlugins::AdditionalRenderEngines)
+::load_dependencies (Roda::RodaPlugins::SymbolMatchers)
+::load_dependencies (Roda::RodaPlugins::TypecastParamsSizedIntegers)
+::load_dependencies (Roda::RodaPlugins::PlaceholderStringMatchers)
+::load_dependencies (Roda::RodaPlugins::ErbH)
::load_plugin (Roda::RodaPlugins)
::max_input_bytesize (Roda::RodaPlugins::TypecastParams::Params)
::nest (Roda::RodaPlugins::TypecastParams::Params)
-::new (Roda::RodaPlugins::MiddlewareStack::Stack)
::new (Roda::RodaCache)
-::new (Roda::RodaPlugins::Chunked::StreamBody)
-::new (Roda::RodaPlugins::Base::RequestMethods)
-::new (Roda::RodaPlugins::PermissionsPolicy::ResponseMethods)
-::new (Roda::RodaPlugins::Streaming::Stream)
+::new (Roda::RodaPlugins::ClassLevelRouting::InstanceMethods)
+::new (Roda::RodaPlugins::Middleware::Forwarder)
::new (Roda::RodaPlugins::PathRewriter::RequestMethods)
::new (Roda::RodaPlugins::Mailer::InstanceMethods)
-::new (Roda::RodaPlugins::Streaming::AsyncStream)
+::new (RodaSessionMiddleware::SessionHash)
+::new (Roda::RodaPlugins::Chunked::StreamBody)
+::new (Roda::RodaPlugins::MiddlewareStack::Stack)
::new (Roda::RodaPlugins::ContentSecurityPolicy::Policy)
-::new (Roda::RodaPlugins::Base::ResponseMethods)
+::new (Roda::RodaPlugins::DisallowFileUploads::RequestMethods)
::new (Roda::RodaPlugins::Flash::FlashHash)
-::new (Roda::RodaPlugins::ResponseRequest::InstanceMethods)
+::new (Roda::RodaPlugins::PermissionsPolicy::Policy)
::new (Roda::RodaPlugins::RecheckPrecompiledAssets::CompiledAssetsHash)
-::new (Roda::RodaPlugins::RequestHeaders::Headers)
-::new (Roda::RodaPlugins::IndifferentParams::QueryParser::Params)
-::new (Roda::RodaPlugins::ClassLevelRouting::InstanceMethods)
+::new (Roda::RodaPlugins::Base::RequestMethods)
+::new (Roda::RodaPlugins::SinatraHelpers::DelayedBody)
+::new (Roda::RodaPlugins::PermissionsPolicy::ResponseMethods)
::new (Roda::RodaPlugins::ContentSecurityPolicy::ResponseMethods)
+::new (Roda::RodaPlugins::Streaming::Stream)
+::new (Roda::RodaPlugins::MiddlewareStack::StackPosition)
::new (Roda::RodaPlugins::Base::InstanceMethods)
-::new (Roda::RodaPlugins::TypecastParams::Params)
-::new (RodaSessionMiddleware::SessionHash)
-::new (Roda::RodaPlugins::Chunked::Body)
::new (Roda::RodaPlugins::Render::TemplateMtimeWrapper)
::new (RodaSessionMiddleware)
-::new (Roda::RodaPlugins::Head::CloseLater)
-::new (Roda::RodaPlugins::SinatraHelpers::DelayedBody)
-::new (Roda::RodaPlugins::MiddlewareStack::StackPosition)
-::new (Roda::RodaPlugins::Middleware::Forwarder)
-::new (Roda::RodaPlugins::PermissionsPolicy::Policy)
+::new (Roda::RodaPlugins::IndifferentParams::QueryParser::Params)
+::new (Roda::RodaPlugins::Streaming::AsyncStream)
+::new (Roda::RodaPlugins::ResponseRequest::InstanceMethods)
::new (Roda::RodaPlugins::HashRoutes::DSL)
-::new (Roda::RodaPlugins::DisallowFileUploads::RequestMethods)
+::new (Roda::RodaPlugins::Base::ResponseMethods)
+::new (Roda::RodaPlugins::TypecastParams::Params)
+::new (Roda::RodaPlugins::Chunked::Body)
+::new (Roda::RodaPlugins::RequestHeaders::Headers)
+::new (Roda::RodaPlugins::Head::CloseLater)
::register_plugin (Roda::RodaPlugins)
::split_secret (Roda::RodaPlugins::Sessions)
::start_timer (Roda::RodaPlugins::CommonLogger)
@@ -192,23 +193,23 @@
::urlsafe_decode64 (Roda::RodaPlugins::Base64_)
::urlsafe_encode64 (Roda::RodaPlugins::Base64_)
#<< (Roda::RodaPlugins::Streaming::Stream)
-#POST (Roda::RodaPlugins::InvalidRequestBody::RequestMethods)
#POST (Roda::RodaPlugins::JsonParser::RequestMethods)
+#POST (Roda::RodaPlugins::InvalidRequestBody::RequestMethods)
+#[] (Roda::RodaPlugins::RequestHeaders::Headers)
#[] (Roda::RodaPlugins::Base::ResponseMethods)
#[] (Roda::RodaPlugins::TypecastParams::Params)
-#[] (RodaSessionMiddleware::SessionHash)
-#[] (Roda::RodaPlugins::RequestHeaders::Headers)
#[] (Roda::RodaPlugins::RecheckPrecompiledAssets::CompiledAssetsHash)
#[] (Roda::RodaCache)
-#[]= (Roda::RodaPlugins::RecheckPrecompiledAssets::CompiledAssetsHash)
+#[] (RodaSessionMiddleware::SessionHash)
#[]= (RodaSessionMiddleware::SessionHash)
-#[]= (Roda::RodaPlugins::Base::ResponseMethods)
-#[]= (Roda::RodaPlugins::Flash::FlashHash)
#[]= (Roda::RodaCache)
+#[]= (Roda::RodaPlugins::Flash::FlashHash)
+#[]= (Roda::RodaPlugins::RecheckPrecompiledAssets::CompiledAssetsHash)
+#[]= (Roda::RodaPlugins::Base::ResponseMethods)
#_match_hook_args (Roda::RodaPlugins::MatchHookArgs::InstanceMethods)
#_remaining_path (RodaSessionMiddleware::RequestMethods)
-#_roda_handle_main_route (Roda::RodaPlugins::ErrorHandler::InstanceMethods)
#_roda_handle_main_route (Roda::RodaPlugins::Base::InstanceMethods)
+#_roda_handle_main_route (Roda::RodaPlugins::ErrorHandler::InstanceMethods)
#_roda_handle_route (Roda::RodaPlugins::Base::InstanceMethods)
#_roda_main_route (Roda::RodaPlugins::Base::InstanceMethods)
#_roda_run_main_route (Roda::RodaPlugins::BeforeHook::InstanceMethods)
@@ -225,8 +226,8 @@
#append_view_subdir (Roda::RodaPlugins::ViewOptions::InstanceMethods)
#array (Roda::RodaPlugins::TypecastParams::Params)
#array! (Roda::RodaPlugins::TypecastParams::Params)
-#assets (Roda::RodaPlugins::Assets::RequestMethods)
#assets (Roda::RodaPlugins::Assets::InstanceMethods)
+#assets (Roda::RodaPlugins::Assets::RequestMethods)
#assets_matchers (Roda::RodaPlugins::Assets::RequestClassMethods)
#assets_opts (Roda::RodaPlugins::Assets::ClassMethods)
#assets_paths (Roda::RodaPlugins::Assets::InstanceMethods)
@@ -235,21 +236,21 @@
#autoload_hash_branch_dir (Roda::RodaPlugins::AutoloadHashBranches::ClassMethods)
#autoload_named_route (Roda::RodaPlugins::AutoloadNamedRoutes::ClassMethods)
#back (Roda::RodaPlugins::SinatraHelpers::RequestMethods)
-#before (Roda::RodaPlugins::MiddlewareStack::Stack)
#before (Roda::RodaPlugins::Hooks::ClassMethods)
+#before (Roda::RodaPlugins::MiddlewareStack::Stack)
#block_result (Roda::RodaPlugins::Base::RequestMethods)
#body (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
#body= (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
#build! (Roda::RodaPlugins::DelayBuild::ClassMethods)
#cache_control (Roda::RodaPlugins::Caching::ResponseMethods)
#cached_matcher (Roda::RodaPlugins::Base::RequestClassMethods)
-#call (Roda::RodaPlugins::Middleware::Forwarder)
+#call (Roda::RodaPlugins::Base::ClassMethods)
+#call (Roda::RodaPlugins::Middleware::InstanceMethods)
#call (Roda::RodaPlugins::BeforeHook::InstanceMethods)
+#call (Roda::RodaPlugins::Base::InstanceMethods)
+#call (Roda::RodaPlugins::Middleware::Forwarder)
#call (Roda::RodaPlugins::ErrorHandler::InstanceMethods)
#call (Roda::RodaPlugins::DirectCall::ClassMethods)
-#call (Roda::RodaPlugins::Base::InstanceMethods)
-#call (Roda::RodaPlugins::Base::ClassMethods)
-#call (Roda::RodaPlugins::Middleware::InstanceMethods)
#call (RodaSessionMiddleware)
#capture_erb (Roda::RodaPlugins::CaptureERB::InstanceMethods)
#check_csrf! (Roda::RodaPlugins::RouteCsrf::InstanceMethods)
@@ -264,9 +265,9 @@
#clear_named_route_regexp! (Roda::RodaPlugins::MultiRoute::RequestClassMethods)
#clear_session (Roda::RodaPlugins::Sessions::InstanceMethods)
#client_error? (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
-#close (Roda::RodaPlugins::Streaming::AsyncStream)
-#close (Roda::RodaPlugins::Head::CloseLater)
#close (Roda::RodaPlugins::Streaming::Stream)
+#close (Roda::RodaPlugins::Head::CloseLater)
+#close (Roda::RodaPlugins::Streaming::AsyncStream)
#closed? (Roda::RodaPlugins::Streaming::Stream)
#compile_assets (Roda::RodaPlugins::Assets::ClassMethods)
#compiled_method (Roda::RodaPlugins::Render::TemplateMtimeWrapper)
@@ -288,13 +289,13 @@
#csrf_metatag (Roda::RodaPlugins::RouteCsrf::InstanceMethods)
#csrf_metatag (Roda::RodaPlugins::Csrf::InstanceMethods)
#csrf_path (Roda::RodaPlugins::RouteCsrf::InstanceMethods)
-#csrf_tag (Roda::RodaPlugins::RouteCsrf::InstanceMethods)
#csrf_tag (Roda::RodaPlugins::Csrf::InstanceMethods)
-#csrf_token (Roda::RodaPlugins::Csrf::InstanceMethods)
+#csrf_tag (Roda::RodaPlugins::RouteCsrf::InstanceMethods)
#csrf_token (Roda::RodaPlugins::RouteCsrf::InstanceMethods)
+#csrf_token (Roda::RodaPlugins::Csrf::InstanceMethods)
#custom_matcher (Roda::RodaPlugins::CustomMatchers::ClassMethods)
-#default_headers (Roda::RodaPlugins::DefaultHeaders::ClassMethods)
#default_headers (Roda::RodaPlugins::DefaultHeaders::ResponseMethods)
+#default_headers (Roda::RodaPlugins::DefaultHeaders::ClassMethods)
#default_headers (Roda::RodaPlugins::Base::ResponseMethods)
#default_status (Roda::RodaPlugins::Base::ResponseMethods)
#define_compiled_method (Roda::RodaPlugins::Render::TemplateMtimeWrapper)
@@ -309,22 +310,22 @@
#dispatch_from (Roda::RodaPlugins::HashRoutes::DSL)
#each (Roda::RodaPlugins::Head::CloseLater)
#each (Roda::RodaPlugins::Streaming::AsyncStream)
+#each (Roda::RodaPlugins::Streaming::Stream)
#each (Roda::RodaPlugins::RecheckPrecompiledAssets::CompiledAssetsHash)
#each (RodaSessionMiddleware::SessionHash)
-#each (Roda::RodaPlugins::Chunked::StreamBody)
#each (Roda::RodaPlugins::SinatraHelpers::DelayedBody)
-#each (Roda::RodaPlugins::Streaming::Stream)
#each (Roda::RodaPlugins::Chunked::Body)
+#each (Roda::RodaPlugins::Chunked::StreamBody)
#each_chunk (Roda::RodaPlugins::Chunked::InstanceMethods)
#each_partial (Roda::RodaPlugins::Partials::InstanceMethods)
+#empty? (Roda::RodaPlugins::SinatraHelpers::DelayedBody)
#empty? (Roda::RodaPlugins::Base::ResponseMethods)
#empty? (RodaSessionMiddleware::SessionHash)
-#empty? (Roda::RodaPlugins::SinatraHelpers::DelayedBody)
#env (Roda::RodaPlugins::Base::InstanceMethods)
#environment (Roda::RodaPlugins::Environments::ClassMethods)
#environment= (Roda::RodaPlugins::Environments::ClassMethods)
-#error (Roda::RodaPlugins::SinatraHelpers::RequestMethods)
#error (Roda::RodaPlugins::ErrorHandler::ClassMethods)
+#error (Roda::RodaPlugins::SinatraHelpers::RequestMethods)
#error_email (Roda::RodaPlugins::ErrorEmail::InstanceMethods)
#error_email_content (Roda::RodaPlugins::ErrorEmail::InstanceMethods)
#error_mail (Roda::RodaPlugins::ErrorMail::InstanceMethods)
@@ -338,44 +339,44 @@
#expand_path (Roda::RodaPlugins::Base::ClassMethods)
#expand_path (Roda::RodaPlugins::StripPathPrefix::ClassMethods)
#expires (Roda::RodaPlugins::Caching::ResponseMethods)
-#fetch (RodaSessionMiddleware::SessionHash)
#fetch (Roda::RodaPlugins::TypecastParams::Params)
+#fetch (RodaSessionMiddleware::SessionHash)
#finish (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
#finish (Roda::RodaPlugins::DropBody::ResponseMethods)
+#finish (Roda::RodaPlugins::Base::ResponseMethods)
#finish (Roda::RodaPlugins::DeleteEmptyHeaders::ResponseMethods)
-#finish (Roda::RodaPlugins::Mailer::ResponseMethods)
#finish (Roda::RodaPlugins::Caching::ResponseMethods)
-#finish (Roda::RodaPlugins::Base::ResponseMethods)
-#finish_with_body (Roda::RodaPlugins::Base::ResponseMethods)
+#finish (Roda::RodaPlugins::Mailer::ResponseMethods)
#finish_with_body (Roda::RodaPlugins::DeleteEmptyHeaders::ResponseMethods)
+#finish_with_body (Roda::RodaPlugins::Base::ResponseMethods)
#flash (Roda::RodaPlugins::Flash::InstanceMethods)
#flush (Roda::RodaPlugins::Chunked::InstanceMethods)
#forward_next (Roda::RodaPlugins::Middleware::RequestMethods)
-#freeze (Roda::RodaPlugins::CustomBlockResults::ClassMethods)
-#freeze (Roda::RodaPlugins::MatchHookArgs::ClassMethods)
-#freeze (Roda::RodaPlugins::AutoloadHashBranches::ClassMethods)
-#freeze (Roda::RodaPlugins::Hooks::ClassMethods)
-#freeze (Roda::RodaPlugins::PermissionsPolicy::Policy)
+#freeze (Roda::RodaPlugins::MultiRoute::ClassMethods)
+#freeze (Roda::RodaPlugins::PathRewriter::ClassMethods)
#freeze (Roda::RodaPlugins::MultiRun::ClassMethods)
-#freeze (Roda::RodaPlugins::StatusHandler::ClassMethods)
-#freeze (Roda::RodaPlugins::ContentSecurityPolicy::Policy)
#freeze (Roda::RodaPlugins::ClassLevelRouting::ClassMethods)
-#freeze (Roda::RodaPlugins::HashRoutes::ClassMethods)
+#freeze (Roda::RodaPlugins::HashPaths::ClassMethods)
+#freeze (Roda::RodaPlugins::ContentSecurityPolicy::Policy)
+#freeze (Roda::RodaPlugins::Path::ClassMethods)
+#freeze (Roda::RodaPlugins::MatchHookArgs::ClassMethods)
+#freeze (Roda::RodaPlugins::HashBranches::ClassMethods)
+#freeze (Roda::RodaPlugins::AutoloadNamedRoutes::ClassMethods)
+#freeze (Roda::RodaPlugins::PermissionsPolicy::Policy)
+#freeze (Roda::RodaPlugins::HashBranchViewSubdir::ClassMethods)
+#freeze (Roda::RodaPlugins::AutoloadHashBranches::ClassMethods)
#freeze (Roda::RodaPlugins::NamedRoutes::ClassMethods)
#freeze (Roda::RodaCache)
-#freeze (Roda::RodaPlugins::PathRewriter::ClassMethods)
-#freeze (Roda::RodaPlugins::Base::ClassMethods)
-#freeze (Roda::RodaPlugins::AutoloadNamedRoutes::ClassMethods)
+#freeze (Roda::RodaPlugins::CustomBlockResults::ClassMethods)
#freeze (Roda::RodaPlugins::DefaultHeaders::ClassMethods)
-#freeze (Roda::RodaPlugins::HashBranchViewSubdir::ClassMethods)
-#freeze (Roda::RodaPlugins::Path::ClassMethods)
-#freeze (Roda::RodaPlugins::NamedTemplates::ClassMethods)
+#freeze (Roda::RodaPlugins::MailProcessor::ClassMethods)
#freeze (Roda::RodaPlugins::Render::ClassMethods)
-#freeze (Roda::RodaPlugins::MultiRoute::ClassMethods)
+#freeze (Roda::RodaPlugins::StatusHandler::ClassMethods)
+#freeze (Roda::RodaPlugins::Base::ClassMethods)
+#freeze (Roda::RodaPlugins::NamedTemplates::ClassMethods)
+#freeze (Roda::RodaPlugins::HashRoutes::ClassMethods)
#freeze (Roda::RodaPlugins::TypecastParams::ClassMethods)
-#freeze (Roda::RodaPlugins::HashPaths::ClassMethods)
-#freeze (Roda::RodaPlugins::HashBranches::ClassMethods)
-#freeze (Roda::RodaPlugins::MailProcessor::ClassMethods)
+#freeze (Roda::RodaPlugins::Hooks::ClassMethods)
#freeze_template_caches! (Roda::RodaPlugins::PrecompileTemplates::ClassMethods)
#get (Roda::RodaPlugins::Base::RequestMethods)
#h (Roda::RodaPlugins::H::InstanceMethods)
@@ -389,47 +390,51 @@
#handle_stream_error (Roda::RodaPlugins::Streaming::InstanceMethods)
#handled_mail_hook (Roda::RodaPlugins::MailProcessor::InstanceMethods)
#has_key? (RodaSessionMiddleware::SessionHash)
-#hash_branch (Roda::RodaPlugins::HashBranchViewSubdir::ClassMethods)
#hash_branch (Roda::RodaPlugins::HashBranches::ClassMethods)
+#hash_branch (Roda::RodaPlugins::HashBranchViewSubdir::ClassMethods)
#hash_branches (Roda::RodaPlugins::HashBranches::RequestMethods)
#hash_matcher (Roda::RodaPlugins::HashMatcher::ClassMethods)
#hash_path (Roda::RodaPlugins::HashPaths::ClassMethods)
#hash_paths (Roda::RodaPlugins::HashPaths::RequestMethods)
-#hash_routes (Roda::RodaPlugins::HashRoutes::RequestMethods)
#hash_routes (Roda::RodaPlugins::HashRoutes::ClassMethods)
+#hash_routes (Roda::RodaPlugins::HashRoutes::RequestMethods)
#header (Roda::RodaPlugins::MailProcessor::RequestMethods)
#header_key (Roda::RodaPlugins::ContentSecurityPolicy::Policy)
#header_key (Roda::RodaPlugins::PermissionsPolicy::Policy)
-#header_value (Roda::RodaPlugins::ContentSecurityPolicy::Policy)
#header_value (Roda::RodaPlugins::PermissionsPolicy::Policy)
+#header_value (Roda::RodaPlugins::ContentSecurityPolicy::Policy)
#headers (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
#headers (Roda::RodaPlugins::RequestHeaders::RequestMethods)
+#hmac_path (Roda::RodaPlugins::HmacPaths::RequestMethods)
+#hmac_path (Roda::RodaPlugins::HmacPaths::InstanceMethods)
+#hmac_path_hmac (Roda::RodaPlugins::HmacPaths::InstanceMethods)
#http_version (Roda::RodaPlugins::Base::RequestMethods)
+#if_modified (Roda::RodaPlugins::Render::TemplateMtimeWrapper)
#include (Roda::RodaPlugins::Base::ClassMethods)
#include? (RodaSessionMiddleware::SessionHash)
#informational? (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
-#inherited (Roda::RodaPlugins::MultiRoute::ClassMethods)
-#inherited (Roda::RodaPlugins::TypecastParams::ClassMethods)
-#inherited (Roda::RodaPlugins::Base::ClassMethods)
-#inherited (Roda::RodaPlugins::NamedRoutes::ClassMethods)
#inherited (Roda::RodaPlugins::HashBranches::ClassMethods)
#inherited (Roda::RodaPlugins::HashPaths::ClassMethods)
#inherited (Roda::RodaPlugins::Render::ClassMethods)
+#inherited (Roda::RodaPlugins::TypecastParams::ClassMethods)
+#inherited (Roda::RodaPlugins::MultiRoute::ClassMethods)
+#inherited (Roda::RodaPlugins::Base::ClassMethods)
#inherited (Roda::RodaPlugins::HashBranchViewSubdir::ClassMethods)
+#inherited (Roda::RodaPlugins::NamedRoutes::ClassMethods)
#inject_erb (Roda::RodaPlugins::InjectERB::InstanceMethods)
#inline_template_block (Roda::RodaPlugins::Render::ClassMethods)
#inspect (Roda::RodaPlugins::Base::ResponseClassMethods)
-#inspect (Roda::RodaPlugins::Base::RequestClassMethods)
#inspect (Roda::RodaPlugins::Base::ResponseMethods)
-#inspect (RodaSessionMiddleware::SessionHash)
#inspect (Roda::RodaPlugins::Base::RequestMethods)
-#is (Roda::RodaPlugins::OptimizedMatching::RequestMethods)
-#is (Roda::RodaPlugins::NotAllowed::RequestMethods)
+#inspect (Roda::RodaPlugins::Base::RequestClassMethods)
+#inspect (RodaSessionMiddleware::SessionHash)
#is (Roda::RodaPlugins::Base::RequestMethods)
#is (Roda::RodaPlugins::HashRoutes::DSL)
+#is (Roda::RodaPlugins::OptimizedMatching::RequestMethods)
+#is (Roda::RodaPlugins::NotAllowed::RequestMethods)
#is_exactly (Roda::RodaPlugins::OptimizedStringMatchers::RequestMethods)
-#is_get? (Roda::RodaPlugins::Head::RequestMethods)
#is_get? (Roda::RodaPlugins::Base::RequestMethods)
+#is_get? (Roda::RodaPlugins::Head::RequestMethods)
#is_segment (Roda::RodaPlugins::OptimizedSegmentMatchers::RequestMethods)
#join (Roda::RodaPlugins::SinatraHelpers::DelayedBody)
#json_result_classes (Roda::RodaPlugins::Json::ClassMethods)
@@ -440,9 +445,9 @@
#length (Roda::RodaPlugins::SinatraHelpers::DelayedBody)
#link_to (Roda::RodaPlugins::LinkTo::InstanceMethods)
#loaded? (RodaSessionMiddleware::SessionHash)
+#mail (Roda::RodaPlugins::Mailer::ClassMethods)
#mail (Roda::RodaPlugins::Mailer::RequestMethods)
#mail (Roda::RodaPlugins::MailProcessor::InstanceMethods)
-#mail (Roda::RodaPlugins::Mailer::ClassMethods)
#mail_attachments (Roda::RodaPlugins::Mailer::ResponseMethods)
#mail_recipients (Roda::RodaPlugins::MailProcessor::InstanceMethods)
#mail_text (Roda::RodaPlugins::MailProcessor::InstanceMethods)
@@ -454,13 +459,12 @@
#match_params! (Roda::RodaPlugins::ParamMatchers::RequestMethods)
#match_prefix (Roda::RodaPlugins::PathMatchers::RequestMethods)
#match_suffix (Roda::RodaPlugins::PathMatchers::RequestMethods)
-#matched_path (Roda::RodaPlugins::UnescapePath::RequestMethods)
#matched_path (Roda::RodaPlugins::Base::RequestMethods)
+#matched_path (Roda::RodaPlugins::UnescapePath::RequestMethods)
#merge! (RodaSessionMiddleware::SessionHash)
#middleware_stack (Roda::RodaPlugins::MiddlewareStack::ClassMethods)
-#mime_type (Roda::RodaPlugins::SinatraHelpers::ClassMethods)
#mime_type (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
-#modified? (Roda::RodaPlugins::Render::TemplateMtimeWrapper)
+#mime_type (Roda::RodaPlugins::SinatraHelpers::ClassMethods)
#multi_public (Roda::RodaPlugins::MultiPublic::RequestMethods)
#multi_route (Roda::RodaPlugins::MultiRoute::RequestMethods)
#multi_run (Roda::RodaPlugins::MultiRun::RequestMethods)
@@ -475,12 +479,12 @@
#new (Roda::RodaPlugins::Middleware::ClassMethods)
#no_chunk! (Roda::RodaPlugins::Chunked::InstanceMethods)
#no_mail! (Roda::RodaPlugins::Mailer::InstanceMethods)
-#not_found (Roda::RodaPlugins::SinatraHelpers::RequestMethods)
#not_found (Roda::RodaPlugins::NotFound::ClassMethods)
+#not_found (Roda::RodaPlugins::SinatraHelpers::RequestMethods)
#not_found? (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
-#on (Roda::RodaPlugins::HashRoutes::DSL)
-#on (Roda::RodaPlugins::OptimizedMatching::RequestMethods)
#on (Roda::RodaPlugins::Base::RequestMethods)
+#on (Roda::RodaPlugins::OptimizedMatching::RequestMethods)
+#on (Roda::RodaPlugins::HashRoutes::DSL)
#on_branch (Roda::RodaPlugins::OptimizedStringMatchers::RequestMethods)
#on_segment (Roda::RodaPlugins::OptimizedSegmentMatchers::RequestMethods)
#on_type (Roda::RodaPlugins::TypeRouting::RequestMethods)
@@ -498,8 +502,8 @@
#path (Roda::RodaPlugins::Path::ClassMethods)
#path_block (Roda::RodaPlugins::Path::ClassMethods)
#path_classes (Roda::RodaPlugins::Path::ClassMethods)
-#permissions_policy (Roda::RodaPlugins::PermissionsPolicy::ResponseMethods)
#permissions_policy (Roda::RodaPlugins::PermissionsPolicy::InstanceMethods)
+#permissions_policy (Roda::RodaPlugins::PermissionsPolicy::ResponseMethods)
#persist_session (Roda::RodaPlugins::Sessions::RequestMethods)
#plugin (Roda::RodaPlugins::Base::ClassMethods)
#post (Roda::RodaPlugins::Base::RequestMethods)
@@ -508,30 +512,30 @@
#preload_assets_link_header (Roda::RodaPlugins::AssetsPreloading::InstanceMethods)
#preload_assets_link_tags (Roda::RodaPlugins::AssetsPreloading::InstanceMethods)
#present? (Roda::RodaPlugins::TypecastParams::Params)
-#process_mail (Roda::RodaPlugins::MailProcessor::InstanceMethods)
#process_mail (Roda::RodaPlugins::MailProcessor::ClassMethods)
+#process_mail (Roda::RodaPlugins::MailProcessor::InstanceMethods)
#process_mailbox (Roda::RodaPlugins::MailProcessor::ClassMethods)
#public (Roda::RodaPlugins::Public::RequestMethods)
#r (Roda::RodaPlugins::R::InstanceMethods)
#rcpt (Roda::RodaPlugins::MailProcessor::ClassMethods)
#read_asset_file (Roda::RodaPlugins::Assets::InstanceMethods)
#real_remaining_path (Roda::RodaPlugins::TypeRouting::RequestMethods)
-#redirect (Roda::RodaPlugins::Base::ResponseMethods)
-#redirect (Roda::RodaPlugins::Base::RequestMethods)
#redirect (Roda::RodaPlugins::SinatraHelpers::RequestMethods)
+#redirect (Roda::RodaPlugins::Base::RequestMethods)
+#redirect (Roda::RodaPlugins::Base::ResponseMethods)
#redirect? (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
#redirect_http_to_https (Roda::RodaPlugins::RedirectHttpToHttps::RequestMethods)
#refresh_multi_run_regexp! (Roda::RodaPlugins::MultiRun::RequestClassMethods)
#relative_path (Roda::RodaPlugins::RelativePath::InstanceMethods)
#relative_prefix (Roda::RodaPlugins::RelativePath::InstanceMethods)
#remove (Roda::RodaPlugins::MiddlewareStack::Stack)
-#render (Roda::RodaPlugins::Render::InstanceMethods)
-#render (Roda::RodaPlugins::PadrinoRender::InstanceMethods)
#render (Roda::RodaPlugins::Render::TemplateMtimeWrapper)
+#render (Roda::RodaPlugins::PadrinoRender::InstanceMethods)
+#render (Roda::RodaPlugins::Render::InstanceMethods)
#render_asset (Roda::RodaPlugins::Assets::InstanceMethods)
#render_each (Roda::RodaPlugins::RenderEach::InstanceMethods)
-#render_opts (Roda::RodaPlugins::Render::ClassMethods)
#render_opts (Roda::RodaPlugins::Render::InstanceMethods)
+#render_opts (Roda::RodaPlugins::Render::ClassMethods)
#replace (Roda::RodaPlugins::RecheckPrecompiledAssets::CompiledAssetsHash)
#replace (RodaSessionMiddleware::SessionHash)
#report_only (Roda::RodaPlugins::ContentSecurityPolicy::Policy)
@@ -545,33 +549,33 @@
#response_delegate (Roda::RodaPlugins::Delegate::ClassMethods)
#response_module (Roda::RodaPlugins::ModuleInclude::ClassMethods)
#rewrite_path (Roda::RodaPlugins::PathRewriter::ClassMethods)
-#roda_class (Roda::RodaPlugins::Base::ResponseMethods)
#roda_class (Roda::RodaPlugins::Base::RequestMethods)
+#roda_class (Roda::RodaPlugins::Base::ResponseMethods)
+#root (Roda::RodaPlugins::Base::RequestMethods)
#root (Roda::RodaPlugins::EmptyRoot::RequestMethods)
#root (Roda::RodaPlugins::NotAllowed::RequestMethods)
-#root (Roda::RodaPlugins::Base::RequestMethods)
#route (Roda::RodaPlugins::MultiRoute::ClassMethods)
-#route (Roda::RodaPlugins::Base::ClassMethods)
#route (Roda::RodaPlugins::NamedRoutes::ClassMethods)
#route (Roda::RodaPlugins::NamedRoutes::RequestMethods)
-#run (Roda::RodaPlugins::RunHandler::RequestMethods)
+#route (Roda::RodaPlugins::Base::ClassMethods)
#run (Roda::RodaPlugins::RunRequireSlash::RequestMethods)
+#run (Roda::RodaPlugins::RunHandler::RequestMethods)
+#run (Roda::RodaPlugins::MultiRun::ClassMethods)
#run (Roda::RodaPlugins::RunAppendSlash::RequestMethods)
#run (Roda::RodaPlugins::Base::RequestMethods)
-#run (Roda::RodaPlugins::MultiRun::ClassMethods)
#send_early_hints (Roda::RodaPlugins::EarlyHints::InstanceMethods)
#send_file (Roda::RodaPlugins::SinatraHelpers::RequestMethods)
#sendmail (Roda::RodaPlugins::Mailer::ClassMethods)
#server_error? (Roda::RodaPlugins::SinatraHelpers::ResponseMethods)
-#session (Roda::RodaPlugins::Base::RequestMethods)
#session (Roda::RodaPlugins::Sessions::RequestMethods)
+#session (Roda::RodaPlugins::Base::RequestMethods)
#session (Roda::RodaPlugins::Base::InstanceMethods)
#session_created_at (Roda::RodaPlugins::Sessions::RequestMethods)
#session_updated_at (Roda::RodaPlugins::Sessions::RequestMethods)
#set_cookie (Roda::RodaPlugins::Cookies::ResponseMethods)
#set_default_headers (Roda::RodaPlugins::Base::ClassMethods)
-#set_header (Roda::RodaPlugins::ContentSecurityPolicy::Policy)
#set_header (Roda::RodaPlugins::PermissionsPolicy::Policy)
+#set_header (Roda::RodaPlugins::ContentSecurityPolicy::Policy)
#set_layout_locals (Roda::RodaPlugins::BranchLocals::InstanceMethods)
#set_layout_options (Roda::RodaPlugins::ViewOptions::InstanceMethods)
#set_view_locals (Roda::RodaPlugins::BranchLocals::InstanceMethods)
@@ -603,17 +607,17 @@
#uri (Roda::RodaPlugins::SinatraHelpers::RequestMethods)
#url (Roda::RodaPlugins::Path::InstanceMethods)
#url (Roda::RodaPlugins::SinatraHelpers::RequestMethods)
-#use (Roda::RodaPlugins::MiddlewareStack::StackPosition)
#use (Roda::RodaPlugins::Base::ClassMethods)
+#use (Roda::RodaPlugins::MiddlewareStack::StackPosition)
#use_request_specific_csrf_tokens? (Roda::RodaPlugins::RouteCsrf::InstanceMethods)
#valid_csrf? (Roda::RodaPlugins::RouteCsrf::InstanceMethods)
#values (RodaSessionMiddleware::SessionHash)
-#view (Roda::RodaPlugins::HashRoutes::DSL)
#view (Roda::RodaPlugins::Render::InstanceMethods)
#view (Roda::RodaPlugins::Chunked::InstanceMethods)
+#view (Roda::RodaPlugins::HashRoutes::DSL)
#views (Roda::RodaPlugins::HashRoutes::DSL)
-#write (Roda::RodaPlugins::Base::ResponseMethods)
#write (Roda::RodaPlugins::Streaming::Stream)
+#write (Roda::RodaPlugins::Base::ResponseMethods)
3.79.0 (2024-04-12)¶ ↑
+-
+
Do not update template mtime when there is an error reloading templates in the render plugin (jeremyevans)
+ -
+
Add hmac_paths plugin for preventing path enumeration and supporting access control (jeremyevans)
+
3.78.0 (2024-03-13)¶ ↑
-
Add permissions_policy plugin for setting Permissions-Policy header (jeremyevans)
diff --git a/rdoc/files/doc/release_notes/3_79_0_txt.html b/rdoc/files/doc/release_notes/3_79_0_txt.html new file mode 100644 index 00000000..f7ddbeb6 --- /dev/null +++ b/rdoc/files/doc/release_notes/3_79_0_txt.html @@ -0,0 +1,163 @@ + + + +3.79.0.txt + + + + + + +++ + + diff --git a/rdoc/files/lib/roda/plugins/hmac_paths_rb.html b/rdoc/files/lib/roda/plugins/hmac_paths_rb.html new file mode 100644 index 00000000..f6f12460 --- /dev/null +++ b/rdoc/files/lib/roda/plugins/hmac_paths_rb.html @@ -0,0 +1,58 @@ + + + +++3.79.0.txt +
++doc/release_notes/3.79.0.txt +++Last Update: +2024-04-12 07:38:02 -0700 ++++ + +++++New Features¶ ↑
+-
+
The hmac_paths plugin allows protection of paths using an HMAC. This can be used to prevent users enumerating paths, since only paths with valid HMACs will be respected.
+ +To use the plugin, you must provide a :secret option. This sets the secret for the HMACs. Make sure to keep this value secret, as this plugin does not provide protection against users who know the secret value. The secret must be at least 32 bytes.
+ +plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes' +
+ +To generate a valid HMAC path, you call the hmac_path method:
+ +hmac_path('/widget/1') +# => "/0c2feaefdfc80cc73da19b060c713d4193c57022815238c6657ce2d99b5925eb/0/widget/1" +
+ +The first segment in the returned path is the HMAC. The second segment is flags for the type of paths (see below), and the rest of the path is as given.
+ +To protect a path or any subsection in the routing tree, you wrap the related code in an
+ +r.hmac_path
block.route do |r| + r.hmac_path do + r.get 'widget', Integer do |widget_id| + # ... + end + end +end +
+ +If first segment of the remaining path contains a valid HMAC for the rest of the path (considering the flags), then r.hmac_path will match and yield to the block, and routing continues inside the block with the HMAC and flags segments removed.
+ +In the above example, if you provide a user a link for widget with ID 1, there is no way for them to guess the valid path for the widget with ID 2, preventing a user from enumerating widgets, without relying on custom access control. Users can only access paths that have been generated by the application and provided to them, either directly or indirectly.
+ +In the above example, r.hmac_path is used at the root of the routing tree. If you would like to call it below the root of the routing tree, it works correctly, but you must pass hmac_path the :root option specifying where r.hmac_paths will be called from. Consider this example:
+ +route do |r| + r.on 'widget' do + r.hmac_path do + r.get Integer do |widget_id| + # ... + end + end + end + + r.on 'foobar' do + r.hmac_path do + r.get Integer do |foobar_id| + # ... + end + end + end +end +
+ +For security reasons, the hmac_path plugin does not allow an HMAC path designed for widgets to be a valid match in the r.hmac_path call inside the “r.on ‘foobar’” block, preventing users who have a valid HMAC for a widget from looking at the page for a foobar with the same ID. When generating HMAC paths where the matching r.hmac_path call is not at the root of the routing tree, you must pass the :root option:
+ +hmac_path('/1', root: '/widget') +# => "/widget/daccafce3ce0df52e5ce774626779eaa7286085fcbde1e4681c74175ff0bbacd/0/1" + +hmac_path('/1', root: '/foobar') +# => "/foobar/c5fdaf482771d4f9f38cc13a1b2832929026a4ceb05e98ed6a0cd5a00bf180b7/0/1" +
+ +Note how the HMAC changes even though the path is the same.
+ +In addition to the
+ +:root
option, there are additional options that further constrain use of the generated paths.The :method option creates a path that can only be called with a certain request method:
+ +hmac_path('/widget/1', method: :get) +# => "/d38c1e634ecf9a3c0ab9d0832555b035d91b35069efcbf2670b0dfefd4b62fdd/m/widget/1" +
+ +Note how this results in a different HMAC than the original hmac_path(‘/widget/1’) call. This sets the flags segment to “m”, which means r.hmac_path will consider the request mehod when checking the HMAC, and will only match if the provided request method is GET. This allows you to provide a user the ability to submit a GET request for the underlying path, without providing them the ability to submit a POST request for the underlying path, with no other access control.
+ +The :params option accepts a hash of params, converts it into a query string, and includes the query string in the returned path. It sets the flags segment to
+ +p
, which means r.hmac_path will check for that exact query string. Requests with an empty query string or a different string will not match.hmac_path('/widget/1', params: {foo: 'bar'}) +# => "/fe8d03f9572d5af6c2866295bd3c12c2ea11d290b1cbd016c3b68ee36a678139/p/widget/1?foo=bar" +
+ +For GET requests, which cannot have request bodies, that is sufficient to ensure that the submitted params are exactly as specified. However, POST requests can have request bodies, and request body params override query string params in r.params. So if you are using this for POST requests (or other HTTP verbs that can have request bodies), use r.GET instead of r.params to specifically check query string parameters.
+ +You can use
+ +:root
,:method
, and:params
at the same time:hmac_path('/1', root: '/widget', method: :get, params: {foo: 'bar'}) +# => "/widget/9169af1b8f40c62a1c2bb15b1b377c65bda681b8efded0e613a4176387468c15/mp/1?foo=bar" +
+ +This gives you a path only valid for a GET request with a root of “/widget” and a query string of “foo=bar”.
+ +To handle secret rotation, you can provide an :old_secret option when loading the plugin.
+ +plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes', + old_secret: 'previous-secret-value-with-at-least-32-bytes' +
+ +This will use :secret for constructing new paths, but will respect paths generated by :old_secret.
+
Other Improvements¶ ↑
+-
+
When not using cached templates in the render plugin, the render plugin now has better handling when a template is modified and results in an error. Previously, the error would be raised on the first request after the template modification, but subsequent requests would use the previous template value. The render plugin will no longer update the last modified time in this case, so if a template is modified and introduces an error (e.g. SyntaxError in an erb template), all future requests that use the template will result in the error being raised, until the template is fixed.
+
Backwards Compatibility¶ ↑
+-
+
The internal TemplateMtimeWrapper API has been modified. As documented, this is an internal class and the API can change in any
+ +Roda
version. However, if any code was relying on the previous implementation of TemplateMtimeWrapper#modified?, it will need to be modified, as that method has been replaced with TemplateMtimeWrapper#if_modified.Additionally, the TemplateMtimeWrapper#compiled_method_lambda API has also changed.
+
++ +hmac_paths.rb + + + + + + +++ + + diff --git a/rdoc/files/lib/roda/plugins/render_rb.html b/rdoc/files/lib/roda/plugins/render_rb.html index d7de771a..657d96cc 100644 --- a/rdoc/files/lib/roda/plugins/render_rb.html +++ b/rdoc/files/lib/roda/plugins/render_rb.html @@ -31,7 +31,7 @@++hmac_paths.rb +
++lib/roda/plugins/hmac_paths.rb +++Last Update: +2024-04-12 07:38:02 -0700 ++++ + ++ ++++ +++Required files
+-
+
- openssl +
render.rb
-
+