rack 2.1.4.4 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

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 +598 -15
  3. data/CONTRIBUTING.md +136 -0
  4. data/README.rdoc +84 -54
  5. data/Rakefile +14 -7
  6. data/{SPEC → SPEC.rdoc} +35 -6
  7. data/lib/rack/auth/abstract/request.rb +0 -2
  8. data/lib/rack/auth/basic.rb +3 -3
  9. data/lib/rack/auth/digest/md5.rb +4 -4
  10. data/lib/rack/auth/digest/request.rb +3 -3
  11. data/lib/rack/body_proxy.rb +13 -9
  12. data/lib/rack/builder.rb +77 -8
  13. data/lib/rack/cascade.rb +23 -8
  14. data/lib/rack/chunked.rb +48 -23
  15. data/lib/rack/common_logger.rb +25 -21
  16. data/lib/rack/conditional_get.rb +18 -16
  17. data/lib/rack/content_length.rb +6 -7
  18. data/lib/rack/content_type.rb +3 -4
  19. data/lib/rack/deflater.rb +45 -35
  20. data/lib/rack/directory.rb +77 -60
  21. data/lib/rack/etag.rb +2 -3
  22. data/lib/rack/events.rb +15 -18
  23. data/lib/rack/file.rb +1 -1
  24. data/lib/rack/files.rb +96 -56
  25. data/lib/rack/handler/cgi.rb +1 -4
  26. data/lib/rack/handler/fastcgi.rb +1 -3
  27. data/lib/rack/handler/lsws.rb +1 -3
  28. data/lib/rack/handler/scgi.rb +1 -3
  29. data/lib/rack/handler/thin.rb +15 -11
  30. data/lib/rack/handler/webrick.rb +12 -5
  31. data/lib/rack/head.rb +0 -2
  32. data/lib/rack/lint.rb +58 -15
  33. data/lib/rack/lobster.rb +3 -5
  34. data/lib/rack/lock.rb +0 -1
  35. data/lib/rack/mock.rb +22 -4
  36. data/lib/rack/multipart/generator.rb +11 -6
  37. data/lib/rack/multipart/parser.rb +12 -32
  38. data/lib/rack/multipart/uploaded_file.rb +13 -7
  39. data/lib/rack/multipart.rb +5 -4
  40. data/lib/rack/query_parser.rb +7 -8
  41. data/lib/rack/recursive.rb +1 -1
  42. data/lib/rack/reloader.rb +1 -3
  43. data/lib/rack/request.rb +172 -76
  44. data/lib/rack/response.rb +62 -19
  45. data/lib/rack/rewindable_input.rb +0 -1
  46. data/lib/rack/runtime.rb +3 -3
  47. data/lib/rack/sendfile.rb +0 -3
  48. data/lib/rack/server.rb +9 -8
  49. data/lib/rack/session/abstract/id.rb +20 -18
  50. data/lib/rack/session/cookie.rb +2 -3
  51. data/lib/rack/session/pool.rb +1 -1
  52. data/lib/rack/show_exceptions.rb +2 -4
  53. data/lib/rack/show_status.rb +1 -3
  54. data/lib/rack/static.rb +13 -6
  55. data/lib/rack/tempfile_reaper.rb +0 -2
  56. data/lib/rack/urlmap.rb +1 -4
  57. data/lib/rack/utils.rb +70 -82
  58. data/lib/rack/version.rb +29 -0
  59. data/lib/rack.rb +7 -16
  60. data/rack.gemspec +31 -29
  61. metadata +14 -15
data/lib/rack/response.rb CHANGED
@@ -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
data/lib/rack/runtime.rb CHANGED
@@ -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
 
data/lib/rack/sendfile.rb CHANGED
@@ -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
data/lib/rack/server.rb CHANGED
@@ -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
@@ -285,7 +283,7 @@ module Rack
285
283
  self.class.middleware
286
284
  end
287
285
 
288
- def start &blk
286
+ def start(&block)
289
287
  if options[:warn]
290
288
  $-w = true
291
289
  end
@@ -294,7 +292,7 @@ module Rack
294
292
  $LOAD_PATH.unshift(*includes)
295
293
  end
296
294
 
297
- if library = options[:require]
295
+ Array(options[:require]).each do |library|
298
296
  require library
299
297
  end
300
298
 
@@ -326,7 +324,7 @@ module Rack
326
324
  end
327
325
  end
328
326
 
329
- server.run wrapped_app, options, &blk
327
+ server.run(wrapped_app, **options, &block)
330
328
  end
331
329
 
332
330
  def server
@@ -425,7 +423,10 @@ module Rack
425
423
  end
426
424
 
427
425
  def daemonize_app
426
+ # Cannot be covered as it forks
427
+ # :nocov:
428
428
  Process.daemon
429
+ # :nocov:
429
430
  end
430
431
 
431
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
 
@@ -44,18 +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
45
  include Enumerable
60
46
  attr_writer :id
61
47
 
@@ -98,6 +84,11 @@ module Rack
98
84
  @data[key.to_s]
99
85
  end
100
86
 
87
+ def dig(key, *keys)
88
+ load_for_read!
89
+ @data.dig(key.to_s, *keys)
90
+ end
91
+
101
92
  def fetch(key, default = Unspecified, &block)
102
93
  load_for_read!
103
94
  if default == Unspecified
@@ -201,14 +192,19 @@ module Rack
201
192
  end
202
193
 
203
194
  def stringify_keys(other)
204
- other.to_hash.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
205
201
  end
206
202
  end
207
203
 
208
204
  # ID sets up a basic framework for implementing an id based sessioning
209
205
  # service. Cookies sent to the client for maintaining sessions will only
210
- # contain an id reference. Only #find_session and #write_session are
211
- # required to be overwritten.
206
+ # contain an id reference. Only #find_session, #write_session and
207
+ # #delete_session are required to be overwritten.
212
208
  #
213
209
  # All parameters are optional.
214
210
  # * :key determines the name of the cookie, by default it is
@@ -397,6 +393,12 @@ module Rack
397
393
  cookie[:value] = cookie_value(data)
398
394
  cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
399
395
  cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
396
+
397
+ if @same_site.respond_to? :call
398
+ cookie[:same_site] = @same_site.call(req, res)
399
+ else
400
+ cookie[:same_site] = @same_site
401
+ end
400
402
  set_cookie(req, res, cookie.merge!(options))
401
403
  end
402
404
  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
 
@@ -120,6 +118,7 @@ module Rack
120
118
  Called from: #{caller[0]}.
121
119
  MSG
122
120
  @coder = options[:coder] ||= Base64::Marshal.new
121
+ @same_site = options.delete :same_site
123
122
  super(app, options.merge!(cookie_only: true))
124
123
  end
125
124
 
@@ -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
@@ -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
 
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
- require 'rack/request'
5
- require 'rack/utils'
6
4
 
7
5
  module Rack
8
6
  # Rack::ShowStatus catches all empty responses and replaces them
@@ -20,7 +18,7 @@ module Rack
20
18
 
21
19
  def call(env)
22
20
  status, headers, body = @app.call(env)
23
- headers = Utils::HeaderHash.new(headers)
21
+ headers = Utils::HeaderHash[headers]
24
22
  empty = headers[CONTENT_LENGTH].to_i <= 0
25
23
 
26
24
  # client or server error, or explicit message
data/lib/rack/static.rb CHANGED
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rack/files"
4
- require "rack/utils"
5
-
6
- require_relative 'core_ext/regexp'
7
-
8
3
  module Rack
9
4
 
10
5
  # The Rack::Static middleware intercepts requests for static files
@@ -19,6 +14,11 @@ module Rack
19
14
  #
20
15
  # use Rack::Static, :urls => ["/media"]
21
16
  #
17
+ # Same as previous, but instead of returning 404 for missing files under
18
+ # /media, call the next middleware:
19
+ #
20
+ # use Rack::Static, :urls => ["/media"], :cascade => true
21
+ #
22
22
  # Serve all requests beginning with /css or /images from the folder "public"
23
23
  # in the current directory (ie public/css/* and public/images/*):
24
24
  #
@@ -86,13 +86,14 @@ module Rack
86
86
  # ]
87
87
  #
88
88
  class Static
89
- using ::Rack::RegexpExtensions
89
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
90
90
 
91
91
  def initialize(app, options = {})
92
92
  @app = app
93
93
  @urls = options[:urls] || ["/favicon.ico"]
94
94
  @index = options[:index]
95
95
  @gzip = options[:gzip]
96
+ @cascade = options[:cascade]
96
97
  root = options[:root] || Dir.pwd
97
98
 
98
99
  # HTTP Headers
@@ -133,6 +134,8 @@ module Rack
133
134
 
134
135
  if response[0] == 404
135
136
  response = nil
137
+ elsif response[0] == 304
138
+ # Do nothing, leave headers as is
136
139
  else
137
140
  if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
138
141
  response[1][CONTENT_TYPE] = mime_type
@@ -144,6 +147,10 @@ module Rack
144
147
  path = env[PATH_INFO]
145
148
  response ||= @file_server.call(env)
146
149
 
150
+ if @cascade && response[0] == 404
151
+ return @app.call(env)
152
+ end
153
+
147
154
  headers = response[1]
148
155
  applicable_rules(path).each do |rule, new_headers|
149
156
  new_headers.each { |field, content| headers[field] = content }
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/body_proxy'
4
-
5
3
  module Rack
6
4
 
7
5
  # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
data/lib/rack/urlmap.rb CHANGED
@@ -16,9 +16,6 @@ module Rack
16
16
  # first, since they are most specific.
17
17
 
18
18
  class URLMap
19
- NEGATIVE_INFINITY = -1.0 / 0.0
20
- INFINITY = 1.0 / 0.0
21
-
22
19
  def initialize(map = {})
23
20
  remap(map)
24
21
  end
@@ -42,7 +39,7 @@ module Rack
42
39
 
43
40
  [host, location, match, app]
44
41
  }.sort_by do |(host, location, _, _)|
45
- [host ? -host.size : INFINITY, -location.size]
42
+ [host ? -host.size : Float::INFINITY, -location.size]
46
43
  end
47
44
  end
48
45