rack 2.2.10 → 3.1.10

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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +346 -90
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +328 -0
  6. data/SPEC.rdoc +204 -131
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -1
  9. data/lib/rack/auth/basic.rb +1 -3
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +21 -3
  12. data/lib/rack/builder.rb +102 -69
  13. data/lib/rack/cascade.rb +2 -3
  14. data/lib/rack/common_logger.rb +25 -19
  15. data/lib/rack/conditional_get.rb +18 -15
  16. data/lib/rack/constants.rb +67 -0
  17. data/lib/rack/content_length.rb +12 -16
  18. data/lib/rack/content_type.rb +8 -5
  19. data/lib/rack/deflater.rb +40 -26
  20. data/lib/rack/directory.rb +9 -3
  21. data/lib/rack/etag.rb +14 -23
  22. data/lib/rack/events.rb +4 -0
  23. data/lib/rack/files.rb +15 -17
  24. data/lib/rack/head.rb +9 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +840 -644
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/logger.rb +3 -0
  29. data/lib/rack/media_type.rb +8 -3
  30. data/lib/rack/method_override.rb +5 -1
  31. data/lib/rack/mime.rb +14 -5
  32. data/lib/rack/mock.rb +1 -271
  33. data/lib/rack/mock_request.rb +161 -0
  34. data/lib/rack/mock_response.rb +124 -0
  35. data/lib/rack/multipart/generator.rb +7 -5
  36. data/lib/rack/multipart/parser.rb +213 -95
  37. data/lib/rack/multipart/uploaded_file.rb +4 -0
  38. data/lib/rack/multipart.rb +53 -40
  39. data/lib/rack/null_logger.rb +9 -0
  40. data/lib/rack/query_parser.rb +81 -102
  41. data/lib/rack/recursive.rb +2 -0
  42. data/lib/rack/reloader.rb +0 -2
  43. data/lib/rack/request.rb +260 -123
  44. data/lib/rack/response.rb +151 -66
  45. data/lib/rack/rewindable_input.rb +24 -5
  46. data/lib/rack/runtime.rb +7 -6
  47. data/lib/rack/sendfile.rb +30 -25
  48. data/lib/rack/show_exceptions.rb +21 -4
  49. data/lib/rack/show_status.rb +17 -7
  50. data/lib/rack/static.rb +8 -8
  51. data/lib/rack/tempfile_reaper.rb +15 -4
  52. data/lib/rack/urlmap.rb +3 -1
  53. data/lib/rack/utils.rb +236 -237
  54. data/lib/rack/version.rb +1 -9
  55. data/lib/rack.rb +13 -89
  56. metadata +15 -44
  57. data/README.rdoc +0 -320
  58. data/Rakefile +0 -130
  59. data/bin/rackup +0 -5
  60. data/contrib/rack.png +0 -0
  61. data/contrib/rack.svg +0 -150
  62. data/contrib/rack_logo.svg +0 -164
  63. data/contrib/rdoc.css +0 -412
  64. data/example/lobster.ru +0 -6
  65. data/example/protectedlobster.rb +0 -16
  66. data/example/protectedlobster.ru +0 -10
  67. data/lib/rack/auth/digest/md5.rb +0 -131
  68. data/lib/rack/auth/digest/nonce.rb +0 -53
  69. data/lib/rack/auth/digest/params.rb +0 -54
  70. data/lib/rack/auth/digest/request.rb +0 -43
  71. data/lib/rack/chunked.rb +0 -117
  72. data/lib/rack/core_ext/regexp.rb +0 -14
  73. data/lib/rack/file.rb +0 -7
  74. data/lib/rack/handler/cgi.rb +0 -59
  75. data/lib/rack/handler/fastcgi.rb +0 -100
  76. data/lib/rack/handler/lsws.rb +0 -61
  77. data/lib/rack/handler/scgi.rb +0 -71
  78. data/lib/rack/handler/thin.rb +0 -36
  79. data/lib/rack/handler/webrick.rb +0 -129
  80. data/lib/rack/handler.rb +0 -104
  81. data/lib/rack/lobster.rb +0 -70
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -203
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -85
  87. data/rack.gemspec +0 -46
data/lib/rack/response.rb CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  require 'time'
4
4
 
5
+ require_relative 'constants'
6
+ require_relative 'utils'
7
+ require_relative 'media_type'
8
+ require_relative 'headers'
9
+
5
10
  module Rack
6
11
  # Rack::Response provides a convenient interface to create a Rack
7
12
  # response.
@@ -26,22 +31,38 @@ module Rack
26
31
  attr_accessor :length, :status, :body
27
32
  attr_reader :headers
28
33
 
29
- # @deprecated Use {#headers} instead.
30
- alias header headers
31
-
32
- # Initialize the response object with the specified body, status
33
- # and headers.
34
+ # Initialize the response object with the specified +body+, +status+
35
+ # and +headers+.
36
+ #
37
+ # If the +body+ is +nil+, construct an empty response object with internal
38
+ # buffering.
39
+ #
40
+ # If the +body+ responds to +to_str+, assume it's a string-like object and
41
+ # construct a buffered response object containing using that string as the
42
+ # initial contents of the buffer.
43
+ #
44
+ # Otherwise it is expected +body+ conforms to the normal requirements of a
45
+ # Rack response body, typically implementing one of +each+ (enumerable
46
+ # body) or +call+ (streaming body).
34
47
  #
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.
48
+ # The +status+ defaults to +200+ which is the "OK" HTTP status code. You
49
+ # can provide any other valid status code.
40
50
  #
41
- # Providing a body which responds to #to_str is legacy behaviour.
51
+ # The +headers+ must be a +Hash+ of key-value header pairs which conform to
52
+ # the Rack specification for response headers. The key must be a +String+
53
+ # instance and the value can be either a +String+ or +Array+ instance.
42
54
  def initialize(body = nil, status = 200, headers = {})
43
55
  @status = status.to_i
44
- @headers = Utils::HeaderHash[headers]
56
+
57
+ unless headers.is_a?(Hash)
58
+ raise ArgumentError, "Headers must be a Hash!"
59
+ end
60
+
61
+ @headers = Headers.new
62
+ # Convert headers input to a plain hash with lowercase keys.
63
+ headers.each do |k, v|
64
+ @headers[k] = v
65
+ end
45
66
 
46
67
  @writer = self.method(:append)
47
68
 
@@ -51,15 +72,16 @@ module Rack
51
72
  if body.nil?
52
73
  @body = []
53
74
  @buffered = true
54
- @length = 0
75
+ # Body is unspecified - it may be a buffered response, or it may be a HEAD response.
76
+ @length = nil
55
77
  elsif body.respond_to?(:to_str)
56
78
  @body = [body]
57
79
  @buffered = true
58
80
  @length = body.to_str.bytesize
59
81
  else
60
82
  @body = body
61
- @buffered = false
62
- @length = 0
83
+ @buffered = nil # undetermined as of yet.
84
+ @length = nil
63
85
  end
64
86
 
65
87
  yield self if block_given?
@@ -74,20 +96,30 @@ module Rack
74
96
  CHUNKED == get_header(TRANSFER_ENCODING)
75
97
  end
76
98
 
99
+ def no_entity_body?
100
+ # The response body is an enumerable body and it is not allowed to have an entity body.
101
+ @body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
102
+ end
103
+
77
104
  # Generate a response array consistent with the requirements of the SPEC.
78
105
  # @return [Array] a 3-tuple suitable of `[status, headers, body]`
79
106
  # which is suitable to be returned from the middleware `#call(env)` method.
80
107
  def finish(&block)
81
- if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
108
+ if no_entity_body?
82
109
  delete_header CONTENT_TYPE
83
110
  delete_header CONTENT_LENGTH
84
111
  close
85
112
  return [@status, @headers, []]
86
113
  else
87
114
  if block_given?
115
+ # We don't add the content-length here as the user has provided a block that can #write additional chunks to the body.
88
116
  @block = block
89
117
  return [@status, @headers, self]
90
118
  else
119
+ # If we know the length of the body, set the content-length header... except if we are chunked? which is a legacy special case where the body might already be encoded and thus the actual encoded body length and the content-length are likely to be different.
120
+ if @length && !chunked?
121
+ @headers[CONTENT_LENGTH] = @length.to_s
122
+ end
91
123
  return [@status, @headers, @body]
92
124
  end
93
125
  end
@@ -105,7 +137,9 @@ module Rack
105
137
  end
106
138
  end
107
139
 
108
- # Append to body and update Content-Length.
140
+ # Append a chunk to the response body.
141
+ #
142
+ # Converts the response into a buffered response if it wasn't already.
109
143
  #
110
144
  # NOTE: Do not mix #write and direct #body access!
111
145
  #
@@ -123,10 +157,22 @@ module Rack
123
157
  @block == nil && @body.empty?
124
158
  end
125
159
 
126
- def has_header?(key); headers.key? key; end
127
- def get_header(key); headers[key]; end
128
- def set_header(key, v); headers[key] = v; end
129
- def delete_header(key); headers.delete key; end
160
+ def has_header?(key)
161
+ raise ArgumentError unless key.is_a?(String)
162
+ @headers.key?(key)
163
+ end
164
+ def get_header(key)
165
+ raise ArgumentError unless key.is_a?(String)
166
+ @headers[key]
167
+ end
168
+ def set_header(key, value)
169
+ raise ArgumentError unless key.is_a?(String)
170
+ @headers[key] = value
171
+ end
172
+ def delete_header(key)
173
+ raise ArgumentError unless key.is_a?(String)
174
+ @headers.delete key
175
+ end
130
176
 
131
177
  alias :[] :get_header
132
178
  alias :[]= :set_header
@@ -150,31 +196,43 @@ module Rack
150
196
  def forbidden?; status == 403; end
151
197
  def not_found?; status == 404; end
152
198
  def method_not_allowed?; status == 405; end
199
+ def not_acceptable?; status == 406; end
200
+ def request_timeout?; status == 408; end
153
201
  def precondition_failed?; status == 412; end
154
202
  def unprocessable?; status == 422; end
155
203
 
156
204
  def redirect?; [301, 302, 303, 307, 308].include? status; end
157
205
 
158
206
  def include?(header)
159
- has_header? header
207
+ has_header?(header)
160
208
  end
161
209
 
162
210
  # Add a header that may have multiple values.
163
211
  #
164
212
  # Example:
165
- # response.add_header 'Vary', 'Accept-Encoding'
166
- # response.add_header 'Vary', 'Cookie'
213
+ # response.add_header 'vary', 'accept-encoding'
214
+ # response.add_header 'vary', 'cookie'
167
215
  #
168
- # assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
216
+ # assert_equal 'accept-encoding,cookie', response.get_header('vary')
169
217
  #
170
218
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
171
- def add_header(key, v)
172
- if v.nil?
173
- get_header key
174
- elsif has_header? key
175
- set_header key, "#{get_header key},#{v}"
219
+ def add_header(key, value)
220
+ raise ArgumentError unless key.is_a?(String)
221
+
222
+ if value.nil?
223
+ return get_header(key)
224
+ end
225
+
226
+ value = value.to_s
227
+
228
+ if header = get_header(key)
229
+ if header.is_a?(Array)
230
+ header << value
231
+ else
232
+ set_header(key, [header, value])
233
+ end
176
234
  else
177
- set_header key, v
235
+ set_header(key, value)
178
236
  end
179
237
  end
180
238
 
@@ -202,36 +260,39 @@ module Rack
202
260
  end
203
261
 
204
262
  def location
205
- get_header "Location"
263
+ get_header "location"
206
264
  end
207
265
 
208
266
  def location=(location)
209
- set_header "Location", location
267
+ set_header "location", location
210
268
  end
211
269
 
212
270
  def set_cookie(key, value)
213
- cookie_header = get_header SET_COOKIE
214
- set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
271
+ add_header SET_COOKIE, Utils.set_cookie_header(key, value)
215
272
  end
216
273
 
217
274
  def delete_cookie(key, value = {})
218
- set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
275
+ set_header(SET_COOKIE,
276
+ Utils.delete_set_cookie_header!(
277
+ get_header(SET_COOKIE), key, value
278
+ )
279
+ )
219
280
  end
220
281
 
221
282
  def set_cookie_header
222
283
  get_header SET_COOKIE
223
284
  end
224
285
 
225
- def set_cookie_header=(v)
226
- set_header SET_COOKIE, v
286
+ def set_cookie_header=(value)
287
+ set_header SET_COOKIE, value
227
288
  end
228
289
 
229
290
  def cache_control
230
291
  get_header CACHE_CONTROL
231
292
  end
232
293
 
233
- def cache_control=(v)
234
- set_header CACHE_CONTROL, v
294
+ def cache_control=(value)
295
+ set_header CACHE_CONTROL, value
235
296
  end
236
297
 
237
298
  # Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
@@ -254,42 +315,55 @@ module Rack
254
315
  get_header ETAG
255
316
  end
256
317
 
257
- def etag=(v)
258
- set_header ETAG, v
318
+ def etag=(value)
319
+ set_header ETAG, value
259
320
  end
260
321
 
261
322
  protected
262
323
 
324
+ # Convert the body of this response into an internally buffered Array if possible.
325
+ #
326
+ # `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
327
+ # * `nil` - The body has not been buffered yet.
328
+ # * `true` - The body is buffered as an Array instance.
329
+ # * `false` - The body is not buffered and cannot be buffered.
330
+ #
331
+ # @return [Boolean] whether the body is buffered as an Array instance.
263
332
  def buffered_body!
264
- return if @buffered
265
-
266
- if @body.is_a?(Array)
267
- # The user supplied body was an array:
268
- @body = @body.compact
269
- @body.each do |part|
270
- @length += part.to_s.bytesize
271
- end
272
- else
273
- # Turn the user supplied body into a buffered array:
274
- body = @body
275
- @body = Array.new
276
-
277
- body.each do |part|
278
- @writer.call(part.to_s)
333
+ if @buffered.nil?
334
+ if @body.is_a?(Array)
335
+ # The user supplied body was an array:
336
+ @body = @body.compact
337
+ @length = @body.sum{|part| part.bytesize}
338
+ @buffered = true
339
+ elsif @body.respond_to?(:each)
340
+ # Turn the user supplied body into a buffered array:
341
+ body = @body
342
+ @body = Array.new
343
+ @buffered = true
344
+
345
+ body.each do |part|
346
+ @writer.call(part.to_s)
347
+ end
348
+
349
+ body.close if body.respond_to?(:close)
350
+ else
351
+ # We don't know how to buffer the user-supplied body:
352
+ @buffered = false
279
353
  end
280
-
281
- body.close if body.respond_to?(:close)
282
354
  end
283
355
 
284
- @buffered = true
356
+ return @buffered
285
357
  end
286
358
 
287
359
  def append(chunk)
360
+ chunk = chunk.dup unless chunk.frozen?
288
361
  @body << chunk
289
362
 
290
- unless chunked?
363
+ if @length
291
364
  @length += chunk.bytesize
292
- set_header(CONTENT_LENGTH, @length.to_s)
365
+ elsif @buffered
366
+ @length = chunk.bytesize
293
367
  end
294
368
 
295
369
  return chunk
@@ -309,10 +383,21 @@ module Rack
309
383
  @headers = headers
310
384
  end
311
385
 
312
- def has_header?(key); headers.key? key; end
313
- def get_header(key); headers[key]; end
314
- def set_header(key, v); headers[key] = v; end
315
- def delete_header(key); headers.delete key; end
386
+ def has_header?(key)
387
+ headers.key?(key)
388
+ end
389
+
390
+ def get_header(key)
391
+ headers[key]
392
+ end
393
+
394
+ def set_header(key, value)
395
+ headers[key] = value
396
+ end
397
+
398
+ def delete_header(key)
399
+ headers.delete(key)
400
+ end
316
401
  end
317
402
  end
318
403
  end
@@ -3,17 +3,29 @@
3
3
 
4
4
  require 'tempfile'
5
5
 
6
+ require_relative 'constants'
7
+
6
8
  module Rack
7
9
  # Class which can make any IO object rewindable, including non-rewindable ones. It does
8
10
  # this by buffering the data into a tempfile, which is rewindable.
9
11
  #
10
- # rack.input is required to be rewindable, so if your input stream IO is non-rewindable
11
- # by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class
12
- # to easily make it rewindable.
13
- #
14
12
  # Don't forget to call #close when you're done. This frees up temporary resources that
15
13
  # RewindableInput uses, though it does *not* close the original IO object.
16
14
  class RewindableInput
15
+ # Makes rack.input rewindable, for compatibility with applications and middleware
16
+ # designed for earlier versions of Rack (where rack.input was required to be
17
+ # rewindable).
18
+ class Middleware
19
+ def initialize(app)
20
+ @app = app
21
+ end
22
+
23
+ def call(env)
24
+ env[RACK_INPUT] = RewindableInput.new(env[RACK_INPUT])
25
+ @app.call(env)
26
+ end
27
+ end
28
+
17
29
  def initialize(io)
18
30
  @io = io
19
31
  @rewindable_io = nil
@@ -40,6 +52,11 @@ module Rack
40
52
  @rewindable_io.rewind
41
53
  end
42
54
 
55
+ def size
56
+ make_rewindable unless @rewindable_io
57
+ @rewindable_io.size
58
+ end
59
+
43
60
  # Closes this RewindableInput object without closing the originally
44
61
  # wrapped IO object. Cleans up any temporary resources that this RewindableInput
45
62
  # has created.
@@ -66,12 +83,14 @@ module Rack
66
83
  # access it because we have the file handle open.
67
84
  @rewindable_io = Tempfile.new('RackRewindableInput')
68
85
  @rewindable_io.chmod(0000)
69
- @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
86
+ @rewindable_io.set_encoding(Encoding::BINARY)
70
87
  @rewindable_io.binmode
88
+ # :nocov:
71
89
  if filesystem_has_posix_semantics?
72
90
  raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
73
91
  @unlinked = true
74
92
  end
93
+ # :nocov:
75
94
 
76
95
  buffer = "".dup
77
96
  while @io.read(1024 * 4, buffer)
data/lib/rack/runtime.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'utils'
4
+
3
5
  module Rack
4
- # Sets an "X-Runtime" response header, indicating the response
6
+ # Sets an "x-runtime" response header, indicating the response
5
7
  # time of the request, in seconds
6
8
  #
7
9
  # You can put it right before the application to see the processing
@@ -9,18 +11,17 @@ module Rack
9
11
  # too.
10
12
  class Runtime
11
13
  FORMAT_STRING = "%0.6f" # :nodoc:
12
- HEADER_NAME = "X-Runtime" # :nodoc:
14
+ HEADER_NAME = "x-runtime" # :nodoc:
13
15
 
14
16
  def initialize(app, name = nil)
15
17
  @app = app
16
18
  @header_name = HEADER_NAME
17
- @header_name += "-#{name}" if name
19
+ @header_name += "-#{name.to_s.downcase}" if name
18
20
  end
19
21
 
20
22
  def call(env)
21
23
  start_time = Utils.clock_time
22
- status, headers, body = @app.call(env)
23
- headers = Utils::HeaderHash[headers]
24
+ _, headers, _ = response = @app.call(env)
24
25
 
25
26
  request_time = Utils.clock_time - start_time
26
27
 
@@ -28,7 +29,7 @@ module Rack
28
29
  headers[@header_name] = FORMAT_STRING % request_time
29
30
  end
30
31
 
31
- [status, headers, body]
32
+ response
32
33
  end
33
34
  end
34
35
  end
data/lib/rack/sendfile.rb CHANGED
@@ -1,32 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+ require_relative 'body_proxy'
6
+
3
7
  module Rack
4
8
 
5
9
  # = Sendfile
6
10
  #
7
11
  # The Sendfile middleware intercepts responses whose body is being
8
- # served from a file and replaces it with a server specific X-Sendfile
12
+ # served from a file and replaces it with a server specific x-sendfile
9
13
  # header. The web server is then responsible for writing the file contents
10
14
  # to the client. This can dramatically reduce the amount of work required
11
15
  # by the Ruby backend and takes advantage of the web server's optimized file
12
16
  # delivery code.
13
17
  #
14
18
  # In order to take advantage of this middleware, the response body must
15
- # respond to +to_path+ and the request must include an X-Sendfile-Type
19
+ # respond to +to_path+ and the request must include an x-sendfile-type
16
20
  # header. Rack::Files and other components implement +to_path+ so there's
17
- # rarely anything you need to do in your application. The X-Sendfile-Type
21
+ # rarely anything you need to do in your application. The x-sendfile-type
18
22
  # header is typically set in your web servers configuration. The following
19
23
  # sections attempt to document
20
24
  #
21
25
  # === Nginx
22
26
  #
23
- # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
27
+ # Nginx supports the x-accel-redirect header. This is similar to x-sendfile
24
28
  # but requires parts of the filesystem to be mapped into a private URL
25
29
  # hierarchy.
26
30
  #
27
31
  # The following example shows the Nginx configuration required to create
28
- # a private "/files/" area, enable X-Accel-Redirect, and pass the special
29
- # X-Sendfile-Type and X-Accel-Mapping headers to the backend:
32
+ # a private "/files/" area, enable x-accel-redirect, and pass the special
33
+ # x-sendfile-type and x-accel-mapping headers to the backend:
30
34
  #
31
35
  # location ~ /files/(.*) {
32
36
  # internal;
@@ -40,14 +44,14 @@ module Rack
40
44
  # proxy_set_header X-Real-IP $remote_addr;
41
45
  # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
42
46
  #
43
- # proxy_set_header X-Sendfile-Type X-Accel-Redirect;
44
- # proxy_set_header X-Accel-Mapping /var/www/=/files/;
47
+ # proxy_set_header x-sendfile-type x-accel-redirect;
48
+ # proxy_set_header x-accel-mapping /var/www/=/files/;
45
49
  #
46
50
  # proxy_pass http://127.0.0.1:8080/;
47
51
  # }
48
52
  #
49
- # Note that the X-Sendfile-Type header must be set exactly as shown above.
50
- # The X-Accel-Mapping header should specify the location on the file system,
53
+ # Note that the x-sendfile-type header must be set exactly as shown above.
54
+ # The x-accel-mapping header should specify the location on the file system,
51
55
  # followed by an equals sign (=), followed name of the private URL pattern
52
56
  # that it maps to. The middleware performs a simple substitution on the
53
57
  # resulting path.
@@ -56,8 +60,8 @@ module Rack
56
60
  #
57
61
  # === lighttpd
58
62
  #
59
- # Lighttpd has supported some variation of the X-Sendfile header for some
60
- # time, although only recent version support X-Sendfile in a reverse proxy
63
+ # Lighttpd has supported some variation of the x-sendfile header for some
64
+ # time, although only recent version support x-sendfile in a reverse proxy
61
65
  # configuration.
62
66
  #
63
67
  # $HTTP["host"] == "example.com" {
@@ -71,7 +75,7 @@ module Rack
71
75
  #
72
76
  # proxy-core.allow-x-sendfile = "enable"
73
77
  # proxy-core.rewrite-request = (
74
- # "X-Sendfile-Type" => (".*" => "X-Sendfile")
78
+ # "x-sendfile-type" => (".*" => "x-sendfile")
75
79
  # )
76
80
  # }
77
81
  #
@@ -79,21 +83,21 @@ module Rack
79
83
  #
80
84
  # === Apache
81
85
  #
82
- # X-Sendfile is supported under Apache 2.x using a separate module:
86
+ # x-sendfile is supported under Apache 2.x using a separate module:
83
87
  #
84
88
  # https://tn123.org/mod_xsendfile/
85
89
  #
86
90
  # Once the module is compiled and installed, you can enable it using
87
91
  # XSendFile config directive:
88
92
  #
89
- # RequestHeader Set X-Sendfile-Type X-Sendfile
93
+ # RequestHeader Set x-sendfile-type x-sendfile
90
94
  # ProxyPassReverse / http://localhost:8001/
91
95
  # XSendFile on
92
96
  #
93
97
  # === Mapping parameter
94
98
  #
95
99
  # The third parameter allows for an overriding extension of the
96
- # X-Accel-Mapping header. Mappings should be provided in tuples of internal to
100
+ # x-accel-mapping header. Mappings should be provided in tuples of internal to
97
101
  # external. The internal values may contain regular expression syntax, they
98
102
  # will be matched with case indifference.
99
103
 
@@ -107,28 +111,29 @@ module Rack
107
111
  end
108
112
 
109
113
  def call(env)
110
- status, headers, body = @app.call(env)
114
+ _, headers, body = response = @app.call(env)
115
+
111
116
  if body.respond_to?(:to_path)
112
117
  case type = variation(env)
113
- when 'X-Accel-Redirect'
118
+ when /x-accel-redirect/i
114
119
  path = ::File.expand_path(body.to_path)
115
120
  if url = map_accel_path(env, path)
116
121
  headers[CONTENT_LENGTH] = '0'
117
122
  # '?' must be percent-encoded because it is not query string but a part of path
118
- headers[type] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
123
+ headers[type.downcase] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
119
124
  obody = body
120
- body = Rack::BodyProxy.new([]) do
125
+ response[2] = Rack::BodyProxy.new([]) do
121
126
  obody.close if obody.respond_to?(:close)
122
127
  end
123
128
  else
124
- env[RACK_ERRORS].puts "X-Accel-Mapping header missing"
129
+ env[RACK_ERRORS].puts "x-accel-mapping header missing"
125
130
  end
126
- when 'X-Sendfile', 'X-Lighttpd-Send-File'
131
+ when /x-sendfile|x-lighttpd-send-file/i
127
132
  path = ::File.expand_path(body.to_path)
128
133
  headers[CONTENT_LENGTH] = '0'
129
- headers[type] = path
134
+ headers[type.downcase] = path
130
135
  obody = body
131
- body = Rack::BodyProxy.new([]) do
136
+ response[2] = Rack::BodyProxy.new([]) do
132
137
  obody.close if obody.respond_to?(:close)
133
138
  end
134
139
  when '', nil
@@ -136,7 +141,7 @@ module Rack
136
141
  env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n"
137
142
  end
138
143
  end
139
- [status, headers, body]
144
+ response
140
145
  end
141
146
 
142
147
  private
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ostruct'
4
3
  require 'erb'
5
4
 
5
+ require_relative 'constants'
6
+ require_relative 'utils'
7
+ require_relative 'request'
8
+
6
9
  module Rack
7
10
  # Rack::ShowExceptions catches all exceptions raised from the app it
8
11
  # wraps. It shows a useful backtrace with the sourcefile and
@@ -15,6 +18,11 @@ module Rack
15
18
  class ShowExceptions
16
19
  CONTEXT = 7
17
20
 
21
+ Frame = Struct.new(:filename, :lineno, :function,
22
+ :pre_context_lineno, :pre_context,
23
+ :context_line, :post_context_lineno,
24
+ :post_context)
25
+
18
26
  def initialize(app)
19
27
  @app = app
20
28
  end
@@ -55,7 +63,12 @@ module Rack
55
63
  private :accepts_html?
56
64
 
57
65
  def dump_exception(exception)
58
- string = "#{exception.class}: #{exception.message}\n".dup
66
+ if exception.respond_to?(:detailed_message)
67
+ message = exception.detailed_message(highlight: false)
68
+ else
69
+ message = exception.message
70
+ end
71
+ string = "#{exception.class}: #{message}\n".dup
59
72
  string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
60
73
  string
61
74
  end
@@ -70,7 +83,7 @@ module Rack
70
83
  # This double assignment is to prevent an "unused variable" warning.
71
84
  # Yes, it is dumb, but I don't like Ruby yelling at me.
72
85
  frames = frames = exception.backtrace.map { |line|
73
- frame = OpenStruct.new
86
+ frame = Frame.new
74
87
  if line =~ /(.*?):(\d+)(:in `(.*)')?/
75
88
  frame.filename = $1
76
89
  frame.lineno = $2.to_i
@@ -159,7 +172,7 @@ module Rack
159
172
  div.commands { margin-left: 40px; }
160
173
  div.commands a { color:black; text-decoration:none; }
161
174
  #summary { background: #ffc; }
162
- #summary h2 { font-weight: normal; color: #666; }
175
+ #summary h2 { font-family: monospace; font-weight: normal; color: #666; white-space: pre-wrap; }
163
176
  #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
164
177
  #summary ul#quicklinks li { float: left; padding: 0 1em; }
165
178
  #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
@@ -227,7 +240,11 @@ module Rack
227
240
 
228
241
  <div id="summary">
229
242
  <h1><%=h exception.class %> at <%=h path %></h1>
243
+ <% if exception.respond_to?(:detailed_message) %>
244
+ <h2><%=h exception.detailed_message(highlight: false) %></h2>
245
+ <% else %>
230
246
  <h2><%=h exception.message %></h2>
247
+ <% end %>
231
248
  <table><tr>
232
249
  <th>Ruby</th>
233
250
  <td>