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
|
@@ -38,11 +38,9 @@ class Roda
|
|
|
38
38
|
|
|
39
39
|
module InstanceMethods
|
|
40
40
|
if RUBY_VERSION >= '2.7'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
end
|
|
45
|
-
RUBY
|
|
41
|
+
def each_part(enum, template, **locals, &block)
|
|
42
|
+
render_each(enum, template, :locals=>locals, &block)
|
|
43
|
+
end
|
|
46
44
|
# simplecov:disable
|
|
47
45
|
else
|
|
48
46
|
def each_part(enum, template, locals=OPTS, &block)
|
|
@@ -56,28 +54,26 @@ class Roda
|
|
|
56
54
|
# simplecov:disable
|
|
57
55
|
if RUBY_VERSION >= '3.0'
|
|
58
56
|
# simplecov:enable
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
yield send(optimized_method, **locals)
|
|
68
|
-
end
|
|
69
|
-
nil
|
|
70
|
-
else
|
|
71
|
-
enum.map do |v|
|
|
72
|
-
locals[as] = v
|
|
73
|
-
send(optimized_method, **locals)
|
|
74
|
-
end.join
|
|
57
|
+
def each_part(enum, template, **locals, &block)
|
|
58
|
+
if optimized_method = _cached_render_each_template_method(template)
|
|
59
|
+
optimized_method = optimized_method[0]
|
|
60
|
+
as = render_each_default_local(template)
|
|
61
|
+
if defined?(yield)
|
|
62
|
+
enum.each do |v|
|
|
63
|
+
locals[as] = v
|
|
64
|
+
yield send(optimized_method, **locals)
|
|
75
65
|
end
|
|
66
|
+
nil
|
|
76
67
|
else
|
|
77
|
-
|
|
68
|
+
enum.map do |v|
|
|
69
|
+
locals[as] = v
|
|
70
|
+
send(optimized_method, **locals)
|
|
71
|
+
end.join
|
|
78
72
|
end
|
|
73
|
+
else
|
|
74
|
+
render_each(enum, template, :locals=>locals, &block)
|
|
79
75
|
end
|
|
80
|
-
|
|
76
|
+
end
|
|
81
77
|
end
|
|
82
78
|
end
|
|
83
79
|
end
|
|
@@ -44,10 +44,12 @@ class Roda
|
|
|
44
44
|
# if no environment is given. If ENV['RACK_ENV'] is not set and
|
|
45
45
|
# no environment is given, assume the development environment.
|
|
46
46
|
def self.configure(app, env=ENV["RACK_ENV"])
|
|
47
|
-
app.environment = (env || 'development').to_sym
|
|
47
|
+
app.opts[:environment] = (env || 'development').to_sym
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
module ClassMethods
|
|
51
|
+
RodaPlugins.opt_attr_reader(self, :environment)
|
|
52
|
+
|
|
51
53
|
# If no environments are given or one of the given environments
|
|
52
54
|
# matches the current environment, yield the receiver to the block.
|
|
53
55
|
def configure(*envs)
|
|
@@ -56,12 +58,6 @@ class Roda
|
|
|
56
58
|
end
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
# The current environment for the application, which should be stored
|
|
60
|
-
# as a symbol.
|
|
61
|
-
def environment
|
|
62
|
-
opts[:environment]
|
|
63
|
-
end
|
|
64
|
-
|
|
65
61
|
# Override the environment for the application, instead of using
|
|
66
62
|
# RACK_ENV.
|
|
67
63
|
def environment=(v)
|
|
@@ -56,7 +56,7 @@ class Roda
|
|
|
56
56
|
{'From'=>h[:from], 'To'=>h[:to], 'Subject'=>"#{h[:prefix]}#{subject}"}
|
|
57
57
|
end,
|
|
58
58
|
:body=>lambda do |s, e|
|
|
59
|
-
filter = s.
|
|
59
|
+
filter = s.class.error_email_opts[:filter]
|
|
60
60
|
format = lambda do |h|
|
|
61
61
|
h = h.map{|k, v| "#{k.inspect} => #{filter.call(k, v) ? 'FILTERED' : v.inspect}"}
|
|
62
62
|
h.sort!
|
|
@@ -120,8 +120,12 @@ END
|
|
|
120
120
|
raise RodaError, "must provide :to and :from options to error_email plugin"
|
|
121
121
|
end
|
|
122
122
|
app.opts[:error_email] = email_opts
|
|
123
|
-
app.
|
|
124
|
-
app.
|
|
123
|
+
app.error_email_opts[:headers].freeze
|
|
124
|
+
app.error_email_opts.freeze
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
module ClassMethods
|
|
128
|
+
RodaPlugins.opt_attr_reader(self, :error_email, name: :error_email_opts)
|
|
125
129
|
end
|
|
126
130
|
|
|
127
131
|
module InstanceMethods
|
|
@@ -129,7 +133,7 @@ END
|
|
|
129
133
|
# instance, but it can be a plain string which is used as the subject for
|
|
130
134
|
# the email.
|
|
131
135
|
def error_email(exception)
|
|
132
|
-
email_opts = self.class.
|
|
136
|
+
email_opts = self.class.error_email_opts.dup
|
|
133
137
|
email_opts[:message] = error_email_content(exception)
|
|
134
138
|
email_opts[:emailer].call(email_opts)
|
|
135
139
|
end
|
|
@@ -137,7 +141,7 @@ END
|
|
|
137
141
|
# The content of the email to send, include the headers and the body.
|
|
138
142
|
# Takes the same argument as #error_email.
|
|
139
143
|
def error_email_content(exception)
|
|
140
|
-
email_opts = self.class.
|
|
144
|
+
email_opts = self.class.error_email_opts
|
|
141
145
|
headers = email_opts[:default_headers].call(email_opts, exception)
|
|
142
146
|
headers = headers.merge(email_opts[:headers])
|
|
143
147
|
headers = headers.map{|k,v| "#{k}: #{v.gsub(/\r?\n/m, "\r\n ")}"}.sort.join("\r\n")
|
|
@@ -49,7 +49,7 @@ class Roda
|
|
|
49
49
|
# If a block is given, automatically call the +error+ method on
|
|
50
50
|
# the Roda class with it.
|
|
51
51
|
def self.configure(app, opts={}, &block)
|
|
52
|
-
app.opts[:error_handler_classes] = (opts[:classes] || app.
|
|
52
|
+
app.opts[:error_handler_classes] = (opts[:classes] || app.error_handler_classes || DEFAULT_ERROR_HANDLER_CLASSES).dup.freeze
|
|
53
53
|
|
|
54
54
|
if block
|
|
55
55
|
app.error(&block)
|
|
@@ -57,6 +57,8 @@ class Roda
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
module ClassMethods
|
|
60
|
+
RodaPlugins.opt_attr_reader(self, :error_handler_classes)
|
|
61
|
+
|
|
60
62
|
# Install the given block as the error handler, so that if routing
|
|
61
63
|
# the request raises an exception, the block will be called with
|
|
62
64
|
# the exception in the scope of the Roda instance.
|
|
@@ -77,7 +79,7 @@ class Roda
|
|
|
77
79
|
ensure
|
|
78
80
|
_roda_after(res)
|
|
79
81
|
end
|
|
80
|
-
rescue *
|
|
82
|
+
rescue *self.class.error_handler_classes => e
|
|
81
83
|
_handle_error(e)
|
|
82
84
|
end
|
|
83
85
|
|
|
@@ -89,7 +91,7 @@ class Roda
|
|
|
89
91
|
ensure
|
|
90
92
|
_roda_after(res)
|
|
91
93
|
end
|
|
92
|
-
rescue *
|
|
94
|
+
rescue *self.class.error_handler_classes => e
|
|
93
95
|
_handle_error(e)
|
|
94
96
|
end
|
|
95
97
|
|
|
@@ -44,12 +44,16 @@ class Roda
|
|
|
44
44
|
|
|
45
45
|
# Set default opts for plugin. See ErrorEmail module RDoc for options.
|
|
46
46
|
def self.configure(app, opts=OPTS)
|
|
47
|
-
app.opts[:error_mail] = email_opts = (app.
|
|
47
|
+
app.opts[:error_mail] = email_opts = (app.error_mail_opts || {:filter=>DEFAULT_FILTER}).merge(opts).freeze
|
|
48
48
|
unless email_opts[:to] && email_opts[:from]
|
|
49
49
|
raise RodaError, "must provide :to and :from options to error_mail plugin"
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
module ClassMethods
|
|
54
|
+
RodaPlugins.opt_attr_reader(self, :error_mail, name: :error_mail_opts)
|
|
55
|
+
end
|
|
56
|
+
|
|
53
57
|
module InstanceMethods
|
|
54
58
|
# Send an email for the given error. +exception+ is usually an exception
|
|
55
59
|
# instance, but it can be a plain string which is used as the subject for
|
|
@@ -67,7 +71,7 @@ class Roda
|
|
|
67
71
|
private
|
|
68
72
|
|
|
69
73
|
def _error_mail(e)
|
|
70
|
-
email_opts = self.class.
|
|
74
|
+
email_opts = self.class.error_mail_opts
|
|
71
75
|
subject = if e.respond_to?(:message)
|
|
72
76
|
"#{e.class}: #{e.message}"
|
|
73
77
|
else
|
|
@@ -33,9 +33,11 @@ class Roda
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
module ClassMethods
|
|
36
|
+
RodaPlugins.opt_attr_reader(self, :hash_branch_view_subdir_methods)
|
|
37
|
+
|
|
36
38
|
# Freeze the hash_branch_view_subdir metadata when freezing the app.
|
|
37
39
|
def freeze
|
|
38
|
-
|
|
40
|
+
hash_branch_view_subdir_methods.freeze.each_value(&:freeze)
|
|
39
41
|
super
|
|
40
42
|
end
|
|
41
43
|
|
|
@@ -43,8 +45,8 @@ class Roda
|
|
|
43
45
|
def inherited(subclass)
|
|
44
46
|
super
|
|
45
47
|
|
|
46
|
-
h = subclass.
|
|
47
|
-
|
|
48
|
+
h = subclass.hash_branch_view_subdir_methods
|
|
49
|
+
hash_branch_view_subdir_methods.each do |namespace, routes|
|
|
48
50
|
h[namespace] = routes.dup
|
|
49
51
|
end
|
|
50
52
|
end
|
|
@@ -53,7 +55,7 @@ class Roda
|
|
|
53
55
|
# by modifying the generated method to append the view subdirectory before
|
|
54
56
|
# dispatching to the original block.
|
|
55
57
|
def hash_branch(namespace='', segment, &block)
|
|
56
|
-
meths =
|
|
58
|
+
meths = hash_branch_view_subdir_methods[namespace] ||= {}
|
|
57
59
|
|
|
58
60
|
if block
|
|
59
61
|
meth = meths[segment] = define_roda_method(meths[segment] || "_hash_branch_view_subdir_#{namespace}_#{segment}", 1, &convert_route_block(block))
|
|
@@ -88,9 +88,11 @@ class Roda
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
module ClassMethods
|
|
91
|
+
RodaPlugins.opt_attr_reader(self, :hash_branches)
|
|
92
|
+
|
|
91
93
|
# Freeze the hash_branches metadata when freezing the app.
|
|
92
94
|
def freeze
|
|
93
|
-
|
|
95
|
+
hash_branches.freeze.each_value(&:freeze)
|
|
94
96
|
super
|
|
95
97
|
end
|
|
96
98
|
|
|
@@ -98,8 +100,8 @@ class Roda
|
|
|
98
100
|
def inherited(subclass)
|
|
99
101
|
super
|
|
100
102
|
|
|
101
|
-
h = subclass.
|
|
102
|
-
|
|
103
|
+
h = subclass.hash_branches
|
|
104
|
+
hash_branches.each do |namespace, routes|
|
|
103
105
|
h[namespace] = routes.dup
|
|
104
106
|
end
|
|
105
107
|
end
|
|
@@ -108,7 +110,7 @@ class Roda
|
|
|
108
110
|
# a block, removes the existing branch handler if it exists.
|
|
109
111
|
def hash_branch(namespace='', segment, &block)
|
|
110
112
|
segment = "/#{segment}"
|
|
111
|
-
routes =
|
|
113
|
+
routes = hash_branches[namespace] ||= {}
|
|
112
114
|
if block
|
|
113
115
|
routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1, &convert_route_block(block))
|
|
114
116
|
elsif meth = routes.delete(segment)
|
|
@@ -125,7 +127,7 @@ class Roda
|
|
|
125
127
|
|
|
126
128
|
return unless rp.getbyte(0) == 47 # "/"
|
|
127
129
|
|
|
128
|
-
if routes = roda_class.
|
|
130
|
+
if routes = roda_class.hash_branches[namespace]
|
|
129
131
|
if segment_end = rp.index('/', 1)
|
|
130
132
|
if meth = routes[rp[0, segment_end]]
|
|
131
133
|
always do
|
|
@@ -80,9 +80,11 @@ class Roda
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
module ClassMethods
|
|
83
|
+
RodaPlugins.opt_attr_reader(self, :hash_paths)
|
|
84
|
+
|
|
83
85
|
# Freeze the hash_paths metadata when freezing the app.
|
|
84
86
|
def freeze
|
|
85
|
-
|
|
87
|
+
hash_paths.freeze.each_value(&:freeze)
|
|
86
88
|
super
|
|
87
89
|
end
|
|
88
90
|
|
|
@@ -90,8 +92,8 @@ class Roda
|
|
|
90
92
|
def inherited(subclass)
|
|
91
93
|
super
|
|
92
94
|
|
|
93
|
-
h = subclass.
|
|
94
|
-
|
|
95
|
+
h = subclass.hash_paths
|
|
96
|
+
hash_paths.each do |namespace, routes|
|
|
95
97
|
h[namespace] = routes.dup
|
|
96
98
|
end
|
|
97
99
|
end
|
|
@@ -102,7 +104,7 @@ class Roda
|
|
|
102
104
|
# there is one. If called without a block, removes the existing
|
|
103
105
|
# path handler if it exists.
|
|
104
106
|
def hash_path(namespace='', path, &block)
|
|
105
|
-
routes =
|
|
107
|
+
routes = hash_paths[namespace] ||= {}
|
|
106
108
|
if block
|
|
107
109
|
routes[path] = define_roda_method(routes[path] || "hash_path_#{namespace}_#{path}", 1, &convert_route_block(block))
|
|
108
110
|
elsif meth = routes.delete(path)
|
|
@@ -115,7 +117,7 @@ class Roda
|
|
|
115
117
|
# Checks the matching hash_path namespace for a branch matching the
|
|
116
118
|
# remaining path, and dispatch to that block if there is one.
|
|
117
119
|
def hash_paths(namespace=matched_path)
|
|
118
|
-
if (routes = roda_class.
|
|
120
|
+
if (routes = roda_class.hash_paths[namespace]) && (meth = routes[@remaining_path])
|
|
119
121
|
always do
|
|
120
122
|
@remaining_path = ''
|
|
121
123
|
scope.send(meth, self)
|
|
@@ -78,16 +78,20 @@ class Roda
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
module ClassMethods
|
|
81
|
+
RodaPlugins.opt_attr_reader(self, :hash_public_prefix)
|
|
82
|
+
RodaPlugins.opt_attr_reader(self, :hash_public_length)
|
|
83
|
+
RodaPlugins.opt_attr_reader(self, :hash_public_mutex)
|
|
84
|
+
RodaPlugins.opt_attr_reader(self, :hash_public_cache)
|
|
85
|
+
|
|
81
86
|
# The digest for the given file to use in hash_path.
|
|
82
87
|
def hash_path_digest(file)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
mutex = opts[:hash_public_mutex]
|
|
88
|
+
cache = hash_public_cache
|
|
89
|
+
mutex = hash_public_mutex
|
|
86
90
|
unless digest = mutex.synchronize{cache[file]}
|
|
87
|
-
digest = Digest::SHA256.file(File.join(
|
|
91
|
+
digest = Digest::SHA256.file(File.join(public_root, file)).base64digest
|
|
88
92
|
digest.chomp!("=")
|
|
89
93
|
digest.tr!("+/", "-_")
|
|
90
|
-
if length =
|
|
94
|
+
if length = hash_public_length
|
|
91
95
|
digest = digest[0, length]
|
|
92
96
|
end
|
|
93
97
|
digest.freeze
|
|
@@ -102,7 +106,7 @@ class Roda
|
|
|
102
106
|
# This does not check the file is inside the directory for performance
|
|
103
107
|
# reasons, so this should not be called with untrusted input.
|
|
104
108
|
def hash_path(file)
|
|
105
|
-
"/#{
|
|
109
|
+
"/#{self.class.hash_public_prefix}/#{self.class.hash_path_digest(file)}/#{file}"
|
|
106
110
|
end
|
|
107
111
|
end
|
|
108
112
|
|
|
@@ -112,7 +116,7 @@ class Roda
|
|
|
112
116
|
# a string segment for the content hash, and this is a GET request.
|
|
113
117
|
def hash_public
|
|
114
118
|
if is_get?
|
|
115
|
-
on roda_class.
|
|
119
|
+
on roda_class.hash_public_prefix, String do |_|
|
|
116
120
|
public
|
|
117
121
|
end
|
|
118
122
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
|
|
3
|
-
require 'json'
|
|
4
3
|
require 'find'
|
|
5
4
|
|
|
6
5
|
class Roda
|
|
@@ -29,6 +28,7 @@ class Roda
|
|
|
29
28
|
# load_hash_public_cache_file
|
|
30
29
|
module HashPublicCache
|
|
31
30
|
def self.load_dependencies(app, _cache_file, opts = OPTS)
|
|
31
|
+
app.plugin :_json
|
|
32
32
|
app.plugin :hash_public, opts
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -40,13 +40,15 @@ class Roda
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
module ClassMethods
|
|
43
|
+
RodaPlugins.opt_attr_reader(self, :hash_public_cache_file)
|
|
44
|
+
|
|
43
45
|
# Load the hash public cache file, if it exists. This replaces the hash
|
|
44
46
|
# public cache with the values from the file.
|
|
45
47
|
def load_hash_public_cache_file
|
|
46
|
-
file =
|
|
48
|
+
file = hash_public_cache_file
|
|
47
49
|
return unless File.file?(file)
|
|
48
50
|
|
|
49
|
-
cache = opts[:hash_public_cache] =
|
|
51
|
+
cache = opts[:hash_public_cache] = json_parser.call(::File.read(file))
|
|
50
52
|
cache.each_value(&:freeze)
|
|
51
53
|
nil
|
|
52
54
|
end
|
|
@@ -56,13 +58,13 @@ class Roda
|
|
|
56
58
|
# If a block is given, it will only calculate the digest for the file
|
|
57
59
|
# if the block returns truthy.
|
|
58
60
|
def scan_hash_public_cache_dir
|
|
59
|
-
cache =
|
|
61
|
+
cache = hash_public_cache
|
|
60
62
|
|
|
61
63
|
# Public root doesn't have trailing slash even if given, as
|
|
62
64
|
# File.expand_path removes it.
|
|
63
|
-
root =
|
|
65
|
+
root = public_root + File::SEPARATOR
|
|
64
66
|
|
|
65
|
-
Find.find(
|
|
67
|
+
Find.find(public_root) do |file|
|
|
66
68
|
if File.file?(file)
|
|
67
69
|
file = file.sub(root, '')
|
|
68
70
|
next if cache[file]
|
|
@@ -80,7 +82,7 @@ class Roda
|
|
|
80
82
|
|
|
81
83
|
# Write the current hash public cache to the cache file.
|
|
82
84
|
def dump_hash_public_cache_file
|
|
83
|
-
File.write(
|
|
85
|
+
File.write(hash_public_cache_file, json_serializer.call(hash_public_cache))
|
|
84
86
|
nil
|
|
85
87
|
end
|
|
86
88
|
end
|
|
@@ -191,7 +191,7 @@ class Roda
|
|
|
191
191
|
def dispatch_from(namespace='', branch, &block)
|
|
192
192
|
ns = @namespace
|
|
193
193
|
if block
|
|
194
|
-
meth_hash = @roda.
|
|
194
|
+
meth_hash = @roda.hash_routes_methods
|
|
195
195
|
key = [:dispatch_from, namespace, branch].freeze
|
|
196
196
|
meth = meth_hash[key] = @roda.define_roda_method(meth_hash[key] || "hash_routes_dispatch_from_#{namespace}_#{branch}", 1, &block)
|
|
197
197
|
@roda.hash_branch(namespace, branch) do |r|
|
|
@@ -253,7 +253,7 @@ class Roda
|
|
|
253
253
|
# Returns 404 for requests for the path with a different request method.
|
|
254
254
|
def verb(verb, path, &block)
|
|
255
255
|
path = path == true ? "" : "/#{path}"
|
|
256
|
-
meth_hash = @roda.
|
|
256
|
+
meth_hash = @roda.hash_routes_methods
|
|
257
257
|
key = [@namespace, path].freeze
|
|
258
258
|
meth = meth_hash[key] = @roda.define_roda_method(meth_hash[key] || "hash_routes_#{@namespace}_#{path}", 0, &block)
|
|
259
259
|
@roda.hash_path(@namespace, path) do |r|
|
|
@@ -265,9 +265,11 @@ class Roda
|
|
|
265
265
|
end
|
|
266
266
|
|
|
267
267
|
module ClassMethods
|
|
268
|
+
RodaPlugins.opt_attr_reader(self, :hash_routes_methods)
|
|
269
|
+
|
|
268
270
|
# Freeze the hash_routes metadata when freezing the app.
|
|
269
271
|
def freeze
|
|
270
|
-
|
|
272
|
+
hash_routes_methods.freeze
|
|
271
273
|
super
|
|
272
274
|
end
|
|
273
275
|
|
|
@@ -16,7 +16,11 @@ class Roda
|
|
|
16
16
|
module Heartbeat
|
|
17
17
|
# Set the heartbeat path to the given path.
|
|
18
18
|
def self.configure(app, opts=OPTS)
|
|
19
|
-
app.opts[:heartbeat_path] = (opts[:path] || app.
|
|
19
|
+
app.opts[:heartbeat_path] = (opts[:path] || app.heartbeat_path || "/heartbeat").dup.freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module ClassMethods
|
|
23
|
+
RodaPlugins.opt_attr_reader(self, :heartbeat_path)
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
module InstanceMethods
|
|
@@ -24,7 +28,7 @@ class Roda
|
|
|
24
28
|
|
|
25
29
|
# If the request is for a heartbeat path, return the heartbeat response.
|
|
26
30
|
def _roda_before_20__heartbeat
|
|
27
|
-
if env['PATH_INFO'] ==
|
|
31
|
+
if env['PATH_INFO'] == self.class.heartbeat_path
|
|
28
32
|
response = @_response
|
|
29
33
|
response.status = 200
|
|
30
34
|
response[RodaResponseHeaders::CONTENT_TYPE] = 'text/plain'
|
|
@@ -231,6 +231,12 @@ class Roda
|
|
|
231
231
|
end
|
|
232
232
|
end
|
|
233
233
|
|
|
234
|
+
module ClassMethods
|
|
235
|
+
RodaPlugins.opt_attr_reader(self, :hmac_paths_secret)
|
|
236
|
+
RodaPlugins.opt_attr_reader(self, :hmac_paths_old_secret)
|
|
237
|
+
RodaPlugins.opt_attr_reader(self, :hmac_paths_namespace_session_key)
|
|
238
|
+
end
|
|
239
|
+
|
|
234
240
|
module InstanceMethods
|
|
235
241
|
# Return a path with an HMAC. Designed to be used with r.hmac_path, to make sure
|
|
236
242
|
# users can only request paths that they have been provided by the application
|
|
@@ -315,7 +321,7 @@ class Roda
|
|
|
315
321
|
# using the secret given in the plugin, for the given root and options.
|
|
316
322
|
# This always returns a hexidecimal string.
|
|
317
323
|
def hmac_path_hmac_secret(root, opts=OPTS)
|
|
318
|
-
secret = opts[:secret] || self.
|
|
324
|
+
secret = opts[:secret] || self.class.hmac_paths_secret
|
|
319
325
|
|
|
320
326
|
if namespace = hmac_path_namespace(opts)
|
|
321
327
|
secret = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, namespace)
|
|
@@ -326,7 +332,7 @@ class Roda
|
|
|
326
332
|
|
|
327
333
|
# The default namespace to use for hmac_path, if a :namespace option is not provided.
|
|
328
334
|
def hmac_path_default_namespace
|
|
329
|
-
if (key =
|
|
335
|
+
if (key = self.class.hmac_paths_namespace_session_key) && (value = session[key])
|
|
330
336
|
value.to_s
|
|
331
337
|
end
|
|
332
338
|
end
|
|
@@ -395,7 +401,7 @@ class Roda
|
|
|
395
401
|
def hmac_path_valid?(root, path, hmac, opts=OPTS)
|
|
396
402
|
if Rack::Utils.secure_compare(scope.hmac_path_hmac(root, path, opts), hmac)
|
|
397
403
|
true
|
|
398
|
-
elsif old_secret = roda_class.
|
|
404
|
+
elsif old_secret = roda_class.hmac_paths_old_secret
|
|
399
405
|
opts = opts.dup
|
|
400
406
|
opts[:secret] = old_secret
|
|
401
407
|
Rack::Utils.secure_compare(scope.hmac_path_hmac(root, path, opts), hmac)
|
data/lib/roda/plugins/hooks.rb
CHANGED
|
@@ -39,21 +39,24 @@ class Roda
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
module ClassMethods
|
|
42
|
+
RodaPlugins.opt_attr_reader(self, :after_hooks)
|
|
43
|
+
RodaPlugins.opt_attr_reader(self, :before_hooks)
|
|
44
|
+
|
|
42
45
|
# Freeze the array of hook methods when freezing the app.
|
|
43
46
|
def freeze
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
after_hooks.freeze
|
|
48
|
+
before_hooks.freeze
|
|
46
49
|
|
|
47
50
|
super
|
|
48
51
|
end
|
|
49
52
|
|
|
50
53
|
# Add an after hook.
|
|
51
54
|
def after(&block)
|
|
52
|
-
|
|
53
|
-
if
|
|
54
|
-
class_eval("alias _roda_after_80__hooks #{
|
|
55
|
+
after_hooks << define_roda_method("after_hook", 1, &block)
|
|
56
|
+
if after_hooks.length == 1
|
|
57
|
+
class_eval("alias _roda_after_80__hooks #{after_hooks.first}", __FILE__, __LINE__)
|
|
55
58
|
else
|
|
56
|
-
class_eval("def _roda_after_80__hooks(res) #{
|
|
59
|
+
class_eval("def _roda_after_80__hooks(res) #{after_hooks.map{|m| "#{m}(res)"}.join(';')} end", __FILE__, __LINE__)
|
|
57
60
|
end
|
|
58
61
|
private :_roda_after_80__hooks
|
|
59
62
|
def_roda_after
|
|
@@ -62,11 +65,11 @@ class Roda
|
|
|
62
65
|
|
|
63
66
|
# Add a before hook.
|
|
64
67
|
def before(&block)
|
|
65
|
-
|
|
66
|
-
if
|
|
67
|
-
class_eval("alias _roda_before_10__hooks #{
|
|
68
|
+
before_hooks.unshift(define_roda_method("before_hook", 0, &block))
|
|
69
|
+
if before_hooks.length == 1
|
|
70
|
+
class_eval("alias _roda_before_10__hooks #{before_hooks.first}", __FILE__, __LINE__)
|
|
68
71
|
else
|
|
69
|
-
class_eval("def _roda_before_10__hooks; #{
|
|
72
|
+
class_eval("def _roda_before_10__hooks; #{before_hooks.join(';')} end", __FILE__, __LINE__)
|
|
70
73
|
end
|
|
71
74
|
private :_roda_before_10__hooks
|
|
72
75
|
def_roda_before
|
|
@@ -101,6 +101,11 @@ class Roda
|
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
module ClassMethods
|
|
105
|
+
RodaPlugins.opt_attr_reader(self, :host_authorization_host)
|
|
106
|
+
RodaPlugins.opt_attr_reader(self, :host_authorization_check_forwarded, name: :host_authorization_check_forwarded?)
|
|
107
|
+
end
|
|
108
|
+
|
|
104
109
|
module InstanceMethods
|
|
105
110
|
# Check whether the host is authorized. If not authorized, return a response
|
|
106
111
|
# immediately based on the plugin block.
|
|
@@ -108,7 +113,7 @@ class Roda
|
|
|
108
113
|
r = @_request
|
|
109
114
|
return if host_authorized?(_convert_host_for_authorization(r.env["HTTP_HOST"].to_s.dup))
|
|
110
115
|
|
|
111
|
-
if
|
|
116
|
+
if self.class.host_authorization_check_forwarded? && (host = r.env["HTTP_X_FORWARDED_HOST"])
|
|
112
117
|
if i = host.rindex(',')
|
|
113
118
|
host = host[i+1, 10000000].to_s
|
|
114
119
|
end
|
|
@@ -133,7 +138,7 @@ class Roda
|
|
|
133
138
|
end
|
|
134
139
|
|
|
135
140
|
# Whether the host given is one of the authorized hosts for this application.
|
|
136
|
-
def host_authorized?(host, authorized_host =
|
|
141
|
+
def host_authorized?(host, authorized_host = self.class.host_authorization_host)
|
|
137
142
|
case authorized_host
|
|
138
143
|
when Array
|
|
139
144
|
authorized_host.any?{|auth_host| host_authorized?(host, auth_host)}
|
|
@@ -176,6 +176,11 @@ class Roda
|
|
|
176
176
|
end
|
|
177
177
|
private_constant :DSL
|
|
178
178
|
|
|
179
|
+
module ClassMethods
|
|
180
|
+
RodaPlugins.opt_attr_reader(self, :host_routing_hash)
|
|
181
|
+
RodaPlugins.opt_attr_reader(self, :host_routing_default_host)
|
|
182
|
+
end
|
|
183
|
+
|
|
179
184
|
module InstanceMethods
|
|
180
185
|
# Handle case where plugin is used without providing a block to
|
|
181
186
|
# +hosts.default+. This returns nil, ensuring that the hostname
|
|
@@ -207,9 +212,9 @@ class Roda
|
|
|
207
212
|
host = ""
|
|
208
213
|
end
|
|
209
214
|
|
|
210
|
-
roda_class.
|
|
215
|
+
roda_class.host_routing_hash[host] ||
|
|
211
216
|
scope._host_routing_default(host) ||
|
|
212
|
-
roda_class.
|
|
217
|
+
roda_class.host_routing_default_host
|
|
213
218
|
end
|
|
214
219
|
end
|
|
215
220
|
end
|
|
@@ -37,13 +37,25 @@ class Roda
|
|
|
37
37
|
# # To treat the exception raised as a submitted parameter
|
|
38
38
|
# {body_error: exception}
|
|
39
39
|
# end
|
|
40
|
+
#
|
|
41
|
+
# You can use the +rescue_classes+ argument when loading the plugin to only rescue
|
|
42
|
+
# specific exception classes:
|
|
43
|
+
#
|
|
44
|
+
# plugin :invalid_request_body, :empty_hash, rescue_classes: Rack::BadRequest
|
|
45
|
+
#
|
|
46
|
+
# However, this can be risky, because there have been many cases in the past where
|
|
47
|
+
# Rack can rescue classes such as +ArgumentError+ or +NoMethodError+ when parsing
|
|
48
|
+
# invalid request bodies. The default for +rescue_classes+ is +StandardError+.
|
|
40
49
|
module InvalidRequestBody
|
|
41
50
|
# Exception class raised for invalid request bodies.
|
|
42
51
|
Error = Class.new(RodaError)
|
|
43
52
|
|
|
44
53
|
# Set the action to use (:empty_400, :empty_hash, :raise) for invalid request bodies,
|
|
45
|
-
# or use a block for custom behavior.
|
|
46
|
-
|
|
54
|
+
# or use a block for custom behavior. The rescue_classes argument is an exception
|
|
55
|
+
# class or array of exception classes to rescue.
|
|
56
|
+
def self.configure(app, action=nil, rescue_classes: StandardError, &block)
|
|
57
|
+
app.opts[:invalid_response_body_rescue_classes] = Array(rescue_classes).dup.freeze
|
|
58
|
+
|
|
47
59
|
if action
|
|
48
60
|
if block
|
|
49
61
|
raise RodaError, "cannot provide both block and action when loading invalid_request_body plugin"
|
|
@@ -56,7 +68,7 @@ class Roda
|
|
|
56
68
|
|
|
57
69
|
app::RodaRequest.send(:alias_method, :handle_invalid_request_body, method)
|
|
58
70
|
elsif block
|
|
59
|
-
app::RodaRequest.
|
|
71
|
+
app::RodaRequest.class_exec do
|
|
60
72
|
define_method(:handle_invalid_request_body, &block)
|
|
61
73
|
alias handle_invalid_request_body handle_invalid_request_body
|
|
62
74
|
end
|
|
@@ -67,6 +79,10 @@ class Roda
|
|
|
67
79
|
app::RodaRequest.send(:private, :handle_invalid_request_body)
|
|
68
80
|
end
|
|
69
81
|
|
|
82
|
+
module ClassMethods
|
|
83
|
+
RodaPlugins.opt_attr_reader(self, :invalid_response_body_rescue_classes)
|
|
84
|
+
end
|
|
85
|
+
|
|
70
86
|
module RequestMethods
|
|
71
87
|
# Handle invalid request bodies as configured if the default behavior
|
|
72
88
|
# raises an exception.
|
|
@@ -74,7 +90,7 @@ class Roda
|
|
|
74
90
|
super
|
|
75
91
|
rescue RodaError
|
|
76
92
|
raise
|
|
77
|
-
rescue => e
|
|
93
|
+
rescue *roda_class.invalid_response_body_rescue_classes => e
|
|
78
94
|
handle_invalid_request_body(e)
|
|
79
95
|
end
|
|
80
96
|
|