roda 3.101.0 → 3.103.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7b1458c27a93cf829302766fa6299abadc1b52a7cc45375c23e588d0a28bbf7
4
- data.tar.gz: d9312bd50698b9eba385272903d3c12a04f5d699185f299b5ddc250fdcbac2ac
3
+ metadata.gz: d9809241399d00f1fc975608188f61e99c76c5818d7775a4fe8f0b7eeb9715e3
4
+ data.tar.gz: e04d8e54e041a4f96e998ebdd0431aada5e6d201fb1b6acc4b8c2a3fab50ea79
5
5
  SHA512:
6
- metadata.gz: 1483575934392739f5ee8c79cd74ca78f434ba9d798d9c2066f24f737f39fbfd0c0ad818acbd28b113ffaedf7751b3c45302ee17f8590c1dd47d6072de785453
7
- data.tar.gz: 20f6bc4308bcebd17abc063c4303487d80fd2ac63a1a35b35b99ae0ed2a2a82cf2c5effb480464817a3c1fbed343006a74547743022b1b2e2717c16164f06740
6
+ metadata.gz: 19235fd38195015a2b7392ebbf97d34d2f7bda2977c6d45c42fd3b2fd86b1cdee5eff2b65bccf485157955ad474994567ae1b99e93c9459a3a9601df65800f6c
7
+ data.tar.gz: 628a775ff0bbb503e18d3cc1989ddfb8dcafec407b890b96adf30684674f5ca749cc6ae6fdc9c4ff9ec59e7779f878f55ae37e76c0e52c26da4e8127a5a0f0f9
@@ -0,0 +1,105 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'digest/sha2'
4
+
5
+ class Roda
6
+ module RodaPlugins
7
+ # The hash_public plugin adds a +hash_path+ method for constructing
8
+ # content-hash-based paths, and a +r.hash_public+ routing method to serve
9
+ # static files from a directory (using the public plugin). This plugin is
10
+ # useful when you want to modify the path to static files when the content
11
+ # of the file changes, ensuring that requests for the static file will not
12
+ # be cached.
13
+ #
14
+ # Unlike the timestamp_public plugin, which uses file modification times,
15
+ # hash_public uses a SHA256 digest of the file content. This makes paths
16
+ # stable across different build environments (e.g. Docker images built in
17
+ # CI/CD pipelines), where file modification times may vary even when the
18
+ # file content has not changed.
19
+ #
20
+ # Note that while this plugin will not serve files outside of the public
21
+ # directory, for performance reasons it does not check the path of the file
22
+ # is inside the public directory when computing the content hash. If the
23
+ # +hash_path+ method is called with untrusted input, it is possible for an
24
+ # attacker to read the content hash of any file on the file system.
25
+ #
26
+ # This plugin caches the digest of file content on first read. That means
27
+ # if you change the file after that, it will continue to show the old hash.
28
+ # This can cause problems in development mode if you are modifying the
29
+ # content of files served by the plugin.
30
+ #
31
+ # Examples:
32
+ #
33
+ # # Use public folder as location of files, and static as the path prefix
34
+ # plugin :hash_public
35
+ #
36
+ # # Use /path/to/app/static as location of files, and public as the path prefix
37
+ # opts[:root] = '/path/to/app'
38
+ # plugin :hash_public, root: 'static', prefix: 'public'
39
+ #
40
+ # # Assuming public is the location of files, and static as the path prefix
41
+ # route do
42
+ # # Make GET /static/any-string/images/foo.png look for public/images/foo.png
43
+ # r.hash_public
44
+ #
45
+ # r.get "example" do
46
+ # # "/static/sha256-url-safe-base64-encoded-file-digest-/images/foo.png"
47
+ # hash_path("images/foo.png")
48
+ # end
49
+ # end
50
+ module HashPublic
51
+ # Use options given to setup content-hash-based file serving. The
52
+ # following options are recognized by the plugin:
53
+ #
54
+ # :prefix :: The prefix for paths, before the hash segment
55
+ # :length :: The number of characters of the digest to use in paths
56
+ # (default: full 43-character SHA256 URL safe base64 digest)
57
+ #
58
+ # The options given are also passed to the public plugin.
59
+ def self.configure(app, opts = {})
60
+ app.plugin :public, opts
61
+ app.opts[:hash_public_prefix] = (opts[:prefix] || app.opts[:hash_public_prefix] || 'static').dup.freeze
62
+ app.opts[:hash_public_length] = opts[:length] || app.opts[:hash_public_length]
63
+ app.opts[:hash_public_mutex] ||= Mutex.new
64
+ app.opts[:hash_public_cache] ||= {}
65
+ end
66
+
67
+ module InstanceMethods
68
+ # Return a path to the static file that could be served by r.hash_public.
69
+ # This does not check the file is inside the directory for performance
70
+ # reasons, so this should not be called with untrusted input.
71
+ def hash_path(file)
72
+ opts = self.opts
73
+ cache = opts[:hash_public_cache]
74
+ mutex = opts[:hash_public_mutex]
75
+ unless digest = mutex.synchronize{cache[file]}
76
+ digest = ::Digest::SHA256.file(File.join(opts[:public_root], file)).base64digest
77
+ digest.chomp!("=")
78
+ digest.tr!("+/", "-_")
79
+ if length = opts[:hash_public_length]
80
+ digest = digest[0, length]
81
+ end
82
+ digest.freeze
83
+ mutex.synchronize{cache[file] = digest}
84
+ end
85
+ "/#{opts[:hash_public_prefix]}/#{digest}/#{file}"
86
+ end
87
+ end
88
+
89
+ module RequestMethods
90
+ # Serve files from the public directory if the file exists,
91
+ # it includes the hash_public prefix segment followed by
92
+ # a string segment for the content hash, and this is a GET request.
93
+ def hash_public
94
+ if is_get?
95
+ on roda_class.opts[:hash_public_prefix], String do |_|
96
+ public
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ register_plugin(:hash_public, HashPublic)
104
+ end
105
+ end
@@ -5,7 +5,7 @@ class Roda
5
5
  module RodaPlugins
6
6
  # The host_authorization plugin allows configuring an authorized host or
7
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!+
8
+ # whether the request uses an authorized host via the +check_host_authorization!+
9
9
  # method.
10
10
  #
11
11
  # If the request doesn't match one of the authorized hosts, the
@@ -16,7 +16,7 @@ class Roda
16
16
  # By default, an empty response using status 403 will be returned for requests
17
17
  # with unauthorized hosts.
18
18
  #
19
- # Because +check_host_authorized!+ is an instance method, you can easily choose
19
+ # Because +check_host_authorization!+ is an instance method, you can easily choose
20
20
  # to only check for authorization in certain routes, or to check it after
21
21
  # other processing. For example, you could check for authorized hosts after
22
22
  # serving static files, since the serving of static files should not be
@@ -24,7 +24,7 @@ class Roda
24
24
  #
25
25
  # = Usage
26
26
  #
27
- # In your routing tree, call the +check_host_authorized!+ method at the point you
27
+ # In your routing tree, call the +check_host_authorization!+ method at the point you
28
28
  # want to check for authorized hosts:
29
29
  #
30
30
  # plugin :host_authorization, 'www.example.com'
@@ -32,7 +32,7 @@ class Roda
32
32
  #
33
33
  # route do |r|
34
34
  # r.public
35
- # check_host_authorized!
35
+ # check_host_authorization!
36
36
  #
37
37
  # # ...
38
38
  # end
@@ -153,4 +153,3 @@ class Roda
153
153
  register_plugin(:host_authorization, HostAuthorization)
154
154
  end
155
155
  end
156
-
@@ -0,0 +1,34 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The ip_from_header plugin allows for overriding +request.ip+ to return
7
+ # the value contained in a specific header. This is useful when the
8
+ # application is behind a proxy that sets a specific header, especially
9
+ # when the proxy does not use a fixed IP address range. Example showing
10
+ # usage with Cloudflare:
11
+ #
12
+ # plugin :ip_from_header, "CF-Connecting-IP"
13
+ #
14
+ # This plugin assumes that if the header is set, it contains a valid IP
15
+ # address, it does not check the format of the header value, just as
16
+ # <tt>Rack::Request#ip</tt> does not check the IP address it returns is
17
+ # actually valid.
18
+ module IPFromHeader
19
+ def self.configure(app, header)
20
+ app.opts[:ip_from_header_env_key] = "HTTP_#{header.upcase.tr('-', '_')}".freeze
21
+ end
22
+
23
+ module RequestMethods
24
+ # Return the IP address continained in the configured header, if present.
25
+ # Fallback to the default behavior if not present.
26
+ def ip
27
+ @env[roda_class.opts[:ip_from_header_env_key]] || super
28
+ end
29
+ end
30
+ end
31
+
32
+ register_plugin(:ip_from_header, IPFromHeader)
33
+ end
34
+ end
@@ -0,0 +1,94 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'rack/mime'
4
+
5
+ #
6
+ class Roda
7
+ module RodaPlugins
8
+ # The attachment plugin adds a response.attachment method.
9
+ # When called with no filename, +attachment+ sets the Content-Disposition
10
+ # to attachment. When called with a filename,+attachment+ sets the Content-Disposition
11
+ # to attachment with the appropriate filename parameter, and if the filename
12
+ # extension is recognized, this also sets the Content-Type to the appropriate
13
+ # MIME type if not already set.
14
+ #
15
+ # # set Content-Disposition to 'attachment'
16
+ # response.attachment
17
+ #
18
+ # # set Content-Disposition to 'attachment; filename="a.csv"',
19
+ # # also set Content-Type to 'text/csv'
20
+ # response.attachment 'a.csv'
21
+ #
22
+ # == License
23
+ #
24
+ # The implementation was originally taken from Sinatra,
25
+ # which is also released under the MIT License:
26
+ #
27
+ # Copyright (c) 2007, 2008, 2009 Blake Mizerany
28
+ # Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase
29
+ #
30
+ # Permission is hereby granted, free of charge, to any person
31
+ # obtaining a copy of this software and associated documentation
32
+ # files (the "Software"), to deal in the Software without
33
+ # restriction, including without limitation the rights to use,
34
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
35
+ # copies of the Software, and to permit persons to whom the
36
+ # Software is furnished to do so, subject to the following
37
+ # conditions:
38
+ #
39
+ # The above copyright notice and this permission notice shall be
40
+ # included in all copies or substantial portions of the Software.
41
+ #
42
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
43
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
44
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
45
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
46
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
47
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
48
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
49
+ # OTHER DEALINGS IN THE SOFTWARE.
50
+ module ResponseAttachment
51
+ UTF8_ENCODING = Encoding.find('UTF-8')
52
+ ISO88591_ENCODING = Encoding.find('ISO-8859-1')
53
+ BINARY_ENCODING = Encoding.find('BINARY')
54
+
55
+ module ResponseMethods
56
+ # Set the Content-Disposition to "attachment" with the specified filename,
57
+ # instructing the user agents to prompt to save.
58
+ def attachment(filename = nil, disposition='attachment')
59
+ if filename
60
+ param_filename = File.basename(filename)
61
+ encoding = param_filename.encoding
62
+
63
+ needs_encoding = param_filename.gsub!(/[^ 0-9a-zA-Z!\#$&\+\.\^_`\|~]+/, '-')
64
+ params = "; filename=#{param_filename.inspect}"
65
+
66
+ if needs_encoding && (encoding == UTF8_ENCODING || encoding == ISO88591_ENCODING)
67
+ # File name contains non attr-char characters from RFC 5987 Section 3.2.1
68
+
69
+ encoded_filename = File.basename(filename).force_encoding(BINARY_ENCODING)
70
+ # Similar regexp as above, but treat each byte separately, and encode
71
+ # space characters, since those aren't allowed in attr-char
72
+ encoded_filename.gsub!(/[^0-9a-zA-Z!\#$&\+\.\^_`\|~]/) do |c|
73
+ "%%%X" % c.ord
74
+ end
75
+
76
+ encoded_params = "; filename*=#{encoding.to_s}''#{encoded_filename}"
77
+ end
78
+
79
+ unless @headers[RodaResponseHeaders::CONTENT_TYPE]
80
+ ext = File.extname(filename)
81
+ if !ext.empty? && (content_type = Rack::Mime.mime_type(ext, nil))
82
+ @headers[RodaResponseHeaders::CONTENT_TYPE] = content_type
83
+ end
84
+ end
85
+ end
86
+
87
+ @headers[RodaResponseHeaders::CONTENT_DISPOSITION] = "#{disposition}#{params}#{encoded_params}"
88
+ end
89
+ end
90
+ end
91
+
92
+ register_plugin(:response_attachment, ResponseAttachment)
93
+ end
94
+ end
@@ -0,0 +1,127 @@
1
+ # frozen-string-literal: true
2
+
3
+ begin
4
+ require 'rack/files'
5
+ rescue LoadError
6
+ require 'rack/file'
7
+ end
8
+
9
+ #
10
+ class Roda
11
+ module RodaPlugins
12
+ # The send_file plugin adds a send_file method, used for
13
+ # returning the contents of a file as the body of a request.
14
+ # It also loads the response_attachment plugin to set the
15
+ # Content-Disposition and Content-Type based on the file's
16
+ # extension.
17
+ #
18
+ # senf_file will serve the file with the given path from the file system:
19
+ #
20
+ # send_file 'path/to/file.txt'
21
+ #
22
+ # Options:
23
+ #
24
+ # :disposition :: Set the Content-Disposition to the given disposition.
25
+ # :filename :: Set the Content-Disposition to attachment (unless :disposition is set),
26
+ # and set the filename parameter to the value.
27
+ # :last_modified :: Explicitly set the Last-Modified header to the given value, and
28
+ # return a not modified response if there has not been modified since
29
+ # the previous request. This option requires the caching plugin.
30
+ # :status :: Override the status for the response.
31
+ # :type :: Set the Content-Type to use for this response.
32
+ #
33
+ # == License
34
+ #
35
+ # The implementation was originally taken from Sinatra,
36
+ # which is also released under the MIT License:
37
+ #
38
+ # Copyright (c) 2007, 2008, 2009 Blake Mizerany
39
+ # Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase
40
+ #
41
+ # Permission is hereby granted, free of charge, to any person
42
+ # obtaining a copy of this software and associated documentation
43
+ # files (the "Software"), to deal in the Software without
44
+ # restriction, including without limitation the rights to use,
45
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
46
+ # copies of the Software, and to permit persons to whom the
47
+ # Software is furnished to do so, subject to the following
48
+ # conditions:
49
+ #
50
+ # The above copyright notice and this permission notice shall be
51
+ # included in all copies or substantial portions of the Software.
52
+ #
53
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
54
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
55
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
56
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
57
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
58
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
59
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
60
+ # OTHER DEALINGS IN THE SOFTWARE.
61
+ module SendFile
62
+ RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
63
+
64
+ # Depend on the status_303 plugin.
65
+ def self.load_dependencies(app)
66
+ app.plugin :response_attachment
67
+ end
68
+
69
+ module InstanceMethods
70
+ # Use the contents of the file at +path+ as the response body. See plugin documentation for options.
71
+ def send_file(path, opts = OPTS)
72
+ r = @_request
73
+ res = @_response
74
+ headers = res.headers
75
+ if (type = opts[:type]) || !headers[RodaResponseHeaders::CONTENT_TYPE]
76
+ type_str = type.to_s
77
+
78
+ if type_str.include?('/')
79
+ type = type_str
80
+ else
81
+ if type
82
+ type = ".#{type}" unless type_str.start_with?(".")
83
+ else
84
+ type = ::File.extname(path)
85
+ end
86
+
87
+ type &&= Rack::Mime.mime_type(type, nil)
88
+ type ||= 'application/octet-stream'
89
+ end
90
+
91
+ headers[RodaResponseHeaders::CONTENT_TYPE] = type
92
+ end
93
+
94
+ disposition = opts[:disposition]
95
+ filename = opts[:filename]
96
+ if disposition || filename
97
+ disposition ||= 'attachment'
98
+ filename = path if filename.nil?
99
+ res.attachment(filename, disposition)
100
+ end
101
+
102
+ if lm = opts[:last_modified]
103
+ r.last_modified(lm)
104
+ end
105
+
106
+ file = RACK_FILES.new nil
107
+ s, h, b = if Rack.release > '2'
108
+ file.serving(r, path)
109
+ else
110
+ file.path = path
111
+ file.serving(env)
112
+ end
113
+
114
+ res.status = opts[:status] || s
115
+ headers.delete(RodaResponseHeaders::CONTENT_LENGTH)
116
+ headers.replace(h.merge!(headers))
117
+ r.halt res.finish_with_body(b)
118
+ rescue Errno::ENOENT
119
+ response.status = 404
120
+ r.halt
121
+ end
122
+ end
123
+ end
124
+
125
+ register_plugin(:send_file, SendFile)
126
+ end
127
+ end
@@ -1,12 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require 'rack/mime'
4
- begin
5
- require 'rack/files'
6
- rescue LoadError
7
- require 'rack/file'
8
- end
9
-
10
4
 
11
5
  #
12
6
  class Roda
@@ -91,20 +85,7 @@ class Roda
91
85
  #
92
86
  # === send_file
93
87
  #
94
- # This will serve the file with the given path from the file system:
95
- #
96
- # send_file 'path/to/file.txt'
97
- #
98
- # Options:
99
- #
100
- # :disposition :: Set the Content-Disposition to the given disposition.
101
- # :filename :: Set the Content-Disposition to attachment (unless :disposition is set),
102
- # and set the filename parameter to the value.
103
- # :last_modified :: Explicitly set the Last-Modified header to the given value, and
104
- # return a not modified response if there has not been modified since
105
- # the previous request. This option requires the caching plugin.
106
- # :status :: Override the status for the response.
107
- # :type :: Set the Content-Type to use for this response.
88
+ # See send_file plugin documentation for details.
108
89
  #
109
90
  # == Response Methods Added
110
91
  #
@@ -166,15 +147,7 @@ class Roda
166
147
  #
167
148
  # === attachment
168
149
  #
169
- # When called with no filename, +attachment+ just sets the Content-Disposition
170
- # to attachment. When called with a filename, this sets the Content-Disposition
171
- # to attachment with the appropriate filename parameter, and if the filename
172
- # extension is recognized, this also sets the Content-Type to the appropriate
173
- # MIME type if not already set.
174
- #
175
- # attachment # set Content-Disposition to 'attachment'
176
- # attachment 'a.csv' # set Content-Disposition to 'attachment;filename="a.csv"',
177
- # # also set Content-Type to 'text/csv'
150
+ # See response_attachment plugin for details.
178
151
  #
179
152
  # === status predicates
180
153
  #
@@ -219,15 +192,10 @@ class Roda
219
192
  # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
220
193
  # OTHER DEALINGS IN THE SOFTWARE.
221
194
  module SinatraHelpers
222
- UTF8_ENCODING = Encoding.find('UTF-8')
223
- ISO88591_ENCODING = Encoding.find('ISO-8859-1')
224
- BINARY_ENCODING = Encoding.find('BINARY')
225
-
226
- RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
227
-
228
195
  # Depend on the status_303 plugin.
229
196
  def self.load_dependencies(app, _opts = nil)
230
197
  app.plugin :status_303
198
+ app.plugin :send_file
231
199
  end
232
200
 
233
201
  # Add delegate methods to the route block scope
@@ -323,40 +291,9 @@ class Roda
323
291
  super(path, status)
324
292
  end
325
293
 
326
- # Use the contents of the file at +path+ as the response body. See plugin documentation for options.
294
+ # Backwards compatibility for callers of r.send_file.
327
295
  def send_file(path, opts = OPTS)
328
- res = response
329
- headers = res.headers
330
- if opts[:type] || !headers[RodaResponseHeaders::CONTENT_TYPE]
331
- res.content_type(opts[:type] || ::File.extname(path), :default => 'application/octet-stream')
332
- end
333
-
334
- disposition = opts[:disposition]
335
- filename = opts[:filename]
336
- if disposition || filename
337
- disposition ||= 'attachment'
338
- filename = path if filename.nil?
339
- res.attachment(filename, disposition)
340
- end
341
-
342
- if lm = opts[:last_modified]
343
- last_modified(lm)
344
- end
345
-
346
- file = RACK_FILES.new nil
347
- s, h, b = if Rack.release > '2'
348
- file.serving(self, path)
349
- else
350
- file.path = path
351
- file.serving(@env)
352
- end
353
-
354
- res.status = opts[:status] || s
355
- headers.delete(RodaResponseHeaders::CONTENT_LENGTH)
356
- headers.replace(h.merge!(headers))
357
- halt res.finish_with_body(b)
358
- rescue Errno::ENOENT
359
- not_found
296
+ scope.send_file(path, opts)
360
297
  end
361
298
 
362
299
  # Generates the absolute URI for a given path in the app.
@@ -438,39 +375,6 @@ class Roda
438
375
  @headers[RodaResponseHeaders::CONTENT_TYPE] = mime_type
439
376
  end
440
377
 
441
- # Set the Content-Disposition to "attachment" with the specified filename,
442
- # instructing the user agents to prompt to save.
443
- def attachment(filename = nil, disposition='attachment')
444
- if filename
445
- param_filename = File.basename(filename)
446
- encoding = param_filename.encoding
447
-
448
- needs_encoding = param_filename.gsub!(/[^ 0-9a-zA-Z!\#$&\+\.\^_`\|~]+/, '-')
449
- params = "; filename=#{param_filename.inspect}"
450
-
451
- if needs_encoding && (encoding == UTF8_ENCODING || encoding == ISO88591_ENCODING)
452
- # File name contains non attr-char characters from RFC 5987 Section 3.2.1
453
-
454
- encoded_filename = File.basename(filename).force_encoding(BINARY_ENCODING)
455
- # Similar regexp as above, but treat each byte separately, and encode
456
- # space characters, since those aren't allowed in attr-char
457
- encoded_filename.gsub!(/[^0-9a-zA-Z!\#$&\+\.\^_`\|~]/) do |c|
458
- "%%%X" % c.ord
459
- end
460
-
461
- encoded_params = "; filename*=#{encoding.to_s}''#{encoded_filename}"
462
- end
463
-
464
- unless @headers[RodaResponseHeaders::CONTENT_TYPE]
465
- ext = File.extname(filename)
466
- unless ext.empty?
467
- content_type(ext)
468
- end
469
- end
470
- end
471
- @headers[RodaResponseHeaders::CONTENT_DISPOSITION] = "#{disposition}#{params}#{encoded_params}"
472
- end
473
-
474
378
  # Whether or not the status is set to 1xx. Returns nil if status not yet set.
475
379
  def informational?
476
380
  @status.between?(100, 199) if @status
@@ -521,7 +425,7 @@ class Roda
521
425
  [:logger, :back].each do |meth|
522
426
  define_method(meth){@_request.public_send(meth)}
523
427
  end
524
- [:redirect, :uri, :url, :to, :send_file, :error, :not_found].each do |meth|
428
+ [:redirect, :uri, :url, :to, :error, :not_found].each do |meth|
525
429
  define_method(meth){|*v, &block| @_request.public_send(meth, *v, &block)}
526
430
  end
527
431
 
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 101
7
+ RodaMinorVersion = 103
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.101.0
4
+ version: 3.103.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -218,6 +218,7 @@ files:
218
218
  - lib/roda/plugins/hash_branches.rb
219
219
  - lib/roda/plugins/hash_matcher.rb
220
220
  - lib/roda/plugins/hash_paths.rb
221
+ - lib/roda/plugins/hash_public.rb
221
222
  - lib/roda/plugins/hash_routes.rb
222
223
  - lib/roda/plugins/head.rb
223
224
  - lib/roda/plugins/header_matchers.rb
@@ -230,6 +231,7 @@ files:
230
231
  - lib/roda/plugins/indifferent_params.rb
231
232
  - lib/roda/plugins/inject_erb.rb
232
233
  - lib/roda/plugins/invalid_request_body.rb
234
+ - lib/roda/plugins/ip_from_header.rb
233
235
  - lib/roda/plugins/json.rb
234
236
  - lib/roda/plugins/json_parser.rb
235
237
  - lib/roda/plugins/link_to.rb
@@ -278,6 +280,7 @@ files:
278
280
  - lib/roda/plugins/render_locals.rb
279
281
  - lib/roda/plugins/request_aref.rb
280
282
  - lib/roda/plugins/request_headers.rb
283
+ - lib/roda/plugins/response_attachment.rb
281
284
  - lib/roda/plugins/response_content_type.rb
282
285
  - lib/roda/plugins/response_request.rb
283
286
  - lib/roda/plugins/route_block_args.rb
@@ -286,6 +289,7 @@ files:
286
289
  - lib/roda/plugins/run_handler.rb
287
290
  - lib/roda/plugins/run_require_slash.rb
288
291
  - lib/roda/plugins/sec_fetch_site_csrf.rb
292
+ - lib/roda/plugins/send_file.rb
289
293
  - lib/roda/plugins/sessions.rb
290
294
  - lib/roda/plugins/shared_vars.rb
291
295
  - lib/roda/plugins/sinatra_helpers.rb