roda 3.53.0 → 3.56.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.
@@ -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