rack 2.1.0 → 2.2.2

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.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -6
  3. data/CONTRIBUTING.md +136 -0
  4. data/README.rdoc +83 -39
  5. data/Rakefile +14 -7
  6. data/{SPEC → SPEC.rdoc} +26 -1
  7. data/lib/rack.rb +7 -16
  8. data/lib/rack/auth/abstract/request.rb +0 -2
  9. data/lib/rack/auth/basic.rb +3 -3
  10. data/lib/rack/auth/digest/md5.rb +4 -4
  11. data/lib/rack/auth/digest/request.rb +3 -3
  12. data/lib/rack/body_proxy.rb +13 -9
  13. data/lib/rack/builder.rb +78 -8
  14. data/lib/rack/cascade.rb +23 -8
  15. data/lib/rack/chunked.rb +48 -23
  16. data/lib/rack/common_logger.rb +25 -18
  17. data/lib/rack/conditional_get.rb +18 -16
  18. data/lib/rack/content_length.rb +6 -7
  19. data/lib/rack/content_type.rb +3 -4
  20. data/lib/rack/deflater.rb +49 -35
  21. data/lib/rack/directory.rb +77 -60
  22. data/lib/rack/etag.rb +2 -3
  23. data/lib/rack/events.rb +15 -18
  24. data/lib/rack/file.rb +1 -2
  25. data/lib/rack/files.rb +97 -57
  26. data/lib/rack/handler/cgi.rb +1 -4
  27. data/lib/rack/handler/fastcgi.rb +1 -3
  28. data/lib/rack/handler/lsws.rb +1 -3
  29. data/lib/rack/handler/scgi.rb +1 -3
  30. data/lib/rack/handler/thin.rb +1 -3
  31. data/lib/rack/handler/webrick.rb +12 -5
  32. data/lib/rack/head.rb +0 -2
  33. data/lib/rack/lint.rb +57 -14
  34. data/lib/rack/lobster.rb +3 -5
  35. data/lib/rack/lock.rb +0 -1
  36. data/lib/rack/mock.rb +22 -4
  37. data/lib/rack/multipart.rb +1 -1
  38. data/lib/rack/multipart/generator.rb +11 -6
  39. data/lib/rack/multipart/parser.rb +10 -18
  40. data/lib/rack/multipart/uploaded_file.rb +13 -7
  41. data/lib/rack/query_parser.rb +7 -8
  42. data/lib/rack/recursive.rb +1 -1
  43. data/lib/rack/reloader.rb +1 -3
  44. data/lib/rack/request.rb +182 -76
  45. data/lib/rack/response.rb +62 -19
  46. data/lib/rack/rewindable_input.rb +0 -1
  47. data/lib/rack/runtime.rb +3 -3
  48. data/lib/rack/sendfile.rb +0 -3
  49. data/lib/rack/server.rb +9 -10
  50. data/lib/rack/session/abstract/id.rb +23 -28
  51. data/lib/rack/session/cookie.rb +1 -3
  52. data/lib/rack/session/pool.rb +1 -1
  53. data/lib/rack/show_exceptions.rb +6 -8
  54. data/lib/rack/show_status.rb +5 -7
  55. data/lib/rack/static.rb +13 -6
  56. data/lib/rack/tempfile_reaper.rb +0 -2
  57. data/lib/rack/urlmap.rb +1 -4
  58. data/lib/rack/utils.rb +58 -54
  59. data/lib/rack/version.rb +29 -0
  60. data/rack.gemspec +31 -29
  61. metadata +11 -12
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/request'
4
- require 'rack/utils'
5
- require 'rack/body_proxy'
6
- require 'rack/media_type'
7
3
  require 'time'
8
4
 
9
5
  module Rack
@@ -19,34 +15,51 @@ module Rack
19
15
  # +write+ are synchronous with the Rack response.
20
16
  #
21
17
  # Your application's +call+ should end returning Response#finish.
22
-
23
18
  class Response
24
- attr_accessor :length, :status, :body
25
- attr_reader :header
26
- alias headers header
19
+ def self.[](status, headers, body)
20
+ self.new(body, status, headers)
21
+ end
27
22
 
28
23
  CHUNKED = 'chunked'
29
24
  STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
30
25
 
31
- def initialize(body = nil, status = 200, header = {})
26
+ attr_accessor :length, :status, :body
27
+ attr_reader :headers
28
+
29
+ # @deprecated Use {#headers} instead.
30
+ alias header headers
31
+
32
+ # Initialize the response object with the specified body, status
33
+ # and headers.
34
+ #
35
+ # @param body [nil, #each, #to_str] the response body.
36
+ # @param status [Integer] the integer status as defined by the
37
+ # HTTP protocol RFCs.
38
+ # @param headers [#each] a list of key-value header pairs which
39
+ # conform to the HTTP protocol RFCs.
40
+ #
41
+ # Providing a body which responds to #to_str is legacy behaviour.
42
+ def initialize(body = nil, status = 200, headers = {})
32
43
  @status = status.to_i
33
- @header = Utils::HeaderHash.new(header)
44
+ @headers = Utils::HeaderHash[headers]
34
45
 
35
46
  @writer = self.method(:append)
36
47
 
37
48
  @block = nil
38
- @length = 0
39
49
 
40
50
  # Keep track of whether we have expanded the user supplied body.
41
51
  if body.nil?
42
52
  @body = []
43
53
  @buffered = true
54
+ @length = 0
44
55
  elsif body.respond_to?(:to_str)
45
56
  @body = [body]
46
57
  @buffered = true
58
+ @length = body.to_str.bytesize
47
59
  else
48
60
  @body = body
49
61
  @buffered = false
62
+ @length = 0
50
63
  end
51
64
 
52
65
  yield self if block_given?
@@ -61,18 +74,21 @@ module Rack
61
74
  CHUNKED == get_header(TRANSFER_ENCODING)
62
75
  end
63
76
 
77
+ # Generate a response array consistent with the requirements of the SPEC.
78
+ # @return [Array] a 3-tuple suitable of `[status, headers, body]`
79
+ # which is suitable to be returned from the middleware `#call(env)` method.
64
80
  def finish(&block)
65
81
  if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
66
82
  delete_header CONTENT_TYPE
67
83
  delete_header CONTENT_LENGTH
68
84
  close
69
- [status.to_i, header, []]
85
+ return [@status, @headers, []]
70
86
  else
71
87
  if block_given?
72
88
  @block = block
73
- [status.to_i, header, self]
89
+ return [@status, @headers, self]
74
90
  else
75
- [status.to_i, header, @body]
91
+ return [@status, @headers, @body]
76
92
  end
77
93
  end
78
94
  end
@@ -152,7 +168,7 @@ module Rack
152
168
  # assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
153
169
  #
154
170
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
155
- def add_header key, v
171
+ def add_header(key, v)
156
172
  if v.nil?
157
173
  get_header key
158
174
  elsif has_header? key
@@ -162,10 +178,16 @@ module Rack
162
178
  end
163
179
  end
164
180
 
181
+ # Get the content type of the response.
165
182
  def content_type
166
183
  get_header CONTENT_TYPE
167
184
  end
168
185
 
186
+ # Set the content type of the response.
187
+ def content_type=(content_type)
188
+ set_header CONTENT_TYPE, content_type
189
+ end
190
+
169
191
  def media_type
170
192
  MediaType.type(content_type)
171
193
  end
@@ -200,7 +222,7 @@ module Rack
200
222
  get_header SET_COOKIE
201
223
  end
202
224
 
203
- def set_cookie_header= v
225
+ def set_cookie_header=(v)
204
226
  set_header SET_COOKIE, v
205
227
  end
206
228
 
@@ -208,15 +230,31 @@ module Rack
208
230
  get_header CACHE_CONTROL
209
231
  end
210
232
 
211
- def cache_control= v
233
+ def cache_control=(v)
212
234
  set_header CACHE_CONTROL, v
213
235
  end
214
236
 
237
+ # Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
238
+ def do_not_cache!
239
+ set_header CACHE_CONTROL, "no-cache, must-revalidate"
240
+ set_header EXPIRES, Time.now.httpdate
241
+ end
242
+
243
+ # Specify that the content should be cached.
244
+ # @param duration [Integer] The number of seconds until the cache expires.
245
+ # @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store".
246
+ def cache!(duration = 3600, directive: "public")
247
+ unless headers[CACHE_CONTROL] =~ /no-cache/
248
+ set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}"
249
+ set_header EXPIRES, (Time.now + duration).httpdate
250
+ end
251
+ end
252
+
215
253
  def etag
216
254
  get_header ETAG
217
255
  end
218
256
 
219
- def etag= v
257
+ def etag=(v)
220
258
  set_header ETAG, v
221
259
  end
222
260
 
@@ -228,6 +266,9 @@ module Rack
228
266
  if @body.is_a?(Array)
229
267
  # The user supplied body was an array:
230
268
  @body = @body.compact
269
+ @body.each do |part|
270
+ @length += part.to_s.bytesize
271
+ end
231
272
  else
232
273
  # Turn the user supplied body into a buffered array:
233
274
  body = @body
@@ -236,6 +277,8 @@ module Rack
236
277
  body.each do |part|
237
278
  @writer.call(part.to_s)
238
279
  end
280
+
281
+ body.close if body.respond_to?(:close)
239
282
  end
240
283
 
241
284
  @buffered = true
@@ -261,7 +304,7 @@ module Rack
261
304
  attr_reader :headers
262
305
  attr_accessor :status
263
306
 
264
- def initialize status, headers
307
+ def initialize(status, headers)
265
308
  @status = status
266
309
  @headers = headers
267
310
  end
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'tempfile'
5
- require 'rack/utils'
6
5
 
7
6
  module Rack
8
7
  # Class which can make any IO object rewindable, including non-rewindable ones. It does
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/utils'
4
-
5
3
  module Rack
6
4
  # Sets an "X-Runtime" response header, indicating the response
7
5
  # time of the request, in seconds
@@ -22,9 +20,11 @@ module Rack
22
20
  def call(env)
23
21
  start_time = Utils.clock_time
24
22
  status, headers, body = @app.call(env)
23
+ headers = Utils::HeaderHash[headers]
24
+
25
25
  request_time = Utils.clock_time - start_time
26
26
 
27
- unless headers.has_key?(@header_name)
27
+ unless headers.key?(@header_name)
28
28
  headers[@header_name] = FORMAT_STRING % request_time
29
29
  end
30
30
 
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/files'
4
- require 'rack/body_proxy'
5
-
6
3
  module Rack
7
4
 
8
5
  # = Sendfile
@@ -3,12 +3,10 @@
3
3
  require 'optparse'
4
4
  require 'fileutils'
5
5
 
6
- require_relative 'core_ext/regexp'
7
-
8
6
  module Rack
9
7
 
10
8
  class Server
11
- using ::Rack::RegexpExtensions
9
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
12
10
 
13
11
  class Options
14
12
  def parse!(args)
@@ -42,7 +40,7 @@ module Rack
42
40
 
43
41
  opts.on("-r", "--require LIBRARY",
44
42
  "require the library, before executing your script") { |library|
45
- options[:require] = library
43
+ (options[:require] ||= []) << library
46
44
  }
47
45
 
48
46
  opts.separator ""
@@ -143,7 +141,7 @@ module Rack
143
141
  return "" if !has_options
144
142
  end
145
143
  info.join("\n")
146
- rescue NameError
144
+ rescue NameError, LoadError
147
145
  return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
148
146
  end
149
147
  end
@@ -262,13 +260,11 @@ module Rack
262
260
  m = Hash.new {|h, k| h[k] = []}
263
261
  m["deployment"] = [
264
262
  [Rack::ContentLength],
265
- [Rack::Chunked],
266
263
  logging_middleware,
267
264
  [Rack::TempfileReaper]
268
265
  ]
269
266
  m["development"] = [
270
267
  [Rack::ContentLength],
271
- [Rack::Chunked],
272
268
  logging_middleware,
273
269
  [Rack::ShowExceptions],
274
270
  [Rack::Lint],
@@ -287,7 +283,7 @@ module Rack
287
283
  self.class.middleware
288
284
  end
289
285
 
290
- def start &blk
286
+ def start(&block)
291
287
  if options[:warn]
292
288
  $-w = true
293
289
  end
@@ -296,7 +292,7 @@ module Rack
296
292
  $LOAD_PATH.unshift(*includes)
297
293
  end
298
294
 
299
- if library = options[:require]
295
+ Array(options[:require]).each do |library|
300
296
  require library
301
297
  end
302
298
 
@@ -328,7 +324,7 @@ module Rack
328
324
  end
329
325
  end
330
326
 
331
- server.run wrapped_app, options, &blk
327
+ server.run(wrapped_app, **options, &block)
332
328
  end
333
329
 
334
330
  def server
@@ -427,7 +423,10 @@ module Rack
427
423
  end
428
424
 
429
425
  def daemonize_app
426
+ # Cannot be covered as it forks
427
+ # :nocov:
430
428
  Process.daemon
429
+ # :nocov:
431
430
  end
432
431
 
433
432
  def write_pid
@@ -3,10 +3,8 @@
3
3
  # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
4
4
  # bugrep: Andreas Zehnder
5
5
 
6
- require 'rack'
6
+ require_relative '../../../rack'
7
7
  require 'time'
8
- require 'rack/request'
9
- require 'rack/response'
10
8
  require 'securerandom'
11
9
  require 'digest/sha2'
12
10
 
@@ -28,9 +26,9 @@ module Rack
28
26
  end
29
27
 
30
28
  alias :cookie_value :public_id
29
+ alias :to_s :public_id
31
30
 
32
31
  def empty?; false; end
33
- def to_s; raise; end
34
32
  def inspect; public_id.inspect; end
35
33
 
36
34
  private
@@ -44,26 +42,6 @@ module Rack
44
42
  # SessionHash is responsible to lazily load the session from store.
45
43
 
46
44
  class SessionHash
47
- using Module.new {
48
- refine Hash do
49
- def transform_keys(&block)
50
- hash = {}
51
- each do |key, value|
52
- hash[block.call(key)] = value
53
- end
54
- hash
55
- end
56
- end
57
- } unless {}.respond_to?(:transform_keys)
58
-
59
- def transform_keys(&block)
60
- hash = dup
61
- each do |key, value|
62
- hash[block.call(key)] = value
63
- end
64
- hash
65
- end
66
-
67
45
  include Enumerable
68
46
  attr_writer :id
69
47
 
@@ -106,6 +84,11 @@ module Rack
106
84
  @data[key.to_s]
107
85
  end
108
86
 
87
+ def dig(key, *keys)
88
+ load_for_read!
89
+ @data.dig(key.to_s, *keys)
90
+ end
91
+
109
92
  def fetch(key, default = Unspecified, &block)
110
93
  load_for_read!
111
94
  if default == Unspecified
@@ -209,14 +192,19 @@ module Rack
209
192
  end
210
193
 
211
194
  def stringify_keys(other)
212
- other.transform_keys(&:to_s)
195
+ # Use transform_keys after dropping Ruby 2.4 support
196
+ hash = {}
197
+ other.to_hash.each do |key, value|
198
+ hash[key.to_s] = value
199
+ end
200
+ hash
213
201
  end
214
202
  end
215
203
 
216
204
  # ID sets up a basic framework for implementing an id based sessioning
217
205
  # service. Cookies sent to the client for maintaining sessions will only
218
- # contain an id reference. Only #find_session and #write_session are
219
- # required to be overwritten.
206
+ # contain an id reference. Only #find_session, #write_session and
207
+ # #delete_session are required to be overwritten.
220
208
  #
221
209
  # All parameters are optional.
222
210
  # * :key determines the name of the cookie, by default it is
@@ -264,6 +252,7 @@ module Rack
264
252
  @default_options = self.class::DEFAULT_OPTIONS.merge(options)
265
253
  @key = @default_options.delete(:key)
266
254
  @cookie_only = @default_options.delete(:cookie_only)
255
+ @same_site = @default_options.delete(:same_site)
267
256
  initialize_sid
268
257
  end
269
258
 
@@ -405,6 +394,12 @@ module Rack
405
394
  cookie[:value] = cookie_value(data)
406
395
  cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
407
396
  cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
397
+
398
+ if @same_site.respond_to? :call
399
+ cookie[:same_site] = @same_site.call(req, res)
400
+ else
401
+ cookie[:same_site] = @same_site
402
+ end
408
403
  set_cookie(req, res, cookie.merge!(options))
409
404
  end
410
405
  end
@@ -460,7 +455,7 @@ module Rack
460
455
  def [](key)
461
456
  if key == "session_id"
462
457
  load_for_read!
463
- id.public_id
458
+ id.public_id if id
464
459
  else
465
460
  super
466
461
  end
@@ -2,9 +2,7 @@
2
2
 
3
3
  require 'openssl'
4
4
  require 'zlib'
5
- require 'rack/request'
6
- require 'rack/response'
7
- require 'rack/session/abstract/id'
5
+ require_relative 'abstract/id'
8
6
  require 'json'
9
7
  require 'base64'
10
8
 
@@ -5,7 +5,7 @@
5
5
  # apeiros, for session id generation, expiry setup, and threadiness
6
6
  # sergio, threadiness and bugreps
7
7
 
8
- require 'rack/session/abstract/id'
8
+ require_relative 'abstract/id'
9
9
  require 'thread'
10
10
 
11
11
  module Rack
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'ostruct'
4
4
  require 'erb'
5
- require 'rack/request'
6
- require 'rack/utils'
7
5
 
8
6
  module Rack
9
7
  # Rack::ShowExceptions catches all exceptions raised from the app it
@@ -65,12 +63,12 @@ module Rack
65
63
  def pretty(env, exception)
66
64
  req = Rack::Request.new(env)
67
65
 
68
- # This double assignment is to prevent an "unused variable" warning on
69
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
66
+ # This double assignment is to prevent an "unused variable" warning.
67
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
70
68
  path = path = (req.script_name + req.path_info).squeeze("/")
71
69
 
72
- # This double assignment is to prevent an "unused variable" warning on
73
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
70
+ # This double assignment is to prevent an "unused variable" warning.
71
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
74
72
  frames = frames = exception.backtrace.map { |line|
75
73
  frame = OpenStruct.new
76
74
  if line =~ /(.*?):(\d+)(:in `(.*)')?/
@@ -313,7 +311,7 @@ module Rack
313
311
  <% end %>
314
312
 
315
313
  <h3 id="post-info">POST</h3>
316
- <% if req.POST and not req.POST.empty? %>
314
+ <% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %>
317
315
  <table class="req">
318
316
  <thead>
319
317
  <tr>
@@ -331,7 +329,7 @@ module Rack
331
329
  </tbody>
332
330
  </table>
333
331
  <% else %>
334
- <p>No POST data.</p>
332
+ <p><%= no_post_data || "No POST data" %>.</p>
335
333
  <% end %>
336
334
 
337
335