roda 3.53.0 → 3.56.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- throw(:halt, res) unless opts[:not_found] == :pass && res[0] == 404
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
@@ -0,0 +1,46 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The run_require_slash plugin makes +r.run+ a no-op if the remaining
7
+ # path is not empty and does not start with +/+. The Rack SPEC requires that
8
+ # +PATH_INFO+ start with a slash if not empty, so this plugin prevents
9
+ # dispatching to the application with an environment that would violate the
10
+ # Rack SPEC.
11
+ #
12
+ # You are unlikely to want to use this plugin unless are consuming partial
13
+ # segments of the request path, or using the match_affix plugin to change
14
+ # how routing is done:
15
+ #
16
+ # plugin :match_affix, "", /(\/|\z)/
17
+ # route do |r|
18
+ # r.on "/a" do
19
+ # r.on "b" do
20
+ # r.run App
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # # with run_require_slash:
26
+ # # GET /a/b/e => App not dispatched to
27
+ # # GET /a/b => App gets "" as PATH_INFO
28
+ #
29
+ # # with run_require_slash:
30
+ # # GET /a/b/e => App gets "e" as PATH_INFO, violating rack SPEC
31
+ # # GET /a/b => App gets "" as PATH_INFO
32
+ module RunRequireSlash
33
+ module RequestMethods
34
+ # Calls the given rack app only if the remaining patch is empty or
35
+ # starts with a slash.
36
+ def run(*)
37
+ if @remaining_path.empty? || @remaining_path.start_with?('/')
38
+ super
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ register_plugin(:run_require_slash, RunRequireSlash)
45
+ end
46
+ end
@@ -14,6 +14,7 @@ require 'base64'
14
14
  require 'json'
15
15
  require 'securerandom'
16
16
  require 'zlib'
17
+ require 'rack/utils'
17
18
 
18
19
  class Roda
19
20
  module RodaPlugins
@@ -1,5 +1,15 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/mime'
4
+ begin
5
+ require 'rack/files'
6
+ rescue LoadError
7
+ # :nocov:
8
+ require 'rack/file'
9
+ # :nocov:
10
+ end
11
+
12
+
3
13
  #
4
14
  class Roda
5
15
  module RodaPlugins
@@ -215,6 +225,10 @@ class Roda
215
225
  ISO88591_ENCODING = Encoding.find('ISO-8859-1')
216
226
  BINARY_ENCODING = Encoding.find('BINARY')
217
227
 
228
+ # :nocov:
229
+ RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
230
+ # :nocov:
231
+
218
232
  # Depend on the status_303 plugin.
219
233
  def self.load_dependencies(app, _opts = nil)
220
234
  app.plugin :status_303
@@ -333,7 +347,7 @@ class Roda
333
347
  last_modified(lm)
334
348
  end
335
349
 
336
- file = ::Rack::File.new nil
350
+ file = RACK_FILES.new nil
337
351
  s, h, b = if Rack.release > '2'
338
352
  file.serving(self, path)
339
353
  else
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/static'
4
+
3
5
  #
4
6
  class Roda
5
7
  module RodaPlugins
@@ -17,10 +17,13 @@ class Roda
17
17
  private
18
18
 
19
19
  def default_redirect_status
20
- if env['HTTP_VERSION'] == 'HTTP/1.1' && !is_get?
21
- 303
22
- else
20
+ return super if is_get?
21
+
22
+ case http_version
23
+ when 'HTTP/1.0', 'HTTP/0.9', nil
23
24
  super
25
+ else
26
+ 303
24
27
  end
25
28
  end
26
29
  end
@@ -15,24 +15,53 @@ class Roda
15
15
  # status_handler(403) do
16
16
  # "You are forbidden from seeing that!"
17
17
  # end
18
+ #
18
19
  # status_handler(404) do
19
20
  # "Where did it go?"
20
21
  # end
21
22
  #
23
+ # status_handler(405, keep_headers: ['Accept']) do
24
+ # "Use a different method!"
25
+ # end
26
+ #
22
27
  # Before a block is called, any existing headers on the response will be
23
- # cleared. So if you want to be sure the headers are set even in your block,
24
- # you need to reset them in the block.
28
+ # cleared, unless the +:keep_headers+ option is used. If the +:keep_headers+
29
+ # option is used, the value should be an array, and only the headers listed
30
+ # in the array will be kept.
25
31
  module StatusHandler
32
+ CLEAR_HEADERS = :clear.to_proc
33
+ private_constant :CLEAR_HEADERS
34
+
26
35
  def self.configure(app)
27
36
  app.opts[:status_handler] ||= {}
28
37
  end
29
38
 
30
39
  module ClassMethods
31
40
  # Install the given block as a status handler for the given HTTP response code.
32
- def status_handler(code, &block)
41
+ def status_handler(code, opts=OPTS, &block)
33
42
  # For backwards compatibility, pass request argument if block accepts argument
34
43
  arity = block.arity == 0 ? 0 : 1
35
- opts[:status_handler][code] = [define_roda_method(:"_roda_status_handler_#{code}", arity, &block), arity]
44
+ handle_headers = case keep_headers = opts[:keep_headers]
45
+ when nil, false
46
+ CLEAR_HEADERS
47
+ when Array
48
+ # :nocov:
49
+ if Rack.release >= '2.3'
50
+ keep_headers = keep_headers.map(&:downcase)
51
+ end
52
+ # :nocov:
53
+ lambda{|headers| headers.delete_if{|k,_| !keep_headers.include?(k)}}
54
+ else
55
+ raise RodaError, "Invalid :keep_headers option"
56
+ end
57
+
58
+ meth = define_roda_method(:"_roda_status_handler__#{code}", arity, &block)
59
+ self.opts[:status_handler][code] = define_roda_method(:"_roda_status_handler_#{code}", 1) do |result|
60
+ res = @_response
61
+ res.status = result[0]
62
+ handle_headers.call(res.headers)
63
+ result.replace(_roda_handle_route{arity == 1 ? send(meth, @_request) : send(meth)})
64
+ end
36
65
  end
37
66
 
38
67
  # Freeze the hash of status handlers so that there can be no thread safety issues at runtime.
@@ -47,11 +76,8 @@ class Roda
47
76
 
48
77
  # If routing returns a response we have a handler for, call that handler.
49
78
  def _roda_after_20__status_handler(result)
50
- if result && (meth, arity = opts[:status_handler][result[0]]; meth) && (v = result[2]).is_a?(Array) && v.empty?
51
- res = @_response
52
- res.headers.clear
53
- res.status = result[0]
54
- result.replace(_roda_handle_route{arity == 1 ? send(meth, @_request) : send(meth)})
79
+ if result && (meth = opts[:status_handler][result[0]]) && (v = result[2]).is_a?(Array) && v.empty?
80
+ send(meth, result)
55
81
  end
56
82
  end
57
83
  end
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/utils'
4
+
3
5
  class Roda
4
6
  module RodaPlugins
5
7
  # The symbol_status plugin patches the +status=+ response method to
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/utils'
4
+
3
5
  #
4
6
  class Roda
5
7
  module RodaPlugins
data/lib/roda/request.rb CHANGED
@@ -1,6 +1,19 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require "rack"
3
+ # :nocov:
4
+ begin
5
+ require "rack/version"
6
+ rescue LoadError
7
+ require "rack"
8
+ else
9
+ if Rack.release >= '2.3'
10
+ require "rack/request"
11
+ else
12
+ require "rack"
13
+ end
14
+ end
15
+ # :nocov:
16
+
4
17
  require_relative "cache"
5
18
 
6
19
  class Roda
@@ -116,6 +129,27 @@ class Roda
116
129
  "#<#{self.class.inspect} #{@env["REQUEST_METHOD"]} #{path}>"
117
130
  end
118
131
 
132
+ # :nocov:
133
+ if Rack.release >= '2.3'
134
+ def http_version
135
+ # Prefer SERVER_PROTOCOL as it is required in Rack 3.
136
+ # Still fall back to HTTP_VERSION if SERVER_PROTOCOL
137
+ # is not set, in case the server in use is not Rack 3
138
+ # compliant.
139
+ @env['SERVER_PROTOCOL'] || @env['HTTP_VERSION']
140
+ end
141
+ else
142
+ # :nocov:
143
+ # What HTTP version the request was submitted with.
144
+ def http_version
145
+ # Prefer HTTP_VERSION as it is backwards compatible
146
+ # with previous Roda versions. Fallback to
147
+ # SERVER_PROTOCOL for servers that do not set
148
+ # HTTP_VERSION.
149
+ @env['HTTP_VERSION'] || @env['SERVER_PROTOCOL']
150
+ end
151
+ end
152
+
119
153
  # Does a terminal match on the current path, matching only if the arguments
120
154
  # have fully matched the path. If it matches, the match block is
121
155
  # executed, and when the match block returns, the rack response is
data/lib/roda/response.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ begin
4
+ require 'rack/headers'
5
+ rescue LoadError
6
+ end
7
+
3
8
  class Roda
4
9
  # Base class used for Roda responses. The instance methods for this
5
10
  # class are added by Roda::RodaPlugins::Base::ResponseMethods, the class
@@ -37,11 +42,22 @@ class Roda
37
42
  # code for non-empty responses and a 404 code for empty responses.
38
43
  attr_accessor :status
39
44
 
40
- # Set the default headers when creating a response.
41
- def initialize
42
- @headers = {}
43
- @body = []
44
- @length = 0
45
+ # :nocov:
46
+ if defined?(Rack::Headers) && Rack::Headers.is_a?(Class)
47
+ # Set the default headers when creating a response.
48
+ def initialize
49
+ @headers = Rack::Headers.new
50
+ @body = []
51
+ @length = 0
52
+ end
53
+ else
54
+ # :nocov:
55
+ # Set the default headers when creating a response.
56
+ def initialize
57
+ @headers = {}
58
+ @body = []
59
+ @length = 0
60
+ end
45
61
  end
46
62
 
47
63
  # Return the response header with the given key. Example:
@@ -96,8 +112,7 @@ class Roda
96
112
  if (s == 304 || s == 204 || (s >= 100 && s <= 199))
97
113
  h.delete("Content-Type")
98
114
  elsif s == 205
99
- h.delete("Content-Type")
100
- h["Content-Length"] = '0'
115
+ empty_205_headers(h)
101
116
  else
102
117
  h["Content-Length"] ||= '0'
103
118
  end
@@ -158,6 +173,23 @@ class Roda
158
173
 
159
174
  private
160
175
 
176
+ # :nocov:
177
+ if Rack.release < '2.0.2'
178
+ # Don't use a content length for empty 205 responses on
179
+ # rack 1, as it violates Rack::Lint in that version.
180
+ def empty_205_headers(headers)
181
+ headers.delete("Content-Type")
182
+ headers.delete("Content-Length")
183
+ end
184
+ # :nocov:
185
+ else
186
+ # Set the content length for empty 205 responses to 0
187
+ def empty_205_headers(headers)
188
+ headers.delete("Content-Type")
189
+ headers["Content-Length"] = '0'
190
+ end
191
+ end
192
+
161
193
  # For each default header, if a header has not already been set for the
162
194
  # response, set the header in the response.
163
195
  def set_default_headers
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 = 53
7
+ RodaMinorVersion = 56
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.53.0
4
+ version: 3.56.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-14 00:00:00.000000000 Z
11
+ date: 2022-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 5.7.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-hooks
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: minitest-global_expectations
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -109,7 +123,7 @@ dependencies:
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
- name: sass
126
+ name: sassc
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - ">="
@@ -211,6 +225,9 @@ extra_rdoc_files:
211
225
  - doc/release_notes/3.51.0.txt
212
226
  - doc/release_notes/3.52.0.txt
213
227
  - doc/release_notes/3.53.0.txt
228
+ - doc/release_notes/3.54.0.txt
229
+ - doc/release_notes/3.55.0.txt
230
+ - doc/release_notes/3.56.0.txt
214
231
  - doc/release_notes/3.6.0.txt
215
232
  - doc/release_notes/3.7.0.txt
216
233
  - doc/release_notes/3.8.0.txt
@@ -271,6 +288,9 @@ files:
271
288
  - doc/release_notes/3.51.0.txt
272
289
  - doc/release_notes/3.52.0.txt
273
290
  - doc/release_notes/3.53.0.txt
291
+ - doc/release_notes/3.54.0.txt
292
+ - doc/release_notes/3.55.0.txt
293
+ - doc/release_notes/3.56.0.txt
274
294
  - doc/release_notes/3.6.0.txt
275
295
  - doc/release_notes/3.7.0.txt
276
296
  - doc/release_notes/3.8.0.txt
@@ -370,6 +390,7 @@ files:
370
390
  - lib/roda/plugins/route_csrf.rb
371
391
  - lib/roda/plugins/run_append_slash.rb
372
392
  - lib/roda/plugins/run_handler.rb
393
+ - lib/roda/plugins/run_require_slash.rb
373
394
  - lib/roda/plugins/sessions.rb
374
395
  - lib/roda/plugins/shared_vars.rb
375
396
  - lib/roda/plugins/sinatra_helpers.rb
@@ -392,13 +413,13 @@ files:
392
413
  - lib/roda/response.rb
393
414
  - lib/roda/session_middleware.rb
394
415
  - lib/roda/version.rb
395
- homepage: http://roda.jeremyevans.net
416
+ homepage: https://roda.jeremyevans.net
396
417
  licenses:
397
418
  - MIT
398
419
  metadata:
399
420
  bug_tracker_uri: https://github.com/jeremyevans/roda/issues
400
- changelog_uri: http://roda.jeremyevans.net/rdoc/files/CHANGELOG.html
401
- documentation_uri: http://roda.jeremyevans.net/documentation.html
421
+ changelog_uri: https://roda.jeremyevans.net/rdoc/files/CHANGELOG.html
422
+ documentation_uri: https://roda.jeremyevans.net/documentation.html
402
423
  mailing_list_uri: https://github.com/jeremyevans/roda/discussions
403
424
  source_code_uri: https://github.com/jeremyevans/roda
404
425
  post_install_message:
@@ -416,7 +437,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
416
437
  - !ruby/object:Gem::Version
417
438
  version: '0'
418
439
  requirements: []
419
- rubygems_version: 3.3.3
440
+ rubygems_version: 3.3.7
420
441
  signing_key:
421
442
  specification_version: 4
422
443
  summary: Routing tree web toolkit