roda 3.51.0 → 3.54.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +38 -588
- data/MIT-LICENSE +1 -1
- data/doc/release_notes/3.52.0.txt +20 -0
- data/doc/release_notes/3.53.0.txt +14 -0
- data/doc/release_notes/3.54.0.txt +48 -0
- data/lib/roda/plugins/additional_view_directories.rb +66 -0
- data/lib/roda/plugins/assets.rb +16 -5
- data/lib/roda/plugins/chunked.rb +47 -21
- data/lib/roda/plugins/drop_body.rb +5 -4
- data/lib/roda/plugins/heartbeat.rb +6 -1
- data/lib/roda/plugins/indifferent_params.rb +17 -5
- data/lib/roda/plugins/json_parser.rb +18 -3
- data/lib/roda/plugins/multi_public.rb +5 -1
- data/lib/roda/plugins/multi_route.rb +1 -0
- data/lib/roda/plugins/path.rb +9 -0
- data/lib/roda/plugins/public.rb +14 -7
- data/lib/roda/plugins/route_csrf.rb +3 -1
- data/lib/roda/plugins/run_handler.rb +7 -1
- data/lib/roda/plugins/sinatra_helpers.rb +5 -1
- data/lib/roda/plugins/typecast_params.rb +39 -1
- data/lib/roda/response.rb +34 -7
- data/lib/roda/version.rb +1 -1
- metadata +10 -17
@@ -0,0 +1,48 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* You can now override the type attribute for script tags produced
|
4
|
+
by the assets plugin, by providing a :type attribute when calling
|
5
|
+
the assets method.
|
6
|
+
|
7
|
+
= Other Improvements
|
8
|
+
|
9
|
+
* Reloading the render plugin after the additional_view_directories
|
10
|
+
plugin no longer removes the additional view directories from
|
11
|
+
the allowed paths for templates.
|
12
|
+
|
13
|
+
* When using Rack 3, Roda will now use an instance of Rack::Headers
|
14
|
+
instead of a plain hash for the headers, allowing for compliance
|
15
|
+
with the Rack 3 SPEC (which will require lowercase header keys).
|
16
|
+
|
17
|
+
* The public, multi_public, and sinatra_helpers plugin now use
|
18
|
+
Rack::Files instead of Rack::File if available, as Rack::File will
|
19
|
+
be deprecated in Rack 3.0.
|
20
|
+
|
21
|
+
* The json_parser plugin no longer rewinds the request body before
|
22
|
+
and after reading it when used with Rack 3.0, as Rack 3.0 has
|
23
|
+
dropped the requirement for rewindable input.
|
24
|
+
|
25
|
+
* The run_handler plugin now closes bodies for upstream 404 responses
|
26
|
+
when using the not_found: :pass option.
|
27
|
+
|
28
|
+
* The chunked plugin no longer uses Transfer-Encoding: chunked by
|
29
|
+
default. Requiring the use of Transfer-Encoding: chunked made the
|
30
|
+
plugin only work on HTTP 1.1, and not older or newer versions. The
|
31
|
+
plugin still allows for streaming template bodies as they are being
|
32
|
+
rendered. To get the previous behavior of forcing the use of
|
33
|
+
Transfer-Encoding: chunked, you can use the :force_chunked_encoding
|
34
|
+
plugin option
|
35
|
+
|
36
|
+
* Roda now supports testing with Rack::Lint. This found multiple
|
37
|
+
violations of the Rack SPEC which are fixed in this version, and
|
38
|
+
should ensure that Roda stays in compliance with the Rack SPEC going
|
39
|
+
forward.
|
40
|
+
|
41
|
+
= Backwards Compatibility
|
42
|
+
|
43
|
+
* Roda will no longer set the Content-Length header for 205 responses
|
44
|
+
when using Rack <2.0.2, as doing so violates the Rack SPEC for those
|
45
|
+
Rack versions.
|
46
|
+
|
47
|
+
* The drop_body plugin now drops response bodies for all 1xx responses,
|
48
|
+
not just for 100 and 101 responses, in compliance with the Rack SPEC.
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The additional_view_directories plugin allows for specifying additional view
|
7
|
+
# directories to look in for templates. When rendering a template, it will
|
8
|
+
# first try the :views directory specified in the render plugin. If the template
|
9
|
+
# file to be rendered does not exist in that directory, it will try each additional
|
10
|
+
# view directory specified in this plugin, in order, using the path to the first
|
11
|
+
# template file that exists in the file system. If no such path is found, it
|
12
|
+
# uses the default path specified by the render plugin.
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# plugin :render, :views=>'dir'
|
17
|
+
# plugin :additional_view_directories, ['dir1', 'dir2', 'dir3']
|
18
|
+
#
|
19
|
+
# route do |r|
|
20
|
+
# # Will check the following in order, using path for first
|
21
|
+
# # template file that exists:
|
22
|
+
# # * dir/t.erb
|
23
|
+
# # * dir1/t.erb
|
24
|
+
# # * dir2/t.erb
|
25
|
+
# # * dir3/t.erb
|
26
|
+
# render :t
|
27
|
+
# end
|
28
|
+
module AdditionalViewDirectories
|
29
|
+
# Depend on the render plugin, since this plugin only makes
|
30
|
+
# sense when the render plugin is used.
|
31
|
+
def self.load_dependencies(app, view_dirs)
|
32
|
+
app.plugin :render
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set the additional view directories to look in. Each additional view directory
|
36
|
+
# is also added as an allowed path.
|
37
|
+
def self.configure(app, view_dirs)
|
38
|
+
view_dirs = app.opts[:additional_view_directories] = view_dirs.map{|f| app.expand_path(f, nil)}.freeze
|
39
|
+
app.plugin :render, :allowed_paths=>(app.opts[:render][:allowed_paths] + view_dirs).uniq.freeze
|
40
|
+
end
|
41
|
+
|
42
|
+
module InstanceMethods
|
43
|
+
private
|
44
|
+
|
45
|
+
# If the template path does not exist, try looking for the template
|
46
|
+
# in each of the additional view directories, in order, returning
|
47
|
+
# the first path that exists. If no additional directory includes
|
48
|
+
# the template, return the original path.
|
49
|
+
def template_path(opts)
|
50
|
+
orig_path = super
|
51
|
+
|
52
|
+
unless File.file?(orig_path)
|
53
|
+
self.opts[:additional_view_directories].each do |view_dir|
|
54
|
+
path = super(opts.merge(:views=>view_dir))
|
55
|
+
return path if File.file?(path)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
orig_path
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
register_plugin(:additional_view_directories, AdditionalViewDirectories)
|
65
|
+
end
|
66
|
+
end
|
data/lib/roda/plugins/assets.rb
CHANGED
@@ -641,9 +641,17 @@ class Roda
|
|
641
641
|
# a different digest type or to return a static string if you don't
|
642
642
|
# want to use a unique value.
|
643
643
|
def asset_digest(content)
|
644
|
-
require 'digest/sha2'
|
645
644
|
algo = assets_opts[:sri] || :sha256
|
646
|
-
|
645
|
+
digest = begin
|
646
|
+
require 'openssl'
|
647
|
+
::OpenSSL::Digest
|
648
|
+
# :nocov:
|
649
|
+
rescue LoadError
|
650
|
+
require 'digest/sha2'
|
651
|
+
::Digest
|
652
|
+
# :nocov:
|
653
|
+
end
|
654
|
+
digest.const_get(algo.to_s.upcase).hexdigest(content)
|
647
655
|
end
|
648
656
|
end
|
649
657
|
|
@@ -700,6 +708,9 @@ class Roda
|
|
700
708
|
# To return the tags for a specific asset group, use an array for
|
701
709
|
# the type, such as [:css, :frontend].
|
702
710
|
#
|
711
|
+
# You can specify custom attributes for the tag by passing a hash
|
712
|
+
# as the attrs argument.
|
713
|
+
#
|
703
714
|
# When the assets are not compiled, this will result in a separate
|
704
715
|
# tag for each asset file. When the assets are compiled, this will
|
705
716
|
# result in a single tag to the compiled asset file.
|
@@ -712,13 +723,13 @@ class Roda
|
|
712
723
|
attrs[:integrity] = "#{algo}-#{h([[hash].pack('H*')].pack('m').tr("\n", ''))}"
|
713
724
|
end
|
714
725
|
|
715
|
-
|
726
|
+
attributes = attrs.map{|k,v| "#{k}=\"#{h(v)}\""}.join(' ')
|
716
727
|
|
717
728
|
if ltype == :js
|
718
|
-
tag_start = "<script type
|
729
|
+
tag_start = "<script#{' type="text/javascript"' unless attrs[:type]} #{attributes} src=\""
|
719
730
|
tag_end = "\"></script>"
|
720
731
|
else
|
721
|
-
tag_start = "<link rel=\"stylesheet\" #{
|
732
|
+
tag_start = "<link rel=\"stylesheet\" #{attributes} href=\""
|
722
733
|
tag_end = "\" />"
|
723
734
|
end
|
724
735
|
|
data/lib/roda/plugins/chunked.rb
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
#
|
4
4
|
class Roda
|
5
5
|
module RodaPlugins
|
6
|
-
# The chunked plugin allows you to stream
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
6
|
+
# The chunked plugin allows you to stream rendered views to clients.
|
7
|
+
# This can significantly improve performance of page rendering on the
|
8
|
+
# client, as it flushes the headers and top part of the layout template
|
9
|
+
# (generally containing references to the stylesheet and javascript assets)
|
10
|
+
# before rendering the content template.
|
11
11
|
#
|
12
12
|
# This allows the client to fetch the assets while the template is still
|
13
13
|
# being rendered. Additionally, this plugin makes it easy to defer
|
@@ -17,11 +17,11 @@ class Roda
|
|
17
17
|
# order to render the content template, such as retrieving values from a
|
18
18
|
# database.
|
19
19
|
#
|
20
|
-
# There are a couple disadvantages of streaming
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
20
|
+
# There are a couple disadvantages of streaming. First is that the layout
|
21
|
+
# must be rendered before the content, so any state changes made in your
|
22
|
+
# content template will not affect the layout template. Second, error
|
23
|
+
# handling is reduced, since if an error occurs while rendering a template,
|
24
|
+
# a successful response code has already been sent.
|
25
25
|
#
|
26
26
|
# To use chunked encoding for a response, just call the chunked method
|
27
27
|
# instead of view:
|
@@ -56,7 +56,6 @@ class Roda
|
|
56
56
|
# end
|
57
57
|
# end
|
58
58
|
#
|
59
|
-
#
|
60
59
|
# If you want to chunk all responses, pass the :chunk_by_default option
|
61
60
|
# when loading the plugin:
|
62
61
|
#
|
@@ -121,12 +120,7 @@ class Roda
|
|
121
120
|
# flush to output the message to the client inside handle_chunk_error.
|
122
121
|
#
|
123
122
|
# In order for chunking to work, you must make sure that no proxies between
|
124
|
-
# the application and the client buffer responses.
|
125
|
-
# plugin only works for HTTP/1.1 requests since Transfer-Encoding: chunked
|
126
|
-
# is not supported in HTTP/1.0. If an HTTP/1.0 request is submitted, this
|
127
|
-
# plugin will automatically fallback to the normal template rendering.
|
128
|
-
# Note that some proxies including nginx default to HTTP/1.0 even if the
|
129
|
-
# client supports HTTP/1.1. For nginx, set the proxy_http_version to 1.1.
|
123
|
+
# the application and the client buffer responses.
|
130
124
|
#
|
131
125
|
# If you are using nginx and have it set to buffer proxy responses by
|
132
126
|
# default, you can turn this off on a per response basis using the
|
@@ -135,6 +129,15 @@ class Roda
|
|
135
129
|
#
|
136
130
|
# plugin :chunked, headers: {'X-Accel-Buffering'=>'no'}
|
137
131
|
#
|
132
|
+
# By default, this plugin does not use Transfer-Encoding: chunked, it only
|
133
|
+
# returns a body that will stream the response in chunks. If you would like
|
134
|
+
# to force the use of Transfer-Encoding: chunked, you can use the
|
135
|
+
# :force_chunked_encoding plugin option. If using the
|
136
|
+
# :force_chunked_encoding plugin option, chunking will only be used for
|
137
|
+
# HTTP/1.1 requests since Transfer-Encoding: chunked is only supported
|
138
|
+
# in HTTP/1.1 (non-HTTP/1.1 requests will have behavior similar to
|
139
|
+
# calling no_chunk!).
|
140
|
+
#
|
138
141
|
# The chunked plugin requires the render plugin, and only works for
|
139
142
|
# template engines that store their template output variable in
|
140
143
|
# @_out_buf. Also, it only works if the content template is directly
|
@@ -155,12 +158,14 @@ class Roda
|
|
155
158
|
# :headers :: Set default additional headers to use when calling view
|
156
159
|
def self.configure(app, opts=OPTS)
|
157
160
|
app.opts[:chunk_by_default] = opts[:chunk_by_default]
|
161
|
+
app.opts[:force_chunked_encoding] = opts[:force_chunked_encoding]
|
158
162
|
if opts[:headers]
|
159
163
|
app.opts[:chunk_headers] = (app.opts[:chunk_headers] || {}).merge(opts[:headers]).freeze
|
160
164
|
end
|
161
165
|
end
|
162
166
|
|
163
|
-
# Rack response body instance for chunked responses
|
167
|
+
# Rack response body instance for chunked responses using
|
168
|
+
# Transfer-Encoding: chunked.
|
164
169
|
class Body
|
165
170
|
# Save the scope of the current request handling.
|
166
171
|
def initialize(scope)
|
@@ -184,6 +189,22 @@ class Roda
|
|
184
189
|
end
|
185
190
|
end
|
186
191
|
|
192
|
+
# Rack response body instance for chunked responses not
|
193
|
+
# using Transfer-Encoding: chunked.
|
194
|
+
class StreamBody
|
195
|
+
# Save the scope of the current request handling.
|
196
|
+
def initialize(scope)
|
197
|
+
@scope = scope
|
198
|
+
end
|
199
|
+
|
200
|
+
# Yield each non-empty chunk as the body.
|
201
|
+
def each(&block)
|
202
|
+
@scope.each_chunk do |chunk|
|
203
|
+
yield chunk if chunk && !chunk.empty?
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
187
208
|
module InstanceMethods
|
188
209
|
# Disable chunking for the current request. Mostly useful when
|
189
210
|
# chunking is turned on by default.
|
@@ -205,7 +226,7 @@ class Roda
|
|
205
226
|
# an overview. If a block is given, it is passed to #delay.
|
206
227
|
def chunked(template, opts=OPTS, &block)
|
207
228
|
unless defined?(@_chunked)
|
208
|
-
@_chunked = env['HTTP_VERSION'] == "HTTP/1.1"
|
229
|
+
@_chunked = !self.opts[:force_chunked_encoding] || env['HTTP_VERSION'] == "HTTP/1.1"
|
209
230
|
end
|
210
231
|
|
211
232
|
if block
|
@@ -235,9 +256,14 @@ class Roda
|
|
235
256
|
if chunk_headers = self.opts[:chunk_headers]
|
236
257
|
headers.merge!(chunk_headers)
|
237
258
|
end
|
238
|
-
|
259
|
+
if self.opts[:force_chunked_encoding]
|
260
|
+
headers['Transfer-Encoding'] = 'chunked'
|
261
|
+
body = Body.new(self)
|
262
|
+
else
|
263
|
+
body = StreamBody.new(self)
|
264
|
+
end
|
239
265
|
|
240
|
-
throw :halt, res.finish_with_body(
|
266
|
+
throw :halt, res.finish_with_body(body)
|
241
267
|
end
|
242
268
|
|
243
269
|
# Delay the execution of the block until right before the
|
@@ -15,22 +15,23 @@ class Roda
|
|
15
15
|
DROP_BODY_STATUSES = [100, 101, 102, 204, 205, 304].freeze
|
16
16
|
RodaPlugins.deprecate_constant(self, :DROP_BODY_STATUSES)
|
17
17
|
|
18
|
+
DROP_BODY_RANGE = 100..199
|
19
|
+
private_constant :DROP_BODY_RANGE
|
20
|
+
|
18
21
|
# If the response status indicates a body should not be
|
19
22
|
# returned, use an empty body and remove the Content-Length
|
20
23
|
# and Content-Type headers.
|
21
24
|
def finish
|
22
25
|
r = super
|
23
26
|
case r[0]
|
24
|
-
when
|
27
|
+
when DROP_BODY_RANGE, 204, 304
|
25
28
|
r[2] = EMPTY_ARRAY
|
26
29
|
h = r[1]
|
27
30
|
h.delete("Content-Length")
|
28
31
|
h.delete("Content-Type")
|
29
32
|
when 205
|
30
33
|
r[2] = EMPTY_ARRAY
|
31
|
-
|
32
|
-
h["Content-Length"] = '0'
|
33
|
-
h.delete("Content-Type")
|
34
|
+
empty_205_headers(r[1])
|
34
35
|
end
|
35
36
|
r
|
36
37
|
end
|
@@ -14,6 +14,11 @@ class Roda
|
|
14
14
|
#
|
15
15
|
# plugin :heartbeat, path: '/status'
|
16
16
|
module Heartbeat
|
17
|
+
# :nocov:
|
18
|
+
HEADER_CLASS = (defined?(Rack::Headers) && Rack::Headers.is_a?(Class)) ? Rack::Headers : Hash
|
19
|
+
# :nocov:
|
20
|
+
private_constant :HEADER_CLASS
|
21
|
+
|
17
22
|
HEARTBEAT_RESPONSE = [200, {'Content-Type'=>'text/plain'}.freeze, ['OK'.freeze].freeze].freeze
|
18
23
|
|
19
24
|
# Set the heartbeat path to the given path.
|
@@ -28,7 +33,7 @@ class Roda
|
|
28
33
|
def _roda_before_20__heartbeat
|
29
34
|
if env['PATH_INFO'] == opts[:heartbeat_path]
|
30
35
|
response = HEARTBEAT_RESPONSE.dup
|
31
|
-
response[1] =
|
36
|
+
response[1] = HEADER_CLASS[response[1]]
|
32
37
|
throw :halt, response
|
33
38
|
end
|
34
39
|
end
|
@@ -52,17 +52,29 @@ class Roda
|
|
52
52
|
end
|
53
53
|
|
54
54
|
class Params < Rack::QueryParser::Params
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
# :nocov:
|
56
|
+
if Rack.release >= '2.3'
|
57
|
+
def initialize
|
58
|
+
@size = 0
|
59
|
+
@params = Hash.new(&INDIFFERENT_PROC)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
# :nocov:
|
63
|
+
def initialize(limit = Rack::Utils.key_space_limit)
|
64
|
+
@limit = limit
|
65
|
+
@size = 0
|
66
|
+
@params = Hash.new(&INDIFFERENT_PROC)
|
67
|
+
end
|
59
68
|
end
|
60
69
|
end
|
61
70
|
|
62
71
|
end
|
63
72
|
|
64
73
|
module RequestMethods
|
65
|
-
|
74
|
+
# :nocov:
|
75
|
+
query_parser = Rack.release >= '2.3' ? QueryParser.new(QueryParser::Params, 32) : QueryParser.new(QueryParser::Params, 65536, 32)
|
76
|
+
# :nocov:
|
77
|
+
QUERY_PARSER = Rack::Utils.default_query_parser = query_parser
|
66
78
|
|
67
79
|
private
|
68
80
|
|
@@ -52,9 +52,7 @@ class Roda
|
|
52
52
|
if post_params = (env["roda.json_params"] || env["rack.request.form_hash"])
|
53
53
|
post_params
|
54
54
|
elsif (input = env["rack.input"]) && content_type =~ /json/
|
55
|
-
input
|
56
|
-
str = input.read
|
57
|
-
input.rewind
|
55
|
+
str = _read_json_input(input)
|
58
56
|
return super if str.empty?
|
59
57
|
begin
|
60
58
|
json_params = parse_json(str)
|
@@ -81,6 +79,23 @@ class Roda
|
|
81
79
|
args << self if roda_class.opts[:json_parser_include_request]
|
82
80
|
roda_class.opts[:json_parser_parser].call(*args)
|
83
81
|
end
|
82
|
+
|
83
|
+
|
84
|
+
# Rack 3 dropped requirement that input be rewindable
|
85
|
+
if Rack.release >= '2.3'
|
86
|
+
# :nocov:
|
87
|
+
def _read_json_input(input)
|
88
|
+
input.read
|
89
|
+
end
|
90
|
+
# :nocov:
|
91
|
+
else
|
92
|
+
def _read_json_input(input)
|
93
|
+
input.rewind
|
94
|
+
str = input.read
|
95
|
+
input.rewind
|
96
|
+
str
|
97
|
+
end
|
98
|
+
end
|
84
99
|
end
|
85
100
|
end
|
86
101
|
|
@@ -58,6 +58,10 @@ class Roda
|
|
58
58
|
# end
|
59
59
|
# end
|
60
60
|
module MultiPublic
|
61
|
+
# :nocov:
|
62
|
+
RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
|
63
|
+
# :nocov:
|
64
|
+
|
61
65
|
def self.load_dependencies(app, _, opts=OPTS)
|
62
66
|
app.plugin(:public, opts)
|
63
67
|
end
|
@@ -67,7 +71,7 @@ class Roda
|
|
67
71
|
roots = app.opts[:multi_public_servers] = (app.opts[:multi_public_servers] || {}).dup
|
68
72
|
directories.each do |key, path|
|
69
73
|
path, headers, mime = path
|
70
|
-
roots[key] =
|
74
|
+
roots[key] = RACK_FILES.new(app.expand_path(path), headers||{}, mime||'text/plain')
|
71
75
|
end
|
72
76
|
roots.freeze
|
73
77
|
end
|
data/lib/roda/plugins/path.rb
CHANGED
@@ -148,11 +148,17 @@ class Roda
|
|
148
148
|
# Allow calling private _method to get path
|
149
149
|
relative_path(request.script_name.to_s + send(_meth, *a, &blk))
|
150
150
|
end
|
151
|
+
# :nocov:
|
152
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
153
|
+
# :nocov:
|
151
154
|
elsif add_script_name
|
152
155
|
define_method(meth) do |*a, &blk|
|
153
156
|
# Allow calling private _method to get path
|
154
157
|
request.script_name.to_s + send(_meth, *a, &blk)
|
155
158
|
end
|
159
|
+
# :nocov:
|
160
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
161
|
+
# :nocov:
|
156
162
|
else
|
157
163
|
define_method(meth, &block)
|
158
164
|
end
|
@@ -171,6 +177,9 @@ class Roda
|
|
171
177
|
end
|
172
178
|
|
173
179
|
define_method(url_meth, &url_block)
|
180
|
+
# :nocov:
|
181
|
+
ruby2_keywords(url_meth) if respond_to?(:ruby2_keywords, true)
|
182
|
+
# :nocov:
|
174
183
|
end
|
175
184
|
|
176
185
|
nil
|
data/lib/roda/plugins/public.rb
CHANGED
@@ -37,6 +37,9 @@ class Roda
|
|
37
37
|
module Public
|
38
38
|
SPLIT = Regexp.union(*[File::SEPARATOR, File::ALT_SEPARATOR].compact)
|
39
39
|
PARSER = URI::DEFAULT_PARSER
|
40
|
+
# :nocov:
|
41
|
+
RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
|
42
|
+
# :nocov:
|
40
43
|
|
41
44
|
# Use options given to setup a Rack::File instance for serving files. Options:
|
42
45
|
# :default_mime :: The default mime type to use if the mime type is not recognized.
|
@@ -52,7 +55,7 @@ class Roda
|
|
52
55
|
elsif !app.opts[:public_root]
|
53
56
|
app.opts[:public_root] = app.expand_path("public")
|
54
57
|
end
|
55
|
-
app.opts[:public_server] =
|
58
|
+
app.opts[:public_server] = RACK_FILES.new(app.opts[:public_root], opts[:headers]||{}, opts[:default_mime] || 'text/plain')
|
56
59
|
app.opts[:public_gzip] = opts[:gzip]
|
57
60
|
app.opts[:public_brotli] = opts[:brotli]
|
58
61
|
end
|
@@ -99,7 +102,10 @@ class Roda
|
|
99
102
|
public_serve_compressed(server, path, '.gz', 'gzip') if roda_opts[:public_gzip]
|
100
103
|
|
101
104
|
if public_file_readable?(path)
|
102
|
-
|
105
|
+
s, h, b = public_serve(server, path)
|
106
|
+
headers = response.headers
|
107
|
+
headers.replace(h)
|
108
|
+
halt [s, headers, b]
|
103
109
|
end
|
104
110
|
end
|
105
111
|
|
@@ -108,21 +114,22 @@ class Roda
|
|
108
114
|
compressed_path = path + suffix
|
109
115
|
|
110
116
|
if public_file_readable?(compressed_path)
|
111
|
-
|
112
|
-
headers =
|
117
|
+
s, h, b = public_serve(server, compressed_path)
|
118
|
+
headers = response.headers
|
119
|
+
headers.replace(h)
|
113
120
|
|
114
|
-
unless
|
121
|
+
unless s == 304
|
115
122
|
headers['Content-Type'] = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
116
123
|
headers['Content-Encoding'] = encoding
|
117
124
|
end
|
118
125
|
|
119
|
-
halt
|
126
|
+
halt [s, headers, b]
|
120
127
|
end
|
121
128
|
end
|
122
129
|
end
|
123
130
|
|
124
131
|
if ::Rack.release > '2'
|
125
|
-
# Serve the given path using the given Rack::
|
132
|
+
# Serve the given path using the given Rack::Files server.
|
126
133
|
def public_serve(server, path)
|
127
134
|
server.serving(self, path)
|
128
135
|
end
|
@@ -191,7 +191,9 @@ class Roda
|
|
191
191
|
when :raise
|
192
192
|
raise InvalidToken, msg
|
193
193
|
when :empty_403
|
194
|
-
|
194
|
+
@_response.status = 403
|
195
|
+
@_response.headers.replace('Content-Type'=>'text/html', 'Content-Length'=>'0')
|
196
|
+
throw :halt, @_response.finish_with_body([])
|
195
197
|
when :clear_session
|
196
198
|
session.clear
|
197
199
|
when :csrf_failure_method
|
@@ -35,7 +35,13 @@ class Roda
|
|
35
35
|
def run(app, opts=OPTS)
|
36
36
|
res = catch(:halt){super(app)}
|
37
37
|
yield res if defined?(yield)
|
38
|
-
|
38
|
+
if opts[:not_found] == :pass && res[0] == 404
|
39
|
+
body = res[2]
|
40
|
+
body.close if body.respond_to?(:close)
|
41
|
+
nil
|
42
|
+
else
|
43
|
+
throw(:halt, res)
|
44
|
+
end
|
39
45
|
end
|
40
46
|
end
|
41
47
|
end
|
@@ -215,6 +215,10 @@ class Roda
|
|
215
215
|
ISO88591_ENCODING = Encoding.find('ISO-8859-1')
|
216
216
|
BINARY_ENCODING = Encoding.find('BINARY')
|
217
217
|
|
218
|
+
# :nocov:
|
219
|
+
RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
|
220
|
+
# :nocov:
|
221
|
+
|
218
222
|
# Depend on the status_303 plugin.
|
219
223
|
def self.load_dependencies(app, _opts = nil)
|
220
224
|
app.plugin :status_303
|
@@ -333,7 +337,7 @@ class Roda
|
|
333
337
|
last_modified(lm)
|
334
338
|
end
|
335
339
|
|
336
|
-
file =
|
340
|
+
file = RACK_FILES.new nil
|
337
341
|
s, h, b = if Rack.release > '2'
|
338
342
|
file.serving(self, path)
|
339
343
|
else
|
@@ -265,6 +265,23 @@ class Roda
|
|
265
265
|
# If you would like to skip this check and allow null bytes in param string values,
|
266
266
|
# you can do by passing the <tt>:allow_null_bytes</tt> option when loading the plugin.
|
267
267
|
#
|
268
|
+
# You can use the :date_parse_input_handler option to specify custom handling of date
|
269
|
+
# parsing input. Modern versions of Ruby and the date gem internally raise if the input to
|
270
|
+
# date parsing methods is too large to prevent denial of service. If you are using an
|
271
|
+
# older version of Ruby, you can use this option to enforce the same check:
|
272
|
+
#
|
273
|
+
# plugin :typecast_params, date_parse_input_handler: proc {|string|
|
274
|
+
# raise ArgumentError, "too big" if string.bytesize > 128
|
275
|
+
# string
|
276
|
+
# }
|
277
|
+
#
|
278
|
+
# You can also use this option to modify the input, such as truncating it to the first
|
279
|
+
# 128 bytes:
|
280
|
+
#
|
281
|
+
# plugin :typecast_params, date_parse_input_handler: proc {|string|
|
282
|
+
# string.b[0, 128]
|
283
|
+
# }
|
284
|
+
#
|
268
285
|
# By design, typecast_params only deals with string keys, it is not possible to use
|
269
286
|
# symbol keys as arguments to the conversion methods and have them converted.
|
270
287
|
module TypecastParams
|
@@ -384,6 +401,14 @@ class Roda
|
|
384
401
|
end
|
385
402
|
end
|
386
403
|
|
404
|
+
module DateParseInputHandler
|
405
|
+
# Pass input string to date parsing through handle_date_parse_input.
|
406
|
+
def _string_parse!(klass, v)
|
407
|
+
v = handle_date_parse_input(v)
|
408
|
+
super
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
387
412
|
# Class handling conversion of submitted parameters to desired types.
|
388
413
|
class Params
|
389
414
|
# Handle conversions for the given type using the given block.
|
@@ -999,11 +1024,16 @@ class Roda
|
|
999
1024
|
when ''
|
1000
1025
|
nil
|
1001
1026
|
when String
|
1002
|
-
klass
|
1027
|
+
_string_parse!(klass, v)
|
1003
1028
|
else
|
1004
1029
|
raise Error, "unexpected value received: #{v.inspect}"
|
1005
1030
|
end
|
1006
1031
|
end
|
1032
|
+
|
1033
|
+
# Handle parsing for string values passed to parse!.
|
1034
|
+
def _string_parse!(klass, v)
|
1035
|
+
klass.parse(v)
|
1036
|
+
end
|
1007
1037
|
end
|
1008
1038
|
|
1009
1039
|
# Set application-specific Params subclass unless one has been set,
|
@@ -1019,6 +1049,14 @@ class Roda
|
|
1019
1049
|
if opts[:allow_null_bytes]
|
1020
1050
|
app::TypecastParams.send(:include, AllowNullByte)
|
1021
1051
|
end
|
1052
|
+
if opts[:date_parse_input_handler]
|
1053
|
+
app::TypecastParams.class_eval do
|
1054
|
+
include DateParseInputHandler
|
1055
|
+
define_method(:handle_date_parse_input, &opts[:date_parse_input_handler])
|
1056
|
+
private :handle_date_parse_input
|
1057
|
+
alias handle_date_parse_input handle_date_parse_input
|
1058
|
+
end
|
1059
|
+
end
|
1022
1060
|
end
|
1023
1061
|
|
1024
1062
|
module ClassMethods
|