rack 2.2.9 → 3.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +330 -88
  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 -4
  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 +23 -18
  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/method_override.rb +5 -1
  30. data/lib/rack/mime.rb +14 -5
  31. data/lib/rack/mock.rb +1 -271
  32. data/lib/rack/mock_request.rb +161 -0
  33. data/lib/rack/mock_response.rb +124 -0
  34. data/lib/rack/multipart/generator.rb +7 -5
  35. data/lib/rack/multipart/parser.rb +213 -95
  36. data/lib/rack/multipart/uploaded_file.rb +4 -0
  37. data/lib/rack/multipart.rb +53 -40
  38. data/lib/rack/null_logger.rb +9 -0
  39. data/lib/rack/query_parser.rb +81 -102
  40. data/lib/rack/recursive.rb +2 -0
  41. data/lib/rack/reloader.rb +0 -2
  42. data/lib/rack/request.rb +260 -123
  43. data/lib/rack/response.rb +151 -66
  44. data/lib/rack/rewindable_input.rb +24 -5
  45. data/lib/rack/runtime.rb +7 -6
  46. data/lib/rack/sendfile.rb +30 -25
  47. data/lib/rack/show_exceptions.rb +21 -4
  48. data/lib/rack/show_status.rb +17 -7
  49. data/lib/rack/static.rb +8 -8
  50. data/lib/rack/tempfile_reaper.rb +15 -4
  51. data/lib/rack/urlmap.rb +3 -1
  52. data/lib/rack/utils.rb +232 -233
  53. data/lib/rack/version.rb +1 -9
  54. data/lib/rack.rb +13 -89
  55. metadata +15 -41
  56. data/README.rdoc +0 -320
  57. data/Rakefile +0 -130
  58. data/bin/rackup +0 -5
  59. data/contrib/rack.png +0 -0
  60. data/contrib/rack.svg +0 -150
  61. data/contrib/rack_logo.svg +0 -164
  62. data/contrib/rdoc.css +0 -412
  63. data/example/lobster.ru +0 -6
  64. data/example/protectedlobster.rb +0 -16
  65. data/example/protectedlobster.ru +0 -10
  66. data/lib/rack/auth/digest/md5.rb +0 -131
  67. data/lib/rack/auth/digest/nonce.rb +0 -54
  68. data/lib/rack/auth/digest/params.rb +0 -54
  69. data/lib/rack/auth/digest/request.rb +0 -43
  70. data/lib/rack/chunked.rb +0 -117
  71. data/lib/rack/core_ext/regexp.rb +0 -14
  72. data/lib/rack/file.rb +0 -7
  73. data/lib/rack/handler/cgi.rb +0 -59
  74. data/lib/rack/handler/fastcgi.rb +0 -100
  75. data/lib/rack/handler/lsws.rb +0 -61
  76. data/lib/rack/handler/scgi.rb +0 -71
  77. data/lib/rack/handler/thin.rb +0 -36
  78. data/lib/rack/handler/webrick.rb +0 -129
  79. data/lib/rack/handler.rb +0 -104
  80. data/lib/rack/lobster.rb +0 -70
  81. data/lib/rack/server.rb +0 -466
  82. data/lib/rack/session/abstract/id.rb +0 -523
  83. data/lib/rack/session/cookie.rb +0 -204
  84. data/lib/rack/session/memcache.rb +0 -10
  85. data/lib/rack/session/pool.rb +0 -85
  86. 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>