roda 3.39.0 → 3.43.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +36 -0
- data/MIT-LICENSE +1 -1
- data/doc/release_notes/3.40.0.txt +24 -0
- data/doc/release_notes/3.41.0.txt +9 -0
- data/doc/release_notes/3.42.0.txt +21 -0
- data/doc/release_notes/3.43.0.txt +34 -0
- data/lib/roda.rb +6 -0
- data/lib/roda/cache.rb +7 -0
- data/lib/roda/plugins/assets.rb +31 -10
- data/lib/roda/plugins/common_logger.rb +3 -2
- data/lib/roda/plugins/custom_matchers.rb +2 -0
- data/lib/roda/plugins/default_headers.rb +15 -14
- data/lib/roda/plugins/host_authorization.rb +156 -0
- data/lib/roda/plugins/precompile_templates.rb +96 -21
- data/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
- data/lib/roda/plugins/render.rb +82 -14
- data/lib/roda/plugins/render_locals.rb +4 -0
- data/lib/roda/version.rb +2 -2
- metadata +21 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95c0306a50aefb2cfa3bd700403ff2d8441acb78438dcca36bdb0341708208fe
|
4
|
+
data.tar.gz: df524e11cab19b33d6f4e2a139f2813b89dc0832863aa2e0ac70c69cc4f98e68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc0c531cdc7d9ae5d122cae6394b49ebd9014811c6dfba64ef70bc2b4ffe03d66efe2a10dd11f180a1c68db15a7bf3e048f8fdd8550fa72584d2190d8b3d59ea
|
7
|
+
data.tar.gz: 3f42b2dd764cae91a0b81f43933c230b5795f29f33aebf18a7cbfd800071dfdb9ce29f80b471747105e6caa524571877f207cffde621fdaf3d04b89d5c295f8e
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,39 @@
|
|
1
|
+
= 3.43.1 (2021-04-13)
|
2
|
+
|
3
|
+
* [SECURITY] Fix issue where loading content_security_policy plugin after default_headers plugin had no effect (jeremyevans)
|
4
|
+
|
5
|
+
= 3.43.0 (2021-04-12)
|
6
|
+
|
7
|
+
* Add host_authorization plugin, for checking that requests are submitted using an approved host (jeremyevans)
|
8
|
+
|
9
|
+
= 3.42.0 (2021-03-12)
|
10
|
+
|
11
|
+
* Make Roda.plugin support plugins using keyword arguments in Ruby 3 (jeremyevans)
|
12
|
+
|
13
|
+
* Make Roda.use support middleware using keyword arguments in Ruby 3 (pat) (#207)
|
14
|
+
|
15
|
+
* Support common_logger plugin :method option for specifying the method to call on the logger (fnordfish, jeremyevans) (#206)
|
16
|
+
|
17
|
+
* Add recheck_precompiled_assets plugin for checking for updates to the precompiled asset metadata file (jeremyevans)
|
18
|
+
|
19
|
+
* Make compile_assets class method in assets plugin use an atomic approach to writing precompiled metadata file (jeremyevans)
|
20
|
+
|
21
|
+
= 3.41.0 (2021-02-17)
|
22
|
+
|
23
|
+
* Improve view performance with :content option up to 3x by calling compiled template methods directly (jeremyevans)
|
24
|
+
|
25
|
+
= 3.40.0 (2021-01-14)
|
26
|
+
|
27
|
+
* Add freeze_template_caches! to the precompile_templates plugin, which ensures all templates are precompiled, and speeds up template access (jeremyevans)
|
28
|
+
|
29
|
+
* Add precompile_views to the precompile_templates plugin, which precompiles the optimized render methods (jeremyevans)
|
30
|
+
|
31
|
+
* Have RodaCache#freeze return the frozen internal hash (which no longer needs a mutex for thread-safety) (jeremyevans)
|
32
|
+
|
33
|
+
* Speed up the view method in the render plugin even more when freezing the application (jeremyevans)
|
34
|
+
|
35
|
+
* Speed up the view method in the render plugin when called with a single argument (jeremyevans)
|
36
|
+
|
1
37
|
= 3.39.0 (2020-12-15)
|
2
38
|
|
3
39
|
* Speed up relative_path plugin if relative_path or relative_prefix is called more than once (jeremyevans)
|
data/MIT-LICENSE
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A precompile_views method has been added to the
|
4
|
+
precompile_templates plugin. This method works with Roda's
|
5
|
+
optimized compiled view methods, allowing additional memory
|
6
|
+
sharing between parent and child processes.
|
7
|
+
|
8
|
+
* A freeze_template_caches! method has been added to the
|
9
|
+
precompile_templates plugin. This freezes the template caches,
|
10
|
+
preventing the compilation of additional templates, useful for
|
11
|
+
enforcing that only precompiled templates are used. Additionally,
|
12
|
+
this speeds up access to the template caches.
|
13
|
+
|
14
|
+
* RodaCache#freeze now returns the frozen internal hash, which can
|
15
|
+
then be accessed without a mutex. Previously, freeze only froze
|
16
|
+
the receiver and not the internal hash, so it didn't have the
|
17
|
+
expected effect.
|
18
|
+
|
19
|
+
= Other Improvements
|
20
|
+
|
21
|
+
* The view method in the render plugin is now faster in most cases
|
22
|
+
when a single argument is used. When freezing the application,
|
23
|
+
an additional optimization is performed to increase the
|
24
|
+
performance of the view method even further.
|
@@ -0,0 +1,9 @@
|
|
1
|
+
= Improvements
|
2
|
+
|
3
|
+
* The performance of the render plugin's view method when passed the
|
4
|
+
:content option and no other options or arguments has been improved
|
5
|
+
by about 3x, by calling compiled template methods directly.
|
6
|
+
|
7
|
+
* The compiled template method for the layout is cleared when the
|
8
|
+
render plugin is loaded again, which can fix issues when it is
|
9
|
+
loaded with different options that affect the layout.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A recheck_precompiled_assets plugin has been added, which allows
|
4
|
+
for checking for updates to the precompiled asset metadata file,
|
5
|
+
and automatically using the updated data.
|
6
|
+
|
7
|
+
* The common_logger plugin now supports a :method plugin option to
|
8
|
+
specify the method to call on the logger.
|
9
|
+
|
10
|
+
= Other Improvements
|
11
|
+
|
12
|
+
* Plugins and middleware that use keyword arguments are now supported
|
13
|
+
in Ruby 3.
|
14
|
+
|
15
|
+
* The compile_assets class method in the assets plugin now uses an
|
16
|
+
atomic approach to writing the precompiled asset metadata file.
|
17
|
+
|
18
|
+
* Minor method visibility issues have been fixed. The custom_matchers
|
19
|
+
plugin no longer makes the unsupported_matcher request method
|
20
|
+
public, and the render plugin no longer makes the _layout_method
|
21
|
+
public when the application is frozen.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A host_authorization plugin has been added to verify the requested
|
4
|
+
Host header is authorized. Using it can prevent DNS rebinding
|
5
|
+
attacks in cases where the application can receive requests for
|
6
|
+
arbitrary hosts.
|
7
|
+
|
8
|
+
To check for authorized hosts in your routing tree, you call the
|
9
|
+
check_host_authorization! method. For example, if you want to
|
10
|
+
check for authorized hosts after serving requests for public
|
11
|
+
files, you could do:
|
12
|
+
|
13
|
+
plugin :public
|
14
|
+
plugin :host_authorization, 'my-domain-name.example.com'
|
15
|
+
|
16
|
+
route do |r|
|
17
|
+
r.public
|
18
|
+
check_host_authorized!
|
19
|
+
|
20
|
+
# ... rest of routing tree
|
21
|
+
end
|
22
|
+
|
23
|
+
In addition to handling single domain names via a string, you can
|
24
|
+
provide an array of domain names, a regexp to match again, or a
|
25
|
+
proc.
|
26
|
+
|
27
|
+
By default, requests using unauthorized hosts receive an empty 403
|
28
|
+
response. If you would like to customize the response, you can
|
29
|
+
pass a block when loading the plugin:
|
30
|
+
|
31
|
+
plugin :host_authorization, 'my-domain-name.example.com' do |r|
|
32
|
+
response.status = 403
|
33
|
+
"Response Body Here"
|
34
|
+
end
|
data/lib/roda.rb
CHANGED
@@ -292,6 +292,9 @@ class Roda
|
|
292
292
|
plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
|
293
293
|
@app = nil
|
294
294
|
end
|
295
|
+
# :nocov:
|
296
|
+
ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
|
297
|
+
# :nocov:
|
295
298
|
|
296
299
|
# Setup routing tree for the current Roda application, and build the
|
297
300
|
# underlying rack application using the stored middleware. Requires
|
@@ -327,6 +330,9 @@ class Roda
|
|
327
330
|
@middleware << [args, block].freeze
|
328
331
|
@app = nil
|
329
332
|
end
|
333
|
+
# :nocov:
|
334
|
+
ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
|
335
|
+
# :nocov:
|
330
336
|
|
331
337
|
private
|
332
338
|
|
data/lib/roda/cache.rb
CHANGED
@@ -22,6 +22,13 @@ class Roda
|
|
22
22
|
@mutex.synchronize{@hash[key] = value}
|
23
23
|
end
|
24
24
|
|
25
|
+
# Return the frozen internal hash. The internal hash can then
|
26
|
+
# be accessed directly since it is frozen and there are no
|
27
|
+
# thread safety issues.
|
28
|
+
def freeze
|
29
|
+
@hash.freeze
|
30
|
+
end
|
31
|
+
|
25
32
|
private
|
26
33
|
|
27
34
|
# Create a copy of the cache with a separate mutex.
|
data/lib/roda/plugins/assets.rb
CHANGED
@@ -376,7 +376,7 @@ class Roda
|
|
376
376
|
|
377
377
|
if opts[:precompiled] && !opts[:compiled] && ::File.exist?(opts[:precompiled])
|
378
378
|
require 'json'
|
379
|
-
opts[:compiled] =
|
379
|
+
opts[:compiled] = app.send(:_precompiled_asset_metadata, opts[:precompiled])
|
380
380
|
end
|
381
381
|
|
382
382
|
if opts[:early_hints]
|
@@ -455,7 +455,7 @@ class Roda
|
|
455
455
|
require 'fileutils'
|
456
456
|
|
457
457
|
unless assets_opts[:compiled]
|
458
|
-
opts[:assets] = assets_opts.merge(:compiled =>
|
458
|
+
opts[:assets] = assets_opts.merge(:compiled => _compiled_assets_initial_hash).freeze
|
459
459
|
end
|
460
460
|
|
461
461
|
if type == nil
|
@@ -465,10 +465,12 @@ class Roda
|
|
465
465
|
_compile_assets(type)
|
466
466
|
end
|
467
467
|
|
468
|
-
if assets_opts[:precompiled]
|
468
|
+
if precompile_file = assets_opts[:precompiled]
|
469
469
|
require 'json'
|
470
|
-
::FileUtils.mkdir_p(File.dirname(
|
471
|
-
|
470
|
+
::FileUtils.mkdir_p(File.dirname(precompile_file))
|
471
|
+
tmp_file = "#{precompile_file}.tmp"
|
472
|
+
::File.open(tmp_file, 'wb'){|f| f.write((opts[:json_serializer] || :to_json.to_proc).call(assets_opts[:compiled]))}
|
473
|
+
::File.rename(tmp_file, precompile_file)
|
472
474
|
end
|
473
475
|
|
474
476
|
assets_opts[:compiled]
|
@@ -476,6 +478,11 @@ class Roda
|
|
476
478
|
|
477
479
|
private
|
478
480
|
|
481
|
+
# The initial hash to use to store compiled asset metadata.
|
482
|
+
def _compiled_assets_initial_hash
|
483
|
+
{}
|
484
|
+
end
|
485
|
+
|
479
486
|
# Internals of compile_assets, handling recursive calls for loading
|
480
487
|
# all asset groups under the given type.
|
481
488
|
def _compile_assets(type)
|
@@ -493,6 +500,11 @@ class Roda
|
|
493
500
|
end
|
494
501
|
end
|
495
502
|
|
503
|
+
# The precompiled asset metadata stored in the given file
|
504
|
+
def _precompiled_asset_metadata(file)
|
505
|
+
(opts[:json_parser] || ::JSON.method(:parse)).call(::File.read(file))
|
506
|
+
end
|
507
|
+
|
496
508
|
# Compile each array of files for the given type into a single
|
497
509
|
# file. Dirs should be an array of asset group names, if these
|
498
510
|
# are files in an asset group.
|
@@ -794,23 +806,32 @@ class Roda
|
|
794
806
|
# handled.
|
795
807
|
def assets_matchers
|
796
808
|
@assets_matchers ||= [:css, :js].map do |t|
|
797
|
-
|
809
|
+
if regexp = assets_regexp(t)
|
810
|
+
[t, regexp].freeze
|
811
|
+
end
|
798
812
|
end.compact.freeze
|
799
813
|
end
|
800
814
|
|
801
815
|
private
|
802
816
|
|
817
|
+
# A string for the asset filename for the asset type, key, and digest.
|
818
|
+
def _asset_regexp(type, key, digest)
|
819
|
+
"#{key.sub(/\A#{type}/, '')}.#{digest}.#{type}"
|
820
|
+
end
|
821
|
+
|
803
822
|
# The regexp matcher to use for the given type. This handles any asset groups
|
804
823
|
# for the asset types.
|
805
824
|
def assets_regexp(type)
|
806
825
|
o = roda_class.assets_opts
|
807
826
|
if compiled = o[:compiled]
|
808
|
-
assets = compiled.
|
809
|
-
|
810
|
-
|
827
|
+
assets = compiled.
|
828
|
+
select{|k,_| k =~ /\A#{type}/}.
|
829
|
+
map{|k, md| _asset_regexp(type, k, md)}
|
830
|
+
return if assets.empty?
|
811
831
|
/#{o[:"compiled_#{type}_prefix"]}(#{Regexp.union(assets)})/
|
812
832
|
else
|
813
|
-
assets =
|
833
|
+
return unless assets = o[type]
|
834
|
+
assets = unnest_assets_hash(assets)
|
814
835
|
ts = o[:timestamp_paths]
|
815
836
|
/#{o[:"#{type}_prefix"]}#{"\\d+#{ts}" if ts}(#{Regexp.union(assets.uniq)})#{o[:"#{type}_suffix"]}/
|
816
837
|
end
|
@@ -18,10 +18,11 @@ class Roda
|
|
18
18
|
# plugin :common_logger
|
19
19
|
# plugin :common_logger, $stdout
|
20
20
|
# plugin :common_logger, Logger.new('filename')
|
21
|
+
# plugin :common_logger, Logger.new('filename'), method: :debug
|
21
22
|
module CommonLogger
|
22
|
-
def self.configure(app, logger=nil)
|
23
|
+
def self.configure(app, logger=nil, opts=OPTS)
|
23
24
|
app.opts[:common_logger] = logger || app.opts[:common_logger] || $stderr
|
24
|
-
app.opts[:common_logger_meth] = app.opts[:common_logger].method(logger.respond_to?(:write) ? :write : :<<)
|
25
|
+
app.opts[:common_logger_meth] = app.opts[:common_logger].method(opts.fetch(:method){logger.respond_to?(:write) ? :write : :<<})
|
25
26
|
end
|
26
27
|
|
27
28
|
if RUBY_VERSION >= '2.1'
|
@@ -23,30 +23,31 @@ class Roda
|
|
23
23
|
module DefaultHeaders
|
24
24
|
# Merge the given headers into the existing default headers, if any.
|
25
25
|
def self.configure(app, headers={})
|
26
|
-
|
26
|
+
app.opts[:default_headers] = (app.default_headers || app::RodaResponse::DEFAULT_HEADERS).merge(headers).freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
# The default response headers to use for the current class.
|
31
|
+
def default_headers
|
32
|
+
opts[:default_headers]
|
33
|
+
end
|
27
34
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
if
|
32
|
-
|
33
|
-
|
35
|
+
# Optimize the response class set_default_headers method if it hasn't been
|
36
|
+
# overridden and all default headers are strings.
|
37
|
+
def freeze
|
38
|
+
if (headers = opts[:default_headers]).all?{|k, v| k.is_a?(String) && v.is_a?(String)} &&
|
39
|
+
(self::RodaResponse.instance_method(:set_default_headers).owner == Base::ResponseMethods)
|
40
|
+
self::RodaResponse.class_eval(<<-END, __FILE__, __LINE__+1)
|
34
41
|
private
|
35
42
|
|
36
|
-
alias set_default_headers set_default_headers
|
37
43
|
def set_default_headers
|
38
44
|
h = @headers
|
39
45
|
#{headers.map{|k,v| "h[#{k.inspect}] ||= #{v.inspect}"}.join('; ')}
|
40
46
|
end
|
41
47
|
END
|
42
48
|
end
|
43
|
-
end
|
44
|
-
end
|
45
49
|
|
46
|
-
|
47
|
-
# The default response headers to use for the current class.
|
48
|
-
def default_headers
|
49
|
-
opts[:default_headers]
|
50
|
+
super
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The host_authorization plugin allows configuring an authorized host or
|
7
|
+
# an array of authorized hosts. Then in the routing tree, you can check
|
8
|
+
# whether the request uses an authorized host via the +check_host_authorized!+
|
9
|
+
# method.
|
10
|
+
#
|
11
|
+
# If the request doesn't match one of the authorized hosts, the
|
12
|
+
# request processing stops at that point. Using this plugin can prevent
|
13
|
+
# DNS rebinding attacks if the application can receive requests for
|
14
|
+
# arbitrary hosts.
|
15
|
+
#
|
16
|
+
# By default, an empty response using status 403 will be returned for requests
|
17
|
+
# with unauthorized hosts.
|
18
|
+
#
|
19
|
+
# Because +check_host_authorized!+ is an instance method, you can easily choose
|
20
|
+
# to only check for authorization in certain routes, or to check it after
|
21
|
+
# other processing. For example, you could check for authorized hosts after
|
22
|
+
# serving static files, since the serving of static files should not be
|
23
|
+
# vulnerable to DNS rebinding attacks.
|
24
|
+
#
|
25
|
+
# = Usage
|
26
|
+
#
|
27
|
+
# In your routing tree, call the +check_host_authorized!+ method at the point you
|
28
|
+
# want to check for authorized hosts:
|
29
|
+
#
|
30
|
+
# plugin :host_authorization, 'www.example.com'
|
31
|
+
# plugin :public
|
32
|
+
#
|
33
|
+
# route do |r|
|
34
|
+
# r.public
|
35
|
+
# check_host_authorized!
|
36
|
+
#
|
37
|
+
# # ...
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# = Specifying authorized hosts
|
41
|
+
#
|
42
|
+
# For applications hosted on a single domain name, you can use a single string:
|
43
|
+
#
|
44
|
+
# plugin :host_authorization, 'www.example.com'
|
45
|
+
#
|
46
|
+
# For applications hosted on multiple domain names, you can use an array of strings:
|
47
|
+
#
|
48
|
+
# plugin :host_authorization, %w'www.example.com www.example2.com'
|
49
|
+
#
|
50
|
+
# For applications supporting arbitrary subdomains, you can use a regexp. If using
|
51
|
+
# a regexp, make sure you use <tt>\A<tt> and <tt>\z</tt> in your regexp, and restrict
|
52
|
+
# the allowed characters to the minimum required, otherwise you can potentionally
|
53
|
+
# introduce a security issue:
|
54
|
+
#
|
55
|
+
# plugin :host_authorization, /\A[-0-9a-f]+\.example\.com\z/
|
56
|
+
#
|
57
|
+
# For applications with more complex requirements, you can use a proc. Similarly
|
58
|
+
# to the regexp case, the proc should be aware the host contains user-submitted
|
59
|
+
# values, and not assume it is in any particular format:
|
60
|
+
#
|
61
|
+
# plugin :host_authorization, proc{|host| ExternalService.allowed_host?(host)}
|
62
|
+
#
|
63
|
+
# If an array of values is passed as the host argument, the host is authorized if
|
64
|
+
# it matches any value in the array. All host authorization checks use the
|
65
|
+
# <tt>===</tt> method, which is why it works for strings, regexps, and procs.
|
66
|
+
# It can also work with arbitrary objects that support <tt>===</tt>.
|
67
|
+
#
|
68
|
+
# For security reasons, only the +Host+ header is checked by default. If you are
|
69
|
+
# sure that your application is being run behind a forwarding proxy that sets the
|
70
|
+
# <tt>X-Forwarded-Host</tt> header, you should enable support for checking that
|
71
|
+
# header using the +:check_forwarded+ option:
|
72
|
+
#
|
73
|
+
# plugin :host_authorization, 'www.example.com', check_forwarded: true
|
74
|
+
#
|
75
|
+
# In this case, the trailing host in the <tt>X-Forwarded-Host</tt> header is checked,
|
76
|
+
# which should be the host set by the forwarding proxy closest to the application.
|
77
|
+
# In cases where multiple forwarding proxies are used that append to the
|
78
|
+
# <tt>X-Forwarded-Host</tt> header, you should not use this plugin.
|
79
|
+
#
|
80
|
+
# = Customizing behavior
|
81
|
+
#
|
82
|
+
# By default, an unauthorized host will receive an empty 403 response. You can
|
83
|
+
# customize this by passing a block when loading the plugin. For example, for
|
84
|
+
# sites using the render plugin, you could return a page that uses your default
|
85
|
+
# layout:
|
86
|
+
#
|
87
|
+
# plugin :render
|
88
|
+
# plugin :host_authorization, 'www.example.com' do |r|
|
89
|
+
# response.status = 403
|
90
|
+
# view(:content=>"<h1>Forbidden</h1>")
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# The block passed to this plugin is treated as a match block.
|
94
|
+
module HostAuthorization
|
95
|
+
def self.configure(app, host, opts=OPTS, &block)
|
96
|
+
app.opts[:host_authorization_host] = host
|
97
|
+
app.opts[:host_authorization_check_forwarded] = opts[:check_forwarded] if opts.key?(:check_forwarded)
|
98
|
+
|
99
|
+
if block
|
100
|
+
app.define_roda_method(:host_authorization_unauthorized, 1, &block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
module InstanceMethods
|
105
|
+
# Check whether the host is authorized. If not authorized, return a response
|
106
|
+
# immediately based on the plugin block.
|
107
|
+
def check_host_authorization!
|
108
|
+
r = @_request
|
109
|
+
return if host_authorized?(_convert_host_for_authorization(r.env["HTTP_HOST"].to_s.dup))
|
110
|
+
|
111
|
+
if opts[:host_authorization_check_forwarded] && (host = r.env["HTTP_X_FORWARDED_HOST"])
|
112
|
+
if i = host.rindex(',')
|
113
|
+
host = host[i+1, 10000000].to_s
|
114
|
+
end
|
115
|
+
host = _convert_host_for_authorization(host.strip)
|
116
|
+
|
117
|
+
if !host.empty? && host_authorized?(host)
|
118
|
+
return
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
r.on do
|
123
|
+
host_authorization_unauthorized(r)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# Remove the port information from the passed string (mutates the passed argument).
|
130
|
+
def _convert_host_for_authorization(host)
|
131
|
+
host.sub!(/:\d+\z/, "")
|
132
|
+
host
|
133
|
+
end
|
134
|
+
|
135
|
+
# Whether the host given is one of the authorized hosts for this application.
|
136
|
+
def host_authorized?(host, authorized_host = opts[:host_authorization_host])
|
137
|
+
case authorized_host
|
138
|
+
when Array
|
139
|
+
authorized_host.any?{|auth_host| host_authorized?(host, auth_host)}
|
140
|
+
else
|
141
|
+
authorized_host === host
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Action to take for unauthorized hosts. Sets a 403 status by default.
|
146
|
+
def host_authorization_unauthorized(_)
|
147
|
+
@_response.status = 403
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
register_plugin(:host_authorization, HostAuthorization)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
@@ -13,32 +13,33 @@ class Roda
|
|
13
13
|
# all of the child processes can use the same precompiled templates, which
|
14
14
|
# saves memory.
|
15
15
|
#
|
16
|
-
#
|
17
|
-
# the
|
16
|
+
# Another advantage of the precompile_templates plugin is that after
|
17
|
+
# template precompilation, access to the template file in the file system is
|
18
|
+
# no longer needed, so this can be used with security features that do not
|
19
|
+
# allow access to the template files at runtime.
|
20
|
+
#
|
21
|
+
# After loading the plugin, you should call precompile_views with an array
|
22
|
+
# of views to compile, using the same argument you are passing to view or
|
23
|
+
# render:
|
18
24
|
#
|
19
25
|
# plugin :precompile_templates
|
20
|
-
#
|
26
|
+
# precompile_views %w'view1 view2'
|
27
|
+
#
|
28
|
+
# If the view requires local variables, you should call precompile_views with a second
|
29
|
+
# argument for the local variables:
|
21
30
|
#
|
22
|
-
#
|
23
|
-
#
|
31
|
+
# plugin :precompile_templates
|
32
|
+
# precompile_views :view3, [:local_var1, :local_var2]
|
24
33
|
#
|
25
|
-
#
|
26
|
-
#
|
34
|
+
# After all templates are precompiled, you can optionally use freeze_template_caches!,
|
35
|
+
# which will freeze the template caches so that any template compilation at runtime
|
36
|
+
# will result in an error. This also speeds up template cache access, since the
|
37
|
+
# template caches no longer need a mutex.
|
27
38
|
#
|
28
|
-
#
|
39
|
+
# freeze_template_caches!
|
29
40
|
#
|
30
41
|
# Note that you should use Tilt 2.0.1+ if you are using this plugin, so
|
31
42
|
# that locals are handled in the same order.
|
32
|
-
#
|
33
|
-
# You can specify other render options when calling +precompile_templates+,
|
34
|
-
# including +:cache_key+, +:template_class+, and +:template_opts+. If you
|
35
|
-
# are passing any of those options to render/view for the template, you
|
36
|
-
# should pass the same options when precompiling the template.
|
37
|
-
#
|
38
|
-
# To compile inline templates, just pass a single hash containing an :inline
|
39
|
-
# to +precompile_templates+:
|
40
|
-
#
|
41
|
-
# precompile_templates inline: some_template_string
|
42
43
|
module PrecompileTemplates
|
43
44
|
# Load the render plugin as precompile_templates depends on it.
|
44
45
|
def self.load_dependencies(app, opts=OPTS)
|
@@ -46,8 +47,49 @@ class Roda
|
|
46
47
|
end
|
47
48
|
|
48
49
|
module ClassMethods
|
49
|
-
#
|
50
|
-
#
|
50
|
+
# Freeze the template caches. Should be called after precompiling all templates during
|
51
|
+
# application startup, if you don't want to allow templates to be cached at runtime.
|
52
|
+
# In addition to ensuring that no templates are compiled at runtime, this also speeds
|
53
|
+
# up rendering by freezing the template caches, so that a mutex is not needed to access
|
54
|
+
# them.
|
55
|
+
def freeze_template_caches!
|
56
|
+
_freeze_layout_method
|
57
|
+
|
58
|
+
opts[:render] = render_opts.merge(
|
59
|
+
:cache=>render_opts[:cache].freeze,
|
60
|
+
:template_method_cache=>render_opts[:template_method_cache].freeze,
|
61
|
+
).freeze
|
62
|
+
self::RodaCompiledTemplates.freeze
|
63
|
+
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# Precompile the templates using the given options. Note that this doesn't
|
68
|
+
# handle optimized template methods supported in newer versions of Roda, but
|
69
|
+
# there are still cases where makes sense to use it.
|
70
|
+
#
|
71
|
+
# You can call +precompile_templates+ with the pattern of templates you would
|
72
|
+
# like to precompile:
|
73
|
+
#
|
74
|
+
# precompile_templates "views/**/*.erb"
|
75
|
+
#
|
76
|
+
# That will precompile all erb template files in the views directory or
|
77
|
+
# any subdirectory.
|
78
|
+
#
|
79
|
+
# If the templates use local variables, you need to specify which local
|
80
|
+
# variables to precompile, which should be an array of symbols:
|
81
|
+
#
|
82
|
+
# precompile_templates 'views/users/_*.erb', locals: [:user]
|
83
|
+
#
|
84
|
+
# You can specify other render options when calling +precompile_templates+,
|
85
|
+
# including +:cache_key+, +:template_class+, and +:template_opts+. If you
|
86
|
+
# are passing any of those options to render/view for the template, you
|
87
|
+
# should pass the same options when precompiling the template.
|
88
|
+
#
|
89
|
+
# To compile inline templates, just pass a single hash containing an :inline
|
90
|
+
# to +precompile_templates+:
|
91
|
+
#
|
92
|
+
# precompile_templates inline: some_template_string
|
51
93
|
def precompile_templates(pattern, opts=OPTS)
|
52
94
|
if pattern.is_a?(Hash)
|
53
95
|
opts = pattern.merge(opts)
|
@@ -68,7 +110,40 @@ class Roda
|
|
68
110
|
instance = allocate
|
69
111
|
compile_opts.each do |compile_opt|
|
70
112
|
template = instance.send(:retrieve_template, compile_opt)
|
71
|
-
|
113
|
+
begin
|
114
|
+
Render.tilt_template_compiled_method(template, locals, self)
|
115
|
+
rescue NotImplementedError
|
116
|
+
# When freezing template caches, you may want to precompile a template for a
|
117
|
+
# template type that doesn't support template precompilation, just to populate
|
118
|
+
# the cache. Tilt rescues NotImplementedError in this case, which we can ignore.
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
# Precompile the given views with the given locals, handling optimized template methods.
|
127
|
+
def precompile_views(views, locals=EMPTY_ARRAY)
|
128
|
+
instance = allocate
|
129
|
+
views = Array(views)
|
130
|
+
|
131
|
+
if locals.empty?
|
132
|
+
opts = OPTS
|
133
|
+
else
|
134
|
+
locals_hash = {}
|
135
|
+
locals.each{|k| locals_hash[k] = nil}
|
136
|
+
opts = {:locals=>locals_hash}
|
137
|
+
end
|
138
|
+
|
139
|
+
views.each do |view|
|
140
|
+
instance.send(:retrieve_template, instance.send(:render_template_opts, view, opts))
|
141
|
+
end
|
142
|
+
|
143
|
+
if locals_hash
|
144
|
+
views.each do |view|
|
145
|
+
instance.send(:_optimized_render_method_for_locals, view, locals_hash)
|
146
|
+
end
|
72
147
|
end
|
73
148
|
|
74
149
|
nil
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The recheck_precompiled_assets plugin enables checking for the precompiled asset metadata file.
|
7
|
+
# You need to have already loaded the assets plugin with the +:precompiled+ option and the file
|
8
|
+
# specified by the +:precompiled+ option must already exist in order to use the
|
9
|
+
# recheck_precompiled_assets plugin.
|
10
|
+
#
|
11
|
+
# Any time you want to check whether the precompiled asset metadata file has changed and should be
|
12
|
+
# reloaded, you can call the +recheck_precompiled_assets+ class method. This method will check
|
13
|
+
# whether the file has changed, and reload it if it has. If you want to check for modifications on
|
14
|
+
# every request, you can use +self.class.recheck_precompiled_assets+ inside your route block.
|
15
|
+
module RecheckPrecompiledAssets
|
16
|
+
# Thread safe wrapper for the compiled asset metadata hash. Does not wrap all
|
17
|
+
# hash methods, only a few that are used.
|
18
|
+
class CompiledAssetsHash
|
19
|
+
include Enumerable
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@hash = {}
|
23
|
+
@mutex = Mutex.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](key)
|
27
|
+
@mutex.synchronize{@hash[key]}
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(key, value)
|
31
|
+
@mutex.synchronize{@hash[key] = value}
|
32
|
+
end
|
33
|
+
|
34
|
+
def replace(hash)
|
35
|
+
hash = hash.instance_variable_get(:@hash) if (CompiledAssetsHash === hash)
|
36
|
+
@mutex.synchronize{@hash.replace(hash)}
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def each(&block)
|
41
|
+
@mutex.synchronize{@hash.dup}.each(&block)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_json(*args)
|
46
|
+
@mutex.synchronize{@hash.dup}.to_json(*args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.load_dependencies(app)
|
51
|
+
unless app.respond_to?(:assets_opts) && app.assets_opts[:precompiled]
|
52
|
+
raise RodaError, "must load assets plugin with precompiled option before loading recheck_precompiled_assets plugin"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.configure(app)
|
57
|
+
precompiled_file = app.assets_opts[:precompiled]
|
58
|
+
prev_mtime = ::File.mtime(precompiled_file)
|
59
|
+
app.instance_exec do
|
60
|
+
opts[:assets] = opts[:assets].merge(:compiled=>_compiled_assets_initial_hash.replace(assets_opts[:compiled])).freeze
|
61
|
+
|
62
|
+
define_singleton_method(:recheck_precompiled_assets) do
|
63
|
+
new_mtime = ::File.mtime(precompiled_file)
|
64
|
+
if new_mtime != prev_mtime
|
65
|
+
prev_mtime = new_mtime
|
66
|
+
assets_opts[:compiled].replace(_precompiled_asset_metadata(precompiled_file))
|
67
|
+
|
68
|
+
# Unset the cached asset matchers, so new ones will be generated.
|
69
|
+
# This is needed in case the new precompiled metadata uses
|
70
|
+
# different files.
|
71
|
+
app::RodaRequest.instance_variable_set(:@assets_matchers, nil)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
singleton_class.send(:alias_method, :recheck_precompiled_assets, :recheck_precompiled_assets)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
module ClassMethods
|
79
|
+
private
|
80
|
+
|
81
|
+
# Wrap the precompiled asset metadata in a thread-safe hash.
|
82
|
+
def _precompiled_asset_metadata(file)
|
83
|
+
CompiledAssetsHash.new.replace(super)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Use a thread-safe wrapper of a hash for the :compiled assets option, since
|
87
|
+
# the recheck_precompiled_asset_metadata can modify it at runtime.
|
88
|
+
def _compiled_assets_initial_hash
|
89
|
+
CompiledAssetsHash.new
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
module RequestClassMethods
|
94
|
+
private
|
95
|
+
|
96
|
+
# Use a regexp that matches any digest. When the precompiled asset metadata
|
97
|
+
# file is updated, this allows requests for a previous precompiled asset to
|
98
|
+
# still work.
|
99
|
+
def _asset_regexp(type, key, _)
|
100
|
+
/#{Regexp.escape(key.sub(/\A#{type}/, ''))}\.[0-9a-fA-F]+\.#{type}/
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
register_plugin(:recheck_precompiled_assets, RecheckPrecompiledAssets)
|
106
|
+
end
|
107
|
+
end
|
data/lib/roda/plugins/render.rb
CHANGED
@@ -183,6 +183,7 @@ class Roda
|
|
183
183
|
app.const_set(:RodaCompiledTemplates, compiled_templates_module)
|
184
184
|
end
|
185
185
|
opts[:template_method_cache] = orig_method_cache || (opts[:cache_class] || RodaCache).new
|
186
|
+
opts[:template_method_cache][:_roda_layout] = nil if opts[:template_method_cache][:_roda_layout]
|
186
187
|
opts[:cache] = orig_cache || (opts[:cache_class] || RodaCache).new
|
187
188
|
|
188
189
|
opts[:layout_opts] = (opts[:layout_opts] || {}).dup
|
@@ -333,6 +334,25 @@ class Roda
|
|
333
334
|
end
|
334
335
|
|
335
336
|
module ClassMethods
|
337
|
+
# :nocov:
|
338
|
+
if COMPILED_METHOD_SUPPORT
|
339
|
+
# :nocov:
|
340
|
+
# If using compiled methods and there is an optimized layout, speed up
|
341
|
+
# access to the layout method to improve the performance of view.
|
342
|
+
def freeze
|
343
|
+
begin
|
344
|
+
_freeze_layout_method
|
345
|
+
rescue
|
346
|
+
# This is only for optimization, if any errors occur, they can be ignored.
|
347
|
+
# One possibility for error is the app doesn't use a layout, but doesn't
|
348
|
+
# specifically set the :layout=>false plugin option.
|
349
|
+
nil
|
350
|
+
end
|
351
|
+
|
352
|
+
super
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
336
356
|
# Copy the rendering options into the subclass, duping
|
337
357
|
# them as necessary to prevent changes in the subclass
|
338
358
|
# affecting the parent class.
|
@@ -352,6 +372,29 @@ class Roda
|
|
352
372
|
def render_opts
|
353
373
|
opts[:render]
|
354
374
|
end
|
375
|
+
|
376
|
+
private
|
377
|
+
|
378
|
+
# Precompile the layout method, to reduce method calls to look it up at runtime.
|
379
|
+
def _freeze_layout_method
|
380
|
+
if render_opts[:layout]
|
381
|
+
instance = allocate
|
382
|
+
instance.send(:retrieve_template, instance.send(:view_layout_opts, OPTS))
|
383
|
+
|
384
|
+
# :nocov:
|
385
|
+
if COMPILED_METHOD_SUPPORT
|
386
|
+
# :nocov:
|
387
|
+
if (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
|
388
|
+
instance.send(:retrieve_template, :template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
|
389
|
+
layout_method = opts[:render][:template_method_cache][:_roda_layout]
|
390
|
+
define_method(:_layout_method){layout_method}
|
391
|
+
private :_layout_method
|
392
|
+
alias_method(:_layout_method, :_layout_method)
|
393
|
+
opts[:render] = opts[:render].merge(:optimized_layout_method_created=>true)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
355
398
|
end
|
356
399
|
|
357
400
|
module InstanceMethods
|
@@ -375,19 +418,21 @@ class Roda
|
|
375
418
|
# Render the given template. If there is a default layout
|
376
419
|
# for the class, take the result of the template rendering
|
377
420
|
# and render it inside the layout. See Render for details.
|
378
|
-
def view(template, opts = (
|
379
|
-
if
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
if
|
384
|
-
|
385
|
-
|
386
|
-
retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
|
387
|
-
layout_method = method_cache[:_roda_layout]
|
388
|
-
end
|
421
|
+
def view(template, opts = (content = _optimized_view_content(template); OPTS))
|
422
|
+
if content
|
423
|
+
# First, check if the optimized layout method has already been created,
|
424
|
+
# and use it if so. This way avoids the extra conditional and local variable
|
425
|
+
# assignments in the next section.
|
426
|
+
if layout_method = _layout_method
|
427
|
+
return send(layout_method, OPTS){content}
|
428
|
+
end
|
389
429
|
|
390
|
-
|
430
|
+
# If we have an optimized template method but no optimized layout method, create the
|
431
|
+
# optimized layout method if possible and use it. If you can't create the optimized
|
432
|
+
# layout method, fall through to the slower approach.
|
433
|
+
if layout_template = self.class.opts[:render][:optimize_layout]
|
434
|
+
retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
|
435
|
+
if layout_method = _layout_method
|
391
436
|
return send(layout_method, OPTS){content}
|
392
437
|
end
|
393
438
|
end
|
@@ -428,6 +473,11 @@ class Roda
|
|
428
473
|
method_cache[template]
|
429
474
|
end
|
430
475
|
|
476
|
+
# Return a symbol containing the optimized layout method
|
477
|
+
def _layout_method
|
478
|
+
self.class.opts[:render][:template_method_cache][:_roda_layout]
|
479
|
+
end
|
480
|
+
|
431
481
|
# Use an optimized render path for templates with a hash of locals. Returns the result
|
432
482
|
# of the template render if the optimized path is used, or nil if the optimized
|
433
483
|
# path is not used and the long method needs to be used.
|
@@ -469,19 +519,37 @@ class Roda
|
|
469
519
|
end
|
470
520
|
end
|
471
521
|
end
|
522
|
+
|
523
|
+
# Get the content for #view, or return nil to use the unoptimized approach. Only called if
|
524
|
+
# a single argument is passed to view.
|
525
|
+
def _optimized_view_content(template)
|
526
|
+
if optimized_template = _cached_template_method(template)
|
527
|
+
send(optimized_template, OPTS)
|
528
|
+
elsif template.is_a?(Hash) && template.length == 1
|
529
|
+
template[:content]
|
530
|
+
end
|
531
|
+
end
|
472
532
|
else
|
473
533
|
# :nocov:
|
474
|
-
def _cached_template_method(
|
534
|
+
def _cached_template_method(_)
|
475
535
|
nil
|
476
536
|
end
|
477
537
|
|
478
|
-
def _cached_template_method_key(
|
538
|
+
def _cached_template_method_key(_)
|
539
|
+
nil
|
540
|
+
end
|
541
|
+
|
542
|
+
def _layout_method
|
479
543
|
nil
|
480
544
|
end
|
481
545
|
|
482
546
|
def _optimized_render_method_for_locals(_, _)
|
483
547
|
nil
|
484
548
|
end
|
549
|
+
|
550
|
+
def _optimized_view_content(template)
|
551
|
+
nil
|
552
|
+
end
|
485
553
|
# :nocov:
|
486
554
|
end
|
487
555
|
|
data/lib/roda/version.rb
CHANGED
@@ -4,11 +4,11 @@ class Roda
|
|
4
4
|
RodaMajorVersion = 3
|
5
5
|
|
6
6
|
# The minor version of Roda, updated for new feature releases of Roda.
|
7
|
-
RodaMinorVersion =
|
7
|
+
RodaMinorVersion = 43
|
8
8
|
|
9
9
|
# The patch version of Roda, updated only for bug fixes from the last
|
10
10
|
# feature release.
|
11
|
-
RodaPatchVersion =
|
11
|
+
RodaPatchVersion = 1
|
12
12
|
|
13
13
|
# The full version of Roda as a string.
|
14
14
|
RodaVersion = "#{RodaMajorVersion}.#{RodaMinorVersion}.#{RodaPatchVersion}".freeze
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roda
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.43.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -174,16 +174,8 @@ extra_rdoc_files:
|
|
174
174
|
- MIT-LICENSE
|
175
175
|
- CHANGELOG
|
176
176
|
- doc/conventions.rdoc
|
177
|
-
- doc/release_notes/3.7.0.txt
|
178
177
|
- doc/release_notes/3.0.0.txt
|
179
178
|
- doc/release_notes/3.1.0.txt
|
180
|
-
- doc/release_notes/3.2.0.txt
|
181
|
-
- doc/release_notes/3.3.0.txt
|
182
|
-
- doc/release_notes/3.4.0.txt
|
183
|
-
- doc/release_notes/3.5.0.txt
|
184
|
-
- doc/release_notes/3.6.0.txt
|
185
|
-
- doc/release_notes/3.8.0.txt
|
186
|
-
- doc/release_notes/3.9.0.txt
|
187
179
|
- doc/release_notes/3.10.0.txt
|
188
180
|
- doc/release_notes/3.11.0.txt
|
189
181
|
- doc/release_notes/3.12.0.txt
|
@@ -195,6 +187,7 @@ extra_rdoc_files:
|
|
195
187
|
- doc/release_notes/3.17.0.txt
|
196
188
|
- doc/release_notes/3.18.0.txt
|
197
189
|
- doc/release_notes/3.19.0.txt
|
190
|
+
- doc/release_notes/3.2.0.txt
|
198
191
|
- doc/release_notes/3.20.0.txt
|
199
192
|
- doc/release_notes/3.21.0.txt
|
200
193
|
- doc/release_notes/3.22.0.txt
|
@@ -205,6 +198,7 @@ extra_rdoc_files:
|
|
205
198
|
- doc/release_notes/3.27.0.txt
|
206
199
|
- doc/release_notes/3.28.0.txt
|
207
200
|
- doc/release_notes/3.29.0.txt
|
201
|
+
- doc/release_notes/3.3.0.txt
|
208
202
|
- doc/release_notes/3.30.0.txt
|
209
203
|
- doc/release_notes/3.31.0.txt
|
210
204
|
- doc/release_notes/3.32.0.txt
|
@@ -215,6 +209,16 @@ extra_rdoc_files:
|
|
215
209
|
- doc/release_notes/3.37.0.txt
|
216
210
|
- doc/release_notes/3.38.0.txt
|
217
211
|
- doc/release_notes/3.39.0.txt
|
212
|
+
- doc/release_notes/3.4.0.txt
|
213
|
+
- doc/release_notes/3.40.0.txt
|
214
|
+
- doc/release_notes/3.41.0.txt
|
215
|
+
- doc/release_notes/3.42.0.txt
|
216
|
+
- doc/release_notes/3.43.0.txt
|
217
|
+
- doc/release_notes/3.5.0.txt
|
218
|
+
- doc/release_notes/3.6.0.txt
|
219
|
+
- doc/release_notes/3.7.0.txt
|
220
|
+
- doc/release_notes/3.8.0.txt
|
221
|
+
- doc/release_notes/3.9.0.txt
|
218
222
|
files:
|
219
223
|
- CHANGELOG
|
220
224
|
- MIT-LICENSE
|
@@ -256,6 +260,10 @@ files:
|
|
256
260
|
- doc/release_notes/3.38.0.txt
|
257
261
|
- doc/release_notes/3.39.0.txt
|
258
262
|
- doc/release_notes/3.4.0.txt
|
263
|
+
- doc/release_notes/3.40.0.txt
|
264
|
+
- doc/release_notes/3.41.0.txt
|
265
|
+
- doc/release_notes/3.42.0.txt
|
266
|
+
- doc/release_notes/3.43.0.txt
|
259
267
|
- doc/release_notes/3.5.0.txt
|
260
268
|
- doc/release_notes/3.6.0.txt
|
261
269
|
- doc/release_notes/3.7.0.txt
|
@@ -306,6 +314,7 @@ files:
|
|
306
314
|
- lib/roda/plugins/header_matchers.rb
|
307
315
|
- lib/roda/plugins/heartbeat.rb
|
308
316
|
- lib/roda/plugins/hooks.rb
|
317
|
+
- lib/roda/plugins/host_authorization.rb
|
309
318
|
- lib/roda/plugins/indifferent_params.rb
|
310
319
|
- lib/roda/plugins/json.rb
|
311
320
|
- lib/roda/plugins/json_parser.rb
|
@@ -337,6 +346,7 @@ files:
|
|
337
346
|
- lib/roda/plugins/precompile_templates.rb
|
338
347
|
- lib/roda/plugins/public.rb
|
339
348
|
- lib/roda/plugins/r.rb
|
349
|
+
- lib/roda/plugins/recheck_precompiled_assets.rb
|
340
350
|
- lib/roda/plugins/relative_path.rb
|
341
351
|
- lib/roda/plugins/render.rb
|
342
352
|
- lib/roda/plugins/render_each.rb
|
@@ -394,7 +404,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
394
404
|
- !ruby/object:Gem::Version
|
395
405
|
version: '0'
|
396
406
|
requirements: []
|
397
|
-
rubygems_version: 3.
|
407
|
+
rubygems_version: 3.2.15
|
398
408
|
signing_key:
|
399
409
|
specification_version: 4
|
400
410
|
summary: Routing tree web toolkit
|