roda 3.108.0 → 3.109.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/roda/opts.rb +22 -0
- data/lib/roda/plugins/Integer_matcher_max.rb +1 -1
- data/lib/roda/plugins/_add_script_name.rb +14 -0
- data/lib/roda/plugins/_json.rb +22 -0
- data/lib/roda/plugins/_symbol_class_matchers.rb +6 -6
- data/lib/roda/plugins/additional_render_engines.rb +5 -1
- data/lib/roda/plugins/additional_view_directories.rb +5 -1
- data/lib/roda/plugins/assets.rb +7 -10
- data/lib/roda/plugins/autoload_hash_branches.rb +9 -3
- data/lib/roda/plugins/autoload_named_routes.rb +9 -3
- data/lib/roda/plugins/capture_erb.rb +5 -1
- data/lib/roda/plugins/chunked.rb +11 -5
- data/lib/roda/plugins/class_level_routing.rb +5 -3
- data/lib/roda/plugins/class_matchers.rb +3 -1
- data/lib/roda/plugins/common_logger.rb +7 -3
- data/lib/roda/plugins/conditional_sessions.rb +1 -1
- data/lib/roda/plugins/content_for.rb +5 -1
- data/lib/roda/plugins/content_security_policy.rb +6 -2
- data/lib/roda/plugins/cookie_flags.rb +6 -2
- data/lib/roda/plugins/cookies.rb +7 -3
- data/lib/roda/plugins/custom_block_results.rb +5 -3
- data/lib/roda/plugins/custom_matchers.rb +4 -2
- data/lib/roda/plugins/default_headers.rb +2 -5
- data/lib/roda/plugins/default_status.rb +1 -0
- data/lib/roda/plugins/each_part.rb +19 -23
- data/lib/roda/plugins/environments.rb +3 -7
- data/lib/roda/plugins/error_email.rb +9 -5
- data/lib/roda/plugins/error_handler.rb +5 -3
- data/lib/roda/plugins/error_mail.rb +6 -2
- data/lib/roda/plugins/hash_branch_view_subdir.rb +6 -4
- data/lib/roda/plugins/hash_branches.rb +7 -5
- data/lib/roda/plugins/hash_paths.rb +7 -5
- data/lib/roda/plugins/hash_public.rb +11 -7
- data/lib/roda/plugins/hash_public_cache.rb +9 -7
- data/lib/roda/plugins/hash_routes.rb +5 -3
- data/lib/roda/plugins/heartbeat.rb +6 -2
- data/lib/roda/plugins/hmac_paths.rb +9 -3
- data/lib/roda/plugins/hooks.rb +13 -10
- data/lib/roda/plugins/host_authorization.rb +7 -2
- data/lib/roda/plugins/host_routing.rb +7 -2
- data/lib/roda/plugins/invalid_request_body.rb +20 -4
- data/lib/roda/plugins/ip_from_header.rb +5 -1
- data/lib/roda/plugins/json.rb +10 -8
- data/lib/roda/plugins/json_parser.rb +26 -12
- data/lib/roda/plugins/mail_processor.rb +9 -6
- data/lib/roda/plugins/mailer.rb +5 -3
- data/lib/roda/plugins/match_affix.rb +6 -1
- data/lib/roda/plugins/match_hook_args.rb +7 -5
- data/lib/roda/plugins/middleware.rb +13 -7
- data/lib/roda/plugins/module_include.rb +1 -1
- data/lib/roda/plugins/multi_public.rb +6 -2
- data/lib/roda/plugins/multi_route.rb +1 -1
- data/lib/roda/plugins/multi_run.rb +13 -17
- data/lib/roda/plugins/named_routes.rb +8 -6
- data/lib/roda/plugins/named_templates.rb +5 -3
- data/lib/roda/plugins/path.rb +16 -14
- data/lib/roda/plugins/path_rewriter.rb +8 -5
- data/lib/roda/plugins/permissions_policy.rb +7 -3
- data/lib/roda/plugins/public.rb +11 -10
- data/lib/roda/plugins/recheck_precompiled_assets.rb +1 -1
- data/lib/roda/plugins/redirect_http_to_https.rb +6 -3
- data/lib/roda/plugins/render.rb +34 -49
- data/lib/roda/plugins/render_coverage.rb +6 -3
- data/lib/roda/plugins/render_locals.rb +12 -6
- data/lib/roda/plugins/request_aref.rb +1 -1
- data/lib/roda/plugins/response_content_type.rb +7 -3
- data/lib/roda/plugins/route_csrf.rb +7 -2
- data/lib/roda/plugins/run_append_slash.rb +5 -1
- data/lib/roda/plugins/sec_fetch_site_csrf.rb +6 -2
- data/lib/roda/plugins/sessions.rb +15 -11
- data/lib/roda/plugins/shape_friendly.rb +3 -1
- data/lib/roda/plugins/sinatra_helpers.rb +7 -3
- data/lib/roda/plugins/status_handler.rb +5 -3
- data/lib/roda/plugins/strip_path_prefix.rb +4 -2
- data/lib/roda/plugins/symbol_matchers.rb +3 -1
- data/lib/roda/plugins/symbol_views.rb +1 -1
- data/lib/roda/plugins/timestamp_public.rb +8 -4
- data/lib/roda/plugins/type_routing.rb +9 -5
- data/lib/roda/plugins/typecast_params.rb +2 -2
- data/lib/roda/plugins/typecast_params_sized_integers.rb +1 -1
- data/lib/roda/plugins/url_escape.rb +2 -14
- data/lib/roda/plugins.rb +10 -0
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +25 -14
- metadata +6 -3
|
@@ -53,10 +53,13 @@ class Roda
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
module ClassMethods
|
|
56
|
+
RodaPlugins.opt_attr_reader(self, :remaining_path_rewrites)
|
|
57
|
+
RodaPlugins.opt_attr_reader(self, :path_info_rewrites)
|
|
58
|
+
|
|
56
59
|
# Freeze the path rewrite metadata.
|
|
57
60
|
def freeze
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
remaining_path_rewrites.freeze
|
|
62
|
+
path_info_rewrites.freeze
|
|
60
63
|
super
|
|
61
64
|
end
|
|
62
65
|
|
|
@@ -75,7 +78,7 @@ class Roda
|
|
|
75
78
|
end
|
|
76
79
|
|
|
77
80
|
was = /\A#{Regexp.escape(was)}/ unless was.is_a?(Regexp)
|
|
78
|
-
array =
|
|
81
|
+
array = opts[:path_info] ? path_info_rewrites : remaining_path_rewrites
|
|
79
82
|
array << [was, is.dup.freeze].freeze
|
|
80
83
|
end
|
|
81
84
|
end
|
|
@@ -85,10 +88,10 @@ class Roda
|
|
|
85
88
|
def initialize(scope, env)
|
|
86
89
|
path_info = env['PATH_INFO']
|
|
87
90
|
|
|
88
|
-
rewrite_path(scope.class.
|
|
91
|
+
rewrite_path(scope.class.path_info_rewrites, path_info)
|
|
89
92
|
super
|
|
90
93
|
remaining_path = @remaining_path = @remaining_path.dup
|
|
91
|
-
rewrite_path(scope.class.
|
|
94
|
+
rewrite_path(scope.class.remaining_path_rewrites, remaining_path)
|
|
92
95
|
end
|
|
93
96
|
|
|
94
97
|
private
|
|
@@ -280,7 +280,7 @@ class Roda
|
|
|
280
280
|
|
|
281
281
|
# Yield the current Permissions Policy to the block.
|
|
282
282
|
def self.configure(app, opts=OPTS)
|
|
283
|
-
policy = app.opts[:permissions_policy] = if policy = app.
|
|
283
|
+
policy = app.opts[:permissions_policy] = if policy = app.permissions_policy
|
|
284
284
|
policy.dup
|
|
285
285
|
else
|
|
286
286
|
Policy.new
|
|
@@ -296,6 +296,10 @@ class Roda
|
|
|
296
296
|
policy.freeze
|
|
297
297
|
end
|
|
298
298
|
|
|
299
|
+
module ClassMethods
|
|
300
|
+
RodaPlugins.opt_attr_reader(self, :permissions_policy)
|
|
301
|
+
end
|
|
302
|
+
|
|
299
303
|
module InstanceMethods
|
|
300
304
|
# If a block is given, yield the current permission policy. Returns the
|
|
301
305
|
# current permissions policy.
|
|
@@ -315,7 +319,7 @@ class Roda
|
|
|
315
319
|
|
|
316
320
|
# The current permissions policy to be used for this response.
|
|
317
321
|
def permissions_policy
|
|
318
|
-
@permissions_policy ||= roda_class.
|
|
322
|
+
@permissions_policy ||= roda_class.permissions_policy.dup
|
|
319
323
|
end
|
|
320
324
|
|
|
321
325
|
# Do not set a permissions policy header for this response.
|
|
@@ -329,7 +333,7 @@ class Roda
|
|
|
329
333
|
def set_default_headers
|
|
330
334
|
super
|
|
331
335
|
unless @skip_permissions_policy
|
|
332
|
-
(@permissions_policy || roda_class.
|
|
336
|
+
(@permissions_policy || roda_class.permissions_policy).set_header(headers)
|
|
333
337
|
end
|
|
334
338
|
end
|
|
335
339
|
end
|
data/lib/roda/plugins/public.rb
CHANGED
|
@@ -69,19 +69,15 @@ class Roda
|
|
|
69
69
|
# :zstd :: Whether to serve already zstd-compressed files with a .zst extension
|
|
70
70
|
# for clients supporting "zstd" transfer encoding.
|
|
71
71
|
def self.configure(app, opts={})
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
elsif !app.opts[:public_root]
|
|
75
|
-
app.opts[:public_root] = app.expand_path("public")
|
|
76
|
-
end
|
|
77
|
-
app.opts[:public_server] = RACK_FILES.new(app.opts[:public_root], opts[:headers]||{}, opts[:default_mime] || 'text/plain')
|
|
72
|
+
app.opts[:public_root] = app.expand_path(opts[:root] || app.public_root || "public")
|
|
73
|
+
app.opts[:public_server] = RACK_FILES.new(app.public_root, opts[:headers]||{}, opts[:default_mime] || 'text/plain')
|
|
78
74
|
|
|
79
75
|
unless encodings = opts[:encodings]
|
|
80
76
|
if ENCODING_MAP.any?{|k,| opts.has_key?(k)}
|
|
81
77
|
encodings = ENCODING_MAP.map{|k, v| [v, ENCODING_EXTENSIONS[v]] if opts[k]}.compact
|
|
82
78
|
end
|
|
83
79
|
end
|
|
84
|
-
encodings = (encodings || app.
|
|
80
|
+
encodings = (encodings || app.public_encodings || EMPTY_ARRAY).map(&:dup).freeze
|
|
85
81
|
encodings.each do |a|
|
|
86
82
|
a << /\b#{a[0]}\b/
|
|
87
83
|
end
|
|
@@ -89,10 +85,16 @@ class Roda
|
|
|
89
85
|
app.opts[:public_encodings] = encodings
|
|
90
86
|
end
|
|
91
87
|
|
|
88
|
+
module ClassMethods
|
|
89
|
+
RodaPlugins.opt_attr_reader(self, :public_root)
|
|
90
|
+
RodaPlugins.opt_attr_reader(self, :public_server)
|
|
91
|
+
RodaPlugins.opt_attr_reader(self, :public_encodings)
|
|
92
|
+
end
|
|
93
|
+
|
|
92
94
|
module RequestMethods
|
|
93
95
|
# Serve files from the public directory if the file exists and this is a GET request.
|
|
94
96
|
def public
|
|
95
|
-
public_serve_with(roda_class.
|
|
97
|
+
public_serve_with(roda_class.public_server)
|
|
96
98
|
end
|
|
97
99
|
|
|
98
100
|
private
|
|
@@ -124,11 +126,10 @@ class Roda
|
|
|
124
126
|
path = PARSER.unescape(real_remaining_path)
|
|
125
127
|
return if path.include?("\0")
|
|
126
128
|
|
|
127
|
-
roda_opts = roda_class.opts
|
|
128
129
|
path = ::File.join(server.root, *public_path_segments(path))
|
|
129
130
|
|
|
130
131
|
if accept_encoding = env['HTTP_ACCEPT_ENCODING']
|
|
131
|
-
|
|
132
|
+
roda_class.public_encodings.each do |enc, ext, regexp|
|
|
132
133
|
if regexp.send(MATCH_METHOD, accept_encoding)
|
|
133
134
|
public_serve_compressed(server, path, ext, enc)
|
|
134
135
|
end
|
|
@@ -57,7 +57,7 @@ class Roda
|
|
|
57
57
|
precompiled_file = app.assets_opts[:precompiled]
|
|
58
58
|
prev_mtime = ::File.mtime(precompiled_file)
|
|
59
59
|
app.instance_exec do
|
|
60
|
-
opts[:assets] =
|
|
60
|
+
opts[:assets] = assets_opts.merge(:compiled=>_compiled_assets_initial_hash.replace(assets_opts[:compiled])).freeze
|
|
61
61
|
|
|
62
62
|
define_singleton_method(:recheck_precompiled_assets) do
|
|
63
63
|
new_mtime = ::File.mtime(precompiled_file)
|
|
@@ -53,13 +53,16 @@ class Roda
|
|
|
53
53
|
# uses a hash that redirects GET and HEAD requests with a 301 status,
|
|
54
54
|
# and other request methods with a 307 status.
|
|
55
55
|
def self.configure(app, opts=OPTS)
|
|
56
|
-
|
|
57
|
-
opts = app.opts[:redirect_http_to_https] = previous.merge(opts)
|
|
56
|
+
opts = app.opts[:redirect_http_to_https] = (app.redirect_http_to_https_opts || DEFAULTS).merge(opts)
|
|
58
57
|
opts[:port_string] = opts[:port] ? ":#{opts[:port]}".freeze : "".freeze
|
|
59
58
|
opts[:prefix] = opts[:host] ? "https://#{opts[:host]}#{opts[:port_string]}".freeze : nil
|
|
60
59
|
opts.freeze
|
|
61
60
|
end
|
|
62
61
|
|
|
62
|
+
module ClassMethods
|
|
63
|
+
RodaPlugins.opt_attr_reader(self, :redirect_http_to_https, name: :redirect_http_to_https_opts)
|
|
64
|
+
end
|
|
65
|
+
|
|
63
66
|
module RequestMethods
|
|
64
67
|
# Redirect HTTP requests to HTTPS. While this doesn't secure the
|
|
65
68
|
# current request, it makes it more likely that the browser will submit
|
|
@@ -67,7 +70,7 @@ class Roda
|
|
|
67
70
|
def redirect_http_to_https
|
|
68
71
|
return if ssl?
|
|
69
72
|
|
|
70
|
-
opts = roda_class.
|
|
73
|
+
opts = roda_class.redirect_http_to_https_opts
|
|
71
74
|
|
|
72
75
|
res = response
|
|
73
76
|
|
data/lib/roda/plugins/render.rb
CHANGED
|
@@ -323,15 +323,15 @@ class Roda
|
|
|
323
323
|
|
|
324
324
|
# Setup default rendering options. See Render for details.
|
|
325
325
|
def self.configure(app, opts=OPTS)
|
|
326
|
-
if app.
|
|
327
|
-
orig_cache = app.
|
|
328
|
-
orig_method_cache = app.
|
|
329
|
-
opts = app.
|
|
326
|
+
if app.render_opts
|
|
327
|
+
orig_cache = app.render_opts[:cache]
|
|
328
|
+
orig_method_cache = app.render_opts[:template_method_cache]
|
|
329
|
+
opts = app.render_opts[:orig_opts].merge(opts)
|
|
330
330
|
end
|
|
331
331
|
app.opts[:render] = opts.dup
|
|
332
|
-
app.
|
|
332
|
+
app.render_opts[:orig_opts] = opts
|
|
333
333
|
|
|
334
|
-
opts = app.
|
|
334
|
+
opts = app.render_opts
|
|
335
335
|
opts[:engine] = (opts[:engine] || "erb").dup.freeze
|
|
336
336
|
opts[:views] = app.expand_path(opts[:views]||"views").freeze
|
|
337
337
|
opts[:check_paths] = true unless opts.has_key?(:check_paths)
|
|
@@ -580,7 +580,7 @@ class Roda
|
|
|
580
580
|
# affecting the parent class.
|
|
581
581
|
def inherited(subclass)
|
|
582
582
|
super
|
|
583
|
-
opts = subclass.opts[:render] = subclass.
|
|
583
|
+
opts = subclass.opts[:render] = subclass.render_opts.dup
|
|
584
584
|
if COMPILED_METHOD_SUPPORT
|
|
585
585
|
opts[:template_method_cache] = (opts[:cache_class] || RodaCache).new
|
|
586
586
|
end
|
|
@@ -588,10 +588,7 @@ class Roda
|
|
|
588
588
|
opts.freeze
|
|
589
589
|
end
|
|
590
590
|
|
|
591
|
-
|
|
592
|
-
def render_opts
|
|
593
|
-
opts[:render]
|
|
594
|
-
end
|
|
591
|
+
RodaPlugins.opt_attr_reader(self, :render, name: :render_opts)
|
|
595
592
|
|
|
596
593
|
private
|
|
597
594
|
|
|
@@ -603,13 +600,13 @@ class Roda
|
|
|
603
600
|
# in order for the precompile_templates plugin to work correctly.
|
|
604
601
|
instance.send(:retrieve_template, instance.send(:view_layout_opts, OPTS))
|
|
605
602
|
|
|
606
|
-
if COMPILED_METHOD_SUPPORT && (layout_template = render_opts[:optimize_layout]) && !
|
|
603
|
+
if COMPILED_METHOD_SUPPORT && (layout_template = render_opts[:optimize_layout]) && !render_opts[:optimized_layout_method_created]
|
|
607
604
|
instance.send(:retrieve_template, :template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
|
|
608
|
-
layout_method =
|
|
605
|
+
layout_method = render_opts[:template_method_cache][:_roda_layout]
|
|
609
606
|
define_method(:_layout_method){layout_method}
|
|
610
607
|
private :_layout_method
|
|
611
608
|
alias_method(:_layout_method, :_layout_method)
|
|
612
|
-
opts[:render] =
|
|
609
|
+
opts[:render] = render_opts.merge(:optimized_layout_method_created=>true)
|
|
613
610
|
end
|
|
614
611
|
end
|
|
615
612
|
end
|
|
@@ -650,7 +647,7 @@ class Roda
|
|
|
650
647
|
# If we have an optimized template method but no optimized layout method, create the
|
|
651
648
|
# optimized layout method if possible and use it. If you can't create the optimized
|
|
652
649
|
# layout method, fall through to the slower approach.
|
|
653
|
-
if layout_template = self.class.
|
|
650
|
+
if layout_template = self.class.render_opts[:optimize_layout]
|
|
654
651
|
retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
|
|
655
652
|
if layout_method = _layout_method
|
|
656
653
|
return _call_optimized_template_method(layout_method, OPTS){content}
|
|
@@ -695,7 +692,7 @@ class Roda
|
|
|
695
692
|
|
|
696
693
|
# Return a symbol containing the optimized layout method
|
|
697
694
|
def _layout_method
|
|
698
|
-
self.class.
|
|
695
|
+
self.class.render_opts[:template_method_cache][:_roda_layout]
|
|
699
696
|
end
|
|
700
697
|
|
|
701
698
|
# Use an optimized render path for templates with a hash of locals. Returns the result
|
|
@@ -766,37 +763,25 @@ class Roda
|
|
|
766
763
|
end
|
|
767
764
|
|
|
768
765
|
if RUBY_VERSION >= '3'
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
send(meth, locals, &block)
|
|
775
|
-
end
|
|
766
|
+
def _call_optimized_template_method((meth, fixed_locals), locals, &block)
|
|
767
|
+
if fixed_locals
|
|
768
|
+
send(meth, **locals, &block)
|
|
769
|
+
else
|
|
770
|
+
send(meth, locals, &block)
|
|
776
771
|
end
|
|
777
|
-
|
|
772
|
+
end
|
|
778
773
|
# simplecov:disable
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if
|
|
783
|
-
|
|
784
|
-
send(meth, &block)
|
|
785
|
-
else
|
|
786
|
-
send(meth, **locals, &block)
|
|
787
|
-
end
|
|
774
|
+
else
|
|
775
|
+
def _call_optimized_template_method((meth, fixed_locals), locals, &block)
|
|
776
|
+
if fixed_locals
|
|
777
|
+
if locals.empty?
|
|
778
|
+
send(meth, &block)
|
|
788
779
|
else
|
|
789
|
-
send(meth, locals, &block)
|
|
780
|
+
send(meth, **locals, &block)
|
|
790
781
|
end
|
|
782
|
+
else
|
|
783
|
+
send(meth, locals, &block)
|
|
791
784
|
end
|
|
792
|
-
RUBY
|
|
793
|
-
else
|
|
794
|
-
# Call the optimized template method. This is designed to be used with the
|
|
795
|
-
# method cache, which caches the method name and whether the method uses
|
|
796
|
-
# fixed locals. Methods with fixed locals need to be called with a keyword
|
|
797
|
-
# splat.
|
|
798
|
-
def _call_optimized_template_method((meth, fixed_locals), locals, &block)
|
|
799
|
-
send(meth, locals, &block)
|
|
800
785
|
end
|
|
801
786
|
end
|
|
802
787
|
# simplecov:enable
|
|
@@ -844,7 +829,7 @@ class Roda
|
|
|
844
829
|
# Given the template name and options, set the template class, template path/content,
|
|
845
830
|
# template block, and locals to use for the render in the passed options.
|
|
846
831
|
def find_template(opts)
|
|
847
|
-
render_opts = self.class.
|
|
832
|
+
render_opts = self.class.render_opts
|
|
848
833
|
engine_override = opts[:engine]
|
|
849
834
|
engine = opts[:engine] ||= render_opts[:engine]
|
|
850
835
|
if content = opts[:inline]
|
|
@@ -908,7 +893,7 @@ class Roda
|
|
|
908
893
|
end
|
|
909
894
|
cached_template(opts) do
|
|
910
895
|
opts = found_template_opts || find_template(opts)
|
|
911
|
-
render_opts = self.class.
|
|
896
|
+
render_opts = self.class.render_opts
|
|
912
897
|
template_opts = render_opts[:template_opts]
|
|
913
898
|
if engine_opts = render_opts[:engine_opts][opts[:engine]]
|
|
914
899
|
template_opts = template_opts.merge(engine_opts)
|
|
@@ -993,14 +978,14 @@ class Roda
|
|
|
993
978
|
end
|
|
994
979
|
|
|
995
980
|
module AssumeFixedLocalsInstanceMethods
|
|
981
|
+
private
|
|
982
|
+
|
|
996
983
|
# simplecov:disable
|
|
997
984
|
if RUBY_VERSION >= '3.0'
|
|
998
985
|
# simplecov:enable
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
end
|
|
1003
|
-
RUBY
|
|
986
|
+
def _call_optimized_template_method((meth,_), locals, &block)
|
|
987
|
+
send(meth, **locals, &block)
|
|
988
|
+
end
|
|
1004
989
|
end
|
|
1005
990
|
end
|
|
1006
991
|
end
|
|
@@ -42,11 +42,14 @@ class Roda
|
|
|
42
42
|
# strip.
|
|
43
43
|
def self.configure(app, opts=OPTS)
|
|
44
44
|
app.opts[:render_coverage_strip_paths] = opts[:strip_paths].map{|f| File.expand_path(f)} if opts.has_key?(:strip_paths)
|
|
45
|
-
coverage_dir = app.opts[:render_coverage_dir] = opts[:dir] || app.
|
|
45
|
+
coverage_dir = app.opts[:render_coverage_dir] = opts[:dir] || app.render_coverage_dir || 'coverage/views'
|
|
46
46
|
Dir.mkdir(coverage_dir) unless File.directory?(coverage_dir)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
module ClassMethods
|
|
50
|
+
RodaPlugins.opt_attr_reader(self, :render_coverage_strip_paths)
|
|
51
|
+
RodaPlugins.opt_attr_reader(self, :render_coverage_dir)
|
|
52
|
+
|
|
50
53
|
# Set a compiled path on the created template, if the path for
|
|
51
54
|
# the template is in one of the allowed_views.
|
|
52
55
|
def create_template(opts, template_opts)
|
|
@@ -54,10 +57,10 @@ class Roda
|
|
|
54
57
|
|
|
55
58
|
path = File.expand_path(opts[:path])
|
|
56
59
|
compiled_path = nil
|
|
57
|
-
(
|
|
60
|
+
(render_coverage_strip_paths || render_opts[:allowed_paths]).each do |dir|
|
|
58
61
|
dir += "/" unless dir.end_with?("/")
|
|
59
62
|
if path.start_with?(dir)
|
|
60
|
-
compiled_path = File.join(
|
|
63
|
+
compiled_path = File.join(render_coverage_dir, path[dir.length, 10000000].tr('/', '-'))
|
|
61
64
|
break
|
|
62
65
|
end
|
|
63
66
|
end
|
|
@@ -32,14 +32,20 @@ class Roda
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def self.configure(app, opts=OPTS)
|
|
35
|
-
app.opts[:render_locals] = (app.
|
|
36
|
-
app.opts[:layout_locals] = (app.
|
|
35
|
+
app.opts[:render_locals] = (app.render_locals || {}).merge(opts[:render]||{}).freeze
|
|
36
|
+
app.opts[:layout_locals] = (app.layout_locals || {}).merge(opts[:layout]||{}).freeze
|
|
37
37
|
if opts.has_key?(:merge)
|
|
38
38
|
app.opts[:merge_locals] = opts[:merge]
|
|
39
|
-
app.opts[:layout_locals] = app.
|
|
39
|
+
app.opts[:layout_locals] = app.render_locals.merge(app.layout_locals).freeze
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
module ClassMethods
|
|
44
|
+
RodaPlugins.opt_attr_reader(self, :render_locals)
|
|
45
|
+
RodaPlugins.opt_attr_reader(self, :layout_locals)
|
|
46
|
+
RodaPlugins.opt_attr_reader(self, :merge_locals, name: :merge_locals?)
|
|
47
|
+
end
|
|
48
|
+
|
|
43
49
|
module InstanceMethods
|
|
44
50
|
private
|
|
45
51
|
|
|
@@ -56,11 +62,11 @@ class Roda
|
|
|
56
62
|
end
|
|
57
63
|
|
|
58
64
|
def render_locals
|
|
59
|
-
|
|
65
|
+
self.class.render_locals
|
|
60
66
|
end
|
|
61
67
|
|
|
62
68
|
def layout_locals
|
|
63
|
-
|
|
69
|
+
self.class.layout_locals
|
|
64
70
|
end
|
|
65
71
|
|
|
66
72
|
# If this isn't the layout template, then use the plugin's render locals as the default locals.
|
|
@@ -79,7 +85,7 @@ class Roda
|
|
|
79
85
|
# If using a layout, then use the plugin's layout locals as the default locals.
|
|
80
86
|
def view_layout_opts(opts)
|
|
81
87
|
if layout_opts = super
|
|
82
|
-
merge_locals = layout_opts.
|
|
88
|
+
merge_locals = layout_opts.fetch(:merge_locals){self.class.merge_locals?}
|
|
83
89
|
|
|
84
90
|
locals = {}
|
|
85
91
|
locals.merge!(layout_locals)
|
|
@@ -20,7 +20,7 @@ class Roda
|
|
|
20
20
|
def self.configure(app, setting)
|
|
21
21
|
case setting
|
|
22
22
|
when :allow, :raise, :warn
|
|
23
|
-
app::RodaRequest.
|
|
23
|
+
app::RodaRequest.class_exec do
|
|
24
24
|
alias_method(:[], :"request_aref_#{setting}")
|
|
25
25
|
alias_method(:[]=, :"request_aset_#{setting}")
|
|
26
26
|
public :[], :[]=
|
|
@@ -50,12 +50,16 @@ class Roda
|
|
|
50
50
|
else
|
|
51
51
|
mime_types.dup
|
|
52
52
|
end
|
|
53
|
-
app.opts[:
|
|
53
|
+
app.opts[:response_content_types] = mime_types.freeze
|
|
54
54
|
else
|
|
55
|
-
app.opts[:
|
|
55
|
+
app.opts[:response_content_types] ||= {}
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
module ClassMethods
|
|
60
|
+
RodaPlugins.opt_attr_reader(self, :response_content_types)
|
|
61
|
+
end
|
|
62
|
+
|
|
59
63
|
module ResponseMethods
|
|
60
64
|
# Return the content-type of the response. Will be nil if it has
|
|
61
65
|
# not yet been explicitly set.
|
|
@@ -68,7 +72,7 @@ class Roda
|
|
|
68
72
|
# type with the given file extension. If the symbol is not
|
|
69
73
|
# a recognized mime type, raises KeyError.
|
|
70
74
|
def content_type=(mime_type)
|
|
71
|
-
mime_type = roda_class.
|
|
75
|
+
mime_type = roda_class.response_content_types.fetch(mime_type) if mime_type.is_a?(Symbol)
|
|
72
76
|
@headers[RodaResponseHeaders::CONTENT_TYPE] = mime_type
|
|
73
77
|
end
|
|
74
78
|
end
|
|
@@ -196,7 +196,7 @@ class Roda
|
|
|
196
196
|
end
|
|
197
197
|
|
|
198
198
|
def self.configure(app, opts=OPTS, &block)
|
|
199
|
-
options = app.opts[:route_csrf] = (app.
|
|
199
|
+
options = app.opts[:route_csrf] = (app.route_csrf_opts || DEFAULTS).merge(opts)
|
|
200
200
|
if block || opts[:csrf_failure].is_a?(Proc)
|
|
201
201
|
if block && opts[:csrf_failure]
|
|
202
202
|
raise RodaError, "Cannot specify both route_csrf plugin block and :csrf_failure option"
|
|
@@ -212,6 +212,10 @@ class Roda
|
|
|
212
212
|
options.freeze
|
|
213
213
|
end
|
|
214
214
|
|
|
215
|
+
module ClassMethods
|
|
216
|
+
RodaPlugins.opt_attr_reader(self, :route_csrf, name: :route_csrf_opts)
|
|
217
|
+
end
|
|
218
|
+
|
|
215
219
|
module InstanceMethods
|
|
216
220
|
# Check that the submitted CSRF token is valid, if the request requires a CSRF token.
|
|
217
221
|
# If the CSRF token is valid or the request does not require a CSRF token, return nil.
|
|
@@ -358,6 +362,7 @@ class Roda
|
|
|
358
362
|
end
|
|
359
363
|
|
|
360
364
|
if (rack_csrf_key = opts[:upgrade_from_rack_csrf_key]) && (rack_csrf_value = session[rack_csrf_key]) && csrf_compare(rack_csrf_value, encoded_token)
|
|
365
|
+
# RODA4: Remove :upgrade_from_rack_csrf_key support
|
|
361
366
|
return
|
|
362
367
|
end
|
|
363
368
|
|
|
@@ -392,7 +397,7 @@ class Roda
|
|
|
392
397
|
|
|
393
398
|
# Helper for getting the plugin options.
|
|
394
399
|
def csrf_options
|
|
395
|
-
|
|
400
|
+
self.class.route_csrf_opts
|
|
396
401
|
end
|
|
397
402
|
|
|
398
403
|
# Perform a constant-time comparison of the two strings, returning true if they match and false otherwise.
|
|
@@ -31,6 +31,10 @@ class Roda
|
|
|
31
31
|
app.opts[:run_append_slash_redirect] = !!opts[:use_redirects]
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
module ClassMethods
|
|
35
|
+
RodaPlugins.opt_attr_reader(self, :run_append_slash_redirect, name: :run_append_slash_redirect?)
|
|
36
|
+
end
|
|
37
|
+
|
|
34
38
|
module RequestMethods
|
|
35
39
|
# Calls the given rack app. If the path matches the root of the app but
|
|
36
40
|
# does not contain a trailing slash, a trailing slash is appended to the
|
|
@@ -39,7 +43,7 @@ class Roda
|
|
|
39
43
|
# the redirect approach will not be used.
|
|
40
44
|
def run(*)
|
|
41
45
|
if @remaining_path.empty?
|
|
42
|
-
if
|
|
46
|
+
if roda_class.run_append_slash_redirect? && !(path = self.path).start_with?('//')
|
|
43
47
|
redirect("#{path}/")
|
|
44
48
|
else
|
|
45
49
|
@remaining_path += '/'
|
|
@@ -69,7 +69,7 @@ class Roda
|
|
|
69
69
|
class CsrfFailure < RodaError; end
|
|
70
70
|
|
|
71
71
|
def self.configure(app, opts=OPTS, &block)
|
|
72
|
-
options = app.opts[:sec_fetch_site_csrf] = (app.
|
|
72
|
+
options = app.opts[:sec_fetch_site_csrf] = (app.sec_fetch_site_csrf_opts || DEFAULTS).merge(opts)
|
|
73
73
|
|
|
74
74
|
allowed_values = options[:allowed_values] = ["same-origin"]
|
|
75
75
|
allowed_values << "same-site" if opts[:allow_same_site]
|
|
@@ -94,6 +94,10 @@ class Roda
|
|
|
94
94
|
options.freeze
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
module ClassMethods
|
|
98
|
+
RodaPlugins.opt_attr_reader(self, :sec_fetch_site_csrf, name: :sec_fetch_site_csrf_opts)
|
|
99
|
+
end
|
|
100
|
+
|
|
97
101
|
module InstanceMethods
|
|
98
102
|
# Check that the Sec-Fetch-Site header is valid, if the request requires it.
|
|
99
103
|
# If the header is valid or the request does not require the header, return nil.
|
|
@@ -101,7 +105,7 @@ class Roda
|
|
|
101
105
|
# if a block is not given, use the plugin :csrf_failure option to determine how to
|
|
102
106
|
# handle it.
|
|
103
107
|
def check_sec_fetch_site!(&block)
|
|
104
|
-
plugin_opts = self.class.
|
|
108
|
+
plugin_opts = self.class.sec_fetch_site_csrf_opts
|
|
105
109
|
return unless plugin_opts[:check_request_methods].include?(request.request_method)
|
|
106
110
|
|
|
107
111
|
sec_fetch_site = env["HTTP_SEC_FETCH_SITE"]
|
|
@@ -10,7 +10,6 @@ rescue OpenSSL::Cipher::CipherError
|
|
|
10
10
|
# simplecov:enable
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
require 'json'
|
|
14
13
|
require 'securerandom'
|
|
15
14
|
require 'zlib'
|
|
16
15
|
require 'rack/utils'
|
|
@@ -175,15 +174,16 @@ class Roda
|
|
|
175
174
|
|
|
176
175
|
def self.load_dependencies(app, opts=OPTS)
|
|
177
176
|
app.plugin :_base64
|
|
177
|
+
app.plugin :_json
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
# Configure the plugin, see Sessions for details on options.
|
|
181
181
|
def self.configure(app, opts=OPTS)
|
|
182
|
-
opts = (app.
|
|
182
|
+
opts = (app.sessions_opts || DEFAULT_OPTIONS).merge(opts)
|
|
183
183
|
co = opts[:cookie_options] = DEFAULT_COOKIE_OPTIONS.merge(opts[:cookie_options] || OPTS).freeze
|
|
184
184
|
opts[:remove_cookie_options] = co.merge(:max_age=>'0', :expires=>Time.at(0))
|
|
185
|
-
opts[:parser] ||= app.
|
|
186
|
-
opts[:serializer] ||= app.
|
|
185
|
+
opts[:parser] ||= app.json_parser
|
|
186
|
+
opts[:serializer] ||= app.json_serializer
|
|
187
187
|
|
|
188
188
|
opts[:per_cookie_cipher_secret] = true unless opts.has_key?(:per_cookie_cipher_secret)
|
|
189
189
|
opts[:session_version_num] = opts[:per_cookie_cipher_secret] ? 1 : 0
|
|
@@ -216,6 +216,10 @@ class Roda
|
|
|
216
216
|
app.opts[:sessions_convert_symbols] = true unless app.opts.has_key?(:sessions_convert_symbols)
|
|
217
217
|
end
|
|
218
218
|
|
|
219
|
+
module ClassMethods
|
|
220
|
+
RodaPlugins.opt_attr_reader(self, :sessions, name: :sessions_opts)
|
|
221
|
+
end
|
|
222
|
+
|
|
219
223
|
module InstanceMethods
|
|
220
224
|
# Clear data from the session, and update the request environment
|
|
221
225
|
# so that the session cookie will use a new creation timestamp
|
|
@@ -233,7 +237,7 @@ class Roda
|
|
|
233
237
|
# update the rack response headers to set the session cookie in
|
|
234
238
|
# the response.
|
|
235
239
|
def _roda_after_50__sessions(res)
|
|
236
|
-
if res && (session = env[self.class.
|
|
240
|
+
if res && (session = env[self.class.sessions_opts[:env_key]])
|
|
237
241
|
@_request.persist_session(res[1], session)
|
|
238
242
|
end
|
|
239
243
|
end
|
|
@@ -247,7 +251,7 @@ class Roda
|
|
|
247
251
|
# this method stores the session in 'rack.session' in the request environment,
|
|
248
252
|
# but that does not happen until this method is called.
|
|
249
253
|
def session
|
|
250
|
-
@env[roda_class.
|
|
254
|
+
@env[roda_class.sessions_opts[:env_key]] ||= _load_session
|
|
251
255
|
end
|
|
252
256
|
|
|
253
257
|
# The time the session was originally created. nil if there is no active session.
|
|
@@ -266,7 +270,7 @@ class Roda
|
|
|
266
270
|
# Rack::Session::Cookie, mark the related cookie for expiration so it isn't
|
|
267
271
|
# sent in the future.
|
|
268
272
|
def persist_session(headers, session)
|
|
269
|
-
opts = roda_class.
|
|
273
|
+
opts = roda_class.sessions_opts
|
|
270
274
|
|
|
271
275
|
if session.empty?
|
|
272
276
|
if env[SESSION_SERIALIZED]
|
|
@@ -317,7 +321,7 @@ class Roda
|
|
|
317
321
|
# Load the session by looking for the appropriate cookie, or falling
|
|
318
322
|
# back to the rack session cookie if configured.
|
|
319
323
|
def _load_session
|
|
320
|
-
opts = roda_class.
|
|
324
|
+
opts = roda_class.sessions_opts
|
|
321
325
|
cs = cookies
|
|
322
326
|
|
|
323
327
|
if data = cs[opts[:key]]
|
|
@@ -340,7 +344,7 @@ class Roda
|
|
|
340
344
|
# serialized session using the default Rack::Session::Cookie
|
|
341
345
|
# hmac and coder.
|
|
342
346
|
def _deserialize_rack_session(data)
|
|
343
|
-
opts = roda_class.
|
|
347
|
+
opts = roda_class.sessions_opts
|
|
344
348
|
data, digest = data.split("--", 2)
|
|
345
349
|
unless digest
|
|
346
350
|
return _session_serialization_error("Not decoding Rack::Session::Cookie session: invalid format")
|
|
@@ -374,7 +378,7 @@ class Roda
|
|
|
374
378
|
|
|
375
379
|
# Interpret given cookie data as a Rack::Session::Cookie
|
|
376
380
|
def _deserialize_session(data)
|
|
377
|
-
opts = roda_class.
|
|
381
|
+
opts = roda_class.sessions_opts
|
|
378
382
|
|
|
379
383
|
begin
|
|
380
384
|
data = Base64_.urlsafe_decode64(data)
|
|
@@ -466,7 +470,7 @@ class Roda
|
|
|
466
470
|
end
|
|
467
471
|
|
|
468
472
|
def _serialize_session(session)
|
|
469
|
-
opts = roda_class.
|
|
473
|
+
opts = roda_class.sessions_opts
|
|
470
474
|
env = @env
|
|
471
475
|
now = Time.now.to_i
|
|
472
476
|
json_data = opts[:serializer].call(session).force_encoding('BINARY')
|
|
@@ -74,6 +74,8 @@ class Roda
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
module ClassMethods
|
|
77
|
+
RodaPlugins.opt_attr_reader(self, :shape_friendly_scope_instance_variables)
|
|
78
|
+
|
|
77
79
|
# Automatically refresh the instance variables used if the plugin
|
|
78
80
|
# sets instance variables.
|
|
79
81
|
def plugin(plugin, *args, &block)
|
|
@@ -107,7 +109,7 @@ class Roda
|
|
|
107
109
|
ivs.concat(mod.const_get(const)) if mod.const_defined?(const)
|
|
108
110
|
end
|
|
109
111
|
|
|
110
|
-
if const == :SCOPE_INSTANCE_VARIABLES && (scope_ivs =
|
|
112
|
+
if const == :SCOPE_INSTANCE_VARIABLES && (scope_ivs = shape_friendly_scope_instance_variables)
|
|
111
113
|
ivs.concat(scope_ivs)
|
|
112
114
|
end
|
|
113
115
|
|