rack 1.4.7 → 2.1.4

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 (183) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +77 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +122 -456
  5. data/Rakefile +32 -31
  6. data/SPEC +119 -29
  7. data/bin/rackup +1 -0
  8. data/contrib/rack_logo.svg +164 -111
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +4 -2
  11. data/example/protectedlobster.ru +3 -1
  12. data/lib/rack/auth/abstract/handler.rb +7 -5
  13. data/lib/rack/auth/abstract/request.rb +8 -6
  14. data/lib/rack/auth/basic.rb +5 -2
  15. data/lib/rack/auth/digest/md5.rb +10 -8
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +5 -4
  18. data/lib/rack/auth/digest/request.rb +4 -2
  19. data/lib/rack/body_proxy.rb +11 -9
  20. data/lib/rack/builder.rb +63 -20
  21. data/lib/rack/cascade.rb +10 -9
  22. data/lib/rack/chunked.rb +45 -11
  23. data/lib/rack/{commonlogger.rb → common_logger.rb} +24 -15
  24. data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -6
  25. data/lib/rack/config.rb +7 -0
  26. data/lib/rack/content_length.rb +12 -6
  27. data/lib/rack/content_type.rb +4 -2
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +73 -42
  30. data/lib/rack/directory.rb +77 -56
  31. data/lib/rack/etag.rb +25 -13
  32. data/lib/rack/events.rb +156 -0
  33. data/lib/rack/file.rb +4 -143
  34. data/lib/rack/files.rb +178 -0
  35. data/lib/rack/handler/cgi.rb +18 -17
  36. data/lib/rack/handler/fastcgi.rb +21 -17
  37. data/lib/rack/handler/lsws.rb +14 -12
  38. data/lib/rack/handler/scgi.rb +27 -21
  39. data/lib/rack/handler/thin.rb +19 -5
  40. data/lib/rack/handler/webrick.rb +66 -24
  41. data/lib/rack/handler.rb +29 -19
  42. data/lib/rack/head.rb +21 -14
  43. data/lib/rack/lint.rb +259 -65
  44. data/lib/rack/lobster.rb +17 -10
  45. data/lib/rack/lock.rb +19 -10
  46. data/lib/rack/logger.rb +4 -2
  47. data/lib/rack/media_type.rb +43 -0
  48. data/lib/rack/method_override.rb +52 -0
  49. data/lib/rack/mime.rb +43 -6
  50. data/lib/rack/mock.rb +109 -44
  51. data/lib/rack/multipart/generator.rb +11 -12
  52. data/lib/rack/multipart/parser.rb +302 -115
  53. data/lib/rack/multipart/uploaded_file.rb +4 -3
  54. data/lib/rack/multipart.rb +40 -9
  55. data/lib/rack/null_logger.rb +39 -0
  56. data/lib/rack/query_parser.rb +218 -0
  57. data/lib/rack/recursive.rb +14 -11
  58. data/lib/rack/reloader.rb +12 -5
  59. data/lib/rack/request.rb +484 -270
  60. data/lib/rack/response.rb +196 -77
  61. data/lib/rack/rewindable_input.rb +5 -14
  62. data/lib/rack/runtime.rb +13 -6
  63. data/lib/rack/sendfile.rb +44 -20
  64. data/lib/rack/server.rb +175 -61
  65. data/lib/rack/session/abstract/id.rb +276 -133
  66. data/lib/rack/session/cookie.rb +75 -40
  67. data/lib/rack/session/memcache.rb +4 -87
  68. data/lib/rack/session/pool.rb +24 -18
  69. data/lib/rack/show_exceptions.rb +392 -0
  70. data/lib/rack/{showstatus.rb → show_status.rb} +11 -9
  71. data/lib/rack/static.rb +65 -38
  72. data/lib/rack/tempfile_reaper.rb +24 -0
  73. data/lib/rack/urlmap.rb +40 -15
  74. data/lib/rack/utils.rb +316 -285
  75. data/lib/rack.rb +78 -23
  76. data/rack.gemspec +26 -19
  77. metadata +44 -209
  78. data/KNOWN-ISSUES +0 -30
  79. data/lib/rack/backports/uri/common_18.rb +0 -56
  80. data/lib/rack/backports/uri/common_192.rb +0 -52
  81. data/lib/rack/backports/uri/common_193.rb +0 -29
  82. data/lib/rack/handler/evented_mongrel.rb +0 -8
  83. data/lib/rack/handler/mongrel.rb +0 -100
  84. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  85. data/lib/rack/methodoverride.rb +0 -33
  86. data/lib/rack/nulllogger.rb +0 -18
  87. data/lib/rack/showexceptions.rb +0 -378
  88. data/test/builder/anything.rb +0 -5
  89. data/test/builder/comment.ru +0 -4
  90. data/test/builder/end.ru +0 -5
  91. data/test/builder/line.ru +0 -1
  92. data/test/builder/options.ru +0 -2
  93. data/test/cgi/assets/folder/test.js +0 -1
  94. data/test/cgi/assets/fonts/font.eot +0 -1
  95. data/test/cgi/assets/images/image.png +0 -1
  96. data/test/cgi/assets/index.html +0 -1
  97. data/test/cgi/assets/javascripts/app.js +0 -1
  98. data/test/cgi/assets/stylesheets/app.css +0 -1
  99. data/test/cgi/lighttpd.conf +0 -26
  100. data/test/cgi/lighttpd.errors +0 -1
  101. data/test/cgi/rackup_stub.rb +0 -6
  102. data/test/cgi/sample_rackup.ru +0 -5
  103. data/test/cgi/test +0 -9
  104. data/test/cgi/test+directory/test+file +0 -1
  105. data/test/cgi/test.fcgi +0 -8
  106. data/test/cgi/test.ru +0 -5
  107. data/test/gemloader.rb +0 -10
  108. data/test/multipart/bad_robots +0 -259
  109. data/test/multipart/binary +0 -0
  110. data/test/multipart/content_type_and_no_filename +0 -6
  111. data/test/multipart/empty +0 -10
  112. data/test/multipart/fail_16384_nofile +0 -814
  113. data/test/multipart/file1.txt +0 -1
  114. data/test/multipart/filename_and_modification_param +0 -7
  115. data/test/multipart/filename_with_escaped_quotes +0 -6
  116. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  117. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  118. data/test/multipart/filename_with_unescaped_percentages +0 -6
  119. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  120. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  121. data/test/multipart/filename_with_unescaped_quotes +0 -6
  122. data/test/multipart/ie +0 -6
  123. data/test/multipart/mixed_files +0 -21
  124. data/test/multipart/nested +0 -10
  125. data/test/multipart/none +0 -9
  126. data/test/multipart/semicolon +0 -6
  127. data/test/multipart/text +0 -15
  128. data/test/multipart/three_files_three_fields +0 -31
  129. data/test/multipart/webkit +0 -32
  130. data/test/rackup/config.ru +0 -31
  131. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  132. data/test/spec_auth.rb +0 -57
  133. data/test/spec_auth_basic.rb +0 -81
  134. data/test/spec_auth_digest.rb +0 -259
  135. data/test/spec_body_proxy.rb +0 -69
  136. data/test/spec_builder.rb +0 -207
  137. data/test/spec_cascade.rb +0 -61
  138. data/test/spec_cgi.rb +0 -102
  139. data/test/spec_chunked.rb +0 -87
  140. data/test/spec_commonlogger.rb +0 -57
  141. data/test/spec_conditionalget.rb +0 -102
  142. data/test/spec_config.rb +0 -22
  143. data/test/spec_content_length.rb +0 -86
  144. data/test/spec_content_type.rb +0 -45
  145. data/test/spec_deflater.rb +0 -187
  146. data/test/spec_directory.rb +0 -88
  147. data/test/spec_etag.rb +0 -98
  148. data/test/spec_fastcgi.rb +0 -107
  149. data/test/spec_file.rb +0 -200
  150. data/test/spec_handler.rb +0 -59
  151. data/test/spec_head.rb +0 -48
  152. data/test/spec_lint.rb +0 -515
  153. data/test/spec_lobster.rb +0 -58
  154. data/test/spec_lock.rb +0 -167
  155. data/test/spec_logger.rb +0 -23
  156. data/test/spec_methodoverride.rb +0 -72
  157. data/test/spec_mock.rb +0 -269
  158. data/test/spec_mongrel.rb +0 -182
  159. data/test/spec_multipart.rb +0 -479
  160. data/test/spec_nulllogger.rb +0 -23
  161. data/test/spec_recursive.rb +0 -72
  162. data/test/spec_request.rb +0 -955
  163. data/test/spec_response.rb +0 -313
  164. data/test/spec_rewindable_input.rb +0 -118
  165. data/test/spec_runtime.rb +0 -49
  166. data/test/spec_sendfile.rb +0 -90
  167. data/test/spec_server.rb +0 -121
  168. data/test/spec_session_abstract_id.rb +0 -43
  169. data/test/spec_session_cookie.rb +0 -361
  170. data/test/spec_session_memcache.rb +0 -321
  171. data/test/spec_session_pool.rb +0 -209
  172. data/test/spec_showexceptions.rb +0 -92
  173. data/test/spec_showstatus.rb +0 -84
  174. data/test/spec_static.rb +0 -145
  175. data/test/spec_thin.rb +0 -86
  176. data/test/spec_urlmap.rb +0 -213
  177. data/test/spec_utils.rb +0 -554
  178. data/test/spec_webrick.rb +0 -143
  179. data/test/static/another/index.html +0 -1
  180. data/test/static/index.html +0 -1
  181. data/test/testrequest.rb +0 -78
  182. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  183. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/response.rb CHANGED
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/request'
2
4
  require 'rack/utils'
5
+ require 'rack/body_proxy'
6
+ require 'rack/media_type'
3
7
  require 'time'
4
8
 
5
9
  module Rack
@@ -7,7 +11,7 @@ module Rack
7
11
  # response.
8
12
  #
9
13
  # It allows setting of headers and cookies, and provides useful
10
- # defaults (a OK response containing HTML).
14
+ # defaults (an OK response with empty headers and body).
11
15
  #
12
16
  # You can use Response#write to iteratively generate your response,
13
17
  # but note that this is buffered by Rack::Response until you call
@@ -17,140 +21,255 @@ module Rack
17
21
  # Your application's +call+ should end returning Response#finish.
18
22
 
19
23
  class Response
20
- attr_accessor :length
24
+ attr_accessor :length, :status, :body
25
+ attr_reader :header
26
+ alias headers header
21
27
 
22
- def initialize(body=[], status=200, header={})
23
- @status = status.to_i
24
- @header = Utils::HeaderHash.new("Content-Type" => "text/html").
25
- merge(header)
26
-
27
- @chunked = "chunked" == @header['Transfer-Encoding']
28
- @writer = lambda { |x| @body << x }
29
- @block = nil
30
- @length = 0
31
-
32
- @body = []
33
-
34
- if body.respond_to? :to_str
35
- write body.to_str
36
- elsif body.respond_to?(:each)
37
- body.each { |part|
38
- write part.to_s
39
- }
40
- else
41
- raise TypeError, "stringable or iterable required"
42
- end
28
+ CHUNKED = 'chunked'
29
+ STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
43
30
 
44
- yield self if block_given?
45
- end
31
+ def initialize(body = nil, status = 200, header = {})
32
+ @status = status.to_i
33
+ @header = Utils::HeaderHash.new(header)
46
34
 
47
- attr_reader :header
48
- attr_accessor :status, :body
35
+ @writer = self.method(:append)
49
36
 
50
- def [](key)
51
- header[key]
52
- end
37
+ @block = nil
38
+ @length = 0
53
39
 
54
- def []=(key, value)
55
- header[key] = value
56
- end
40
+ # Keep track of whether we have expanded the user supplied body.
41
+ if body.nil?
42
+ @body = []
43
+ @buffered = true
44
+ elsif body.respond_to?(:to_str)
45
+ @body = [body]
46
+ @buffered = true
47
+ else
48
+ @body = body
49
+ @buffered = false
50
+ end
57
51
 
58
- def set_cookie(key, value)
59
- Utils.set_cookie_header!(header, key, value)
52
+ yield self if block_given?
60
53
  end
61
54
 
62
- def delete_cookie(key, value={})
63
- Utils.delete_cookie_header!(header, key, value)
55
+ def redirect(target, status = 302)
56
+ self.status = status
57
+ self.location = target
64
58
  end
65
59
 
66
- def redirect(target, status=302)
67
- self.status = status
68
- self["Location"] = target
60
+ def chunked?
61
+ CHUNKED == get_header(TRANSFER_ENCODING)
69
62
  end
70
63
 
71
64
  def finish(&block)
72
- @block = block
73
-
74
- if [204, 205, 304].include?(status.to_i)
75
- header.delete "Content-Type"
76
- header.delete "Content-Length"
65
+ if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
66
+ delete_header CONTENT_TYPE
67
+ delete_header CONTENT_LENGTH
77
68
  close
78
69
  [status.to_i, header, []]
79
70
  else
80
- [status.to_i, header, BodyProxy.new(self){}]
71
+ if block_given?
72
+ @block = block
73
+ [status.to_i, header, self]
74
+ else
75
+ [status.to_i, header, @body]
76
+ end
81
77
  end
82
78
  end
79
+
83
80
  alias to_a finish # For *response
84
- alias to_ary finish # For implicit-splat on Ruby 1.9.2
85
81
 
86
82
  def each(&callback)
87
83
  @body.each(&callback)
88
- @writer = callback
89
- @block.call(self) if @block
84
+ @buffered = true
85
+
86
+ if @block
87
+ @writer = callback
88
+ @block.call(self)
89
+ end
90
90
  end
91
91
 
92
92
  # Append to body and update Content-Length.
93
93
  #
94
94
  # NOTE: Do not mix #write and direct #body access!
95
95
  #
96
- def write(str)
97
- s = str.to_s
98
- @length += Rack::Utils.bytesize(s) unless @chunked
99
- @writer.call s
96
+ def write(chunk)
97
+ buffered_body!
100
98
 
101
- header["Content-Length"] = @length.to_s unless @chunked
102
- str
99
+ @writer.call(chunk.to_s)
103
100
  end
104
101
 
105
102
  def close
106
- body.close if body.respond_to?(:close)
103
+ @body.close if @body.respond_to?(:close)
107
104
  end
108
105
 
109
106
  def empty?
110
107
  @block == nil && @body.empty?
111
108
  end
112
109
 
113
- alias headers header
110
+ def has_header?(key); headers.key? key; end
111
+ def get_header(key); headers[key]; end
112
+ def set_header(key, v); headers[key] = v; end
113
+ def delete_header(key); headers.delete key; end
114
114
 
115
- module Helpers
116
- def invalid?; status < 100 || status >= 600; end
115
+ alias :[] :get_header
116
+ alias :[]= :set_header
117
117
 
118
- def informational?; status >= 100 && status < 200; end
119
- def successful?; status >= 200 && status < 300; end
120
- def redirection?; status >= 300 && status < 400; end
121
- def client_error?; status >= 400 && status < 500; end
122
- def server_error?; status >= 500 && status < 600; end
118
+ module Helpers
119
+ def invalid?; status < 100 || status >= 600; end
123
120
 
124
- def ok?; status == 200; end
125
- def bad_request?; status == 400; end
126
- def forbidden?; status == 403; end
127
- def not_found?; status == 404; end
128
- def method_not_allowed?; status == 405; end
129
- def unprocessable?; status == 422; end
121
+ def informational?; status >= 100 && status < 200; end
122
+ def successful?; status >= 200 && status < 300; end
123
+ def redirection?; status >= 300 && status < 400; end
124
+ def client_error?; status >= 400 && status < 500; end
125
+ def server_error?; status >= 500 && status < 600; end
130
126
 
131
- def redirect?; [301, 302, 303, 307].include? status; end
127
+ def ok?; status == 200; end
128
+ def created?; status == 201; end
129
+ def accepted?; status == 202; end
130
+ def no_content?; status == 204; end
131
+ def moved_permanently?; status == 301; end
132
+ def bad_request?; status == 400; end
133
+ def unauthorized?; status == 401; end
134
+ def forbidden?; status == 403; end
135
+ def not_found?; status == 404; end
136
+ def method_not_allowed?; status == 405; end
137
+ def precondition_failed?; status == 412; end
138
+ def unprocessable?; status == 422; end
132
139
 
133
- # Headers
134
- attr_reader :headers, :original_headers
140
+ def redirect?; [301, 302, 303, 307, 308].include? status; end
135
141
 
136
142
  def include?(header)
137
- !!headers[header]
143
+ has_header? header
144
+ end
145
+
146
+ # Add a header that may have multiple values.
147
+ #
148
+ # Example:
149
+ # response.add_header 'Vary', 'Accept-Encoding'
150
+ # response.add_header 'Vary', 'Cookie'
151
+ #
152
+ # assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
153
+ #
154
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
155
+ def add_header key, v
156
+ if v.nil?
157
+ get_header key
158
+ elsif has_header? key
159
+ set_header key, "#{get_header key},#{v}"
160
+ else
161
+ set_header key, v
162
+ end
138
163
  end
139
164
 
140
165
  def content_type
141
- headers["Content-Type"]
166
+ get_header CONTENT_TYPE
167
+ end
168
+
169
+ def media_type
170
+ MediaType.type(content_type)
171
+ end
172
+
173
+ def media_type_params
174
+ MediaType.params(content_type)
142
175
  end
143
176
 
144
177
  def content_length
145
- cl = headers["Content-Length"]
178
+ cl = get_header CONTENT_LENGTH
146
179
  cl ? cl.to_i : cl
147
180
  end
148
181
 
149
182
  def location
150
- headers["Location"]
183
+ get_header "Location"
184
+ end
185
+
186
+ def location=(location)
187
+ set_header "Location", location
188
+ end
189
+
190
+ def set_cookie(key, value)
191
+ cookie_header = get_header SET_COOKIE
192
+ set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
193
+ end
194
+
195
+ def delete_cookie(key, value = {})
196
+ set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
197
+ end
198
+
199
+ def set_cookie_header
200
+ get_header SET_COOKIE
201
+ end
202
+
203
+ def set_cookie_header= v
204
+ set_header SET_COOKIE, v
205
+ end
206
+
207
+ def cache_control
208
+ get_header CACHE_CONTROL
209
+ end
210
+
211
+ def cache_control= v
212
+ set_header CACHE_CONTROL, v
213
+ end
214
+
215
+ def etag
216
+ get_header ETAG
217
+ end
218
+
219
+ def etag= v
220
+ set_header ETAG, v
221
+ end
222
+
223
+ protected
224
+
225
+ def buffered_body!
226
+ return if @buffered
227
+
228
+ if @body.is_a?(Array)
229
+ # The user supplied body was an array:
230
+ @body = @body.compact
231
+ else
232
+ # Turn the user supplied body into a buffered array:
233
+ body = @body
234
+ @body = Array.new
235
+
236
+ body.each do |part|
237
+ @writer.call(part.to_s)
238
+ end
239
+ end
240
+
241
+ @buffered = true
242
+ end
243
+
244
+ def append(chunk)
245
+ @body << chunk
246
+
247
+ unless chunked?
248
+ @length += chunk.bytesize
249
+ set_header(CONTENT_LENGTH, @length.to_s)
250
+ end
251
+
252
+ return chunk
151
253
  end
152
254
  end
153
255
 
154
256
  include Helpers
257
+
258
+ class Raw
259
+ include Helpers
260
+
261
+ attr_reader :headers
262
+ attr_accessor :status
263
+
264
+ def initialize status, headers
265
+ @status = status
266
+ @headers = headers
267
+ end
268
+
269
+ def has_header?(key); headers.key? key; end
270
+ def get_header(key); headers[key]; end
271
+ def set_header(key, v); headers[key] = v; end
272
+ def delete_header(key); headers.delete key; end
273
+ end
155
274
  end
156
275
  end
@@ -1,4 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
2
4
  require 'tempfile'
3
5
  require 'rack/utils'
4
6
 
@@ -40,7 +42,7 @@ module Rack
40
42
  end
41
43
 
42
44
  # Closes this RewindableInput object without closing the originally
43
- # wrapped IO oject. Cleans up any temporary resources that this RewindableInput
45
+ # wrapped IO object. Cleans up any temporary resources that this RewindableInput
44
46
  # has created.
45
47
  #
46
48
  # This method may be called multiple times. It does nothing on subsequent calls.
@@ -57,15 +59,6 @@ module Rack
57
59
 
58
60
  private
59
61
 
60
- # Ruby's Tempfile class has a bug. Subclass it and fix it.
61
- class Tempfile < ::Tempfile
62
- def _close
63
- @tmpfile.close if @tmpfile
64
- @data[1] = nil if @data
65
- @tmpfile = nil
66
- end
67
- end
68
-
69
62
  def make_rewindable
70
63
  # Buffer all data into a tempfile. Since this tempfile is private to this
71
64
  # RewindableInput object, we chmod it so that nobody else can read or write
@@ -77,18 +70,16 @@ module Rack
77
70
  @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
78
71
  @rewindable_io.binmode
79
72
  if filesystem_has_posix_semantics?
80
- # Use ::File.unlink as 1.9.1 Tempfile has a bug where unlink closes the file!
81
- ::File.unlink @rewindable_io.path
82
73
  raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
83
74
  @unlinked = true
84
75
  end
85
76
 
86
- buffer = ""
77
+ buffer = "".dup
87
78
  while @io.read(1024 * 4, buffer)
88
79
  entire_buffer_written_out = false
89
80
  while !entire_buffer_written_out
90
81
  written = @rewindable_io.write(buffer)
91
- entire_buffer_written_out = written == Rack::Utils.bytesize(buffer)
82
+ entire_buffer_written_out = written == buffer.bytesize
92
83
  if !entire_buffer_written_out
93
84
  buffer.slice!(0 .. written - 1)
94
85
  end
data/lib/rack/runtime.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/utils'
4
+
1
5
  module Rack
2
6
  # Sets an "X-Runtime" response header, indicating the response
3
7
  # time of the request, in seconds
@@ -6,19 +10,22 @@ module Rack
6
10
  # time, or before all the other middlewares to include time for them,
7
11
  # too.
8
12
  class Runtime
13
+ FORMAT_STRING = "%0.6f" # :nodoc:
14
+ HEADER_NAME = "X-Runtime" # :nodoc:
15
+
9
16
  def initialize(app, name = nil)
10
17
  @app = app
11
- @header_name = "X-Runtime"
12
- @header_name << "-#{name}" if name
18
+ @header_name = HEADER_NAME
19
+ @header_name += "-#{name}" if name
13
20
  end
14
21
 
15
22
  def call(env)
16
- start_time = Time.now
23
+ start_time = Utils.clock_time
17
24
  status, headers, body = @app.call(env)
18
- request_time = Time.now - start_time
25
+ request_time = Utils.clock_time - start_time
19
26
 
20
- if !headers.has_key?(@header_name)
21
- headers[@header_name] = "%0.6f" % request_time
27
+ unless headers.has_key?(@header_name)
28
+ headers[@header_name] = FORMAT_STRING % request_time
22
29
  end
23
30
 
24
31
  [status, headers, body]
data/lib/rack/sendfile.rb CHANGED
@@ -1,4 +1,7 @@
1
- require 'rack/file'
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/files'
4
+ require 'rack/body_proxy'
2
5
 
3
6
  module Rack
4
7
 
@@ -13,7 +16,7 @@ module Rack
13
16
  #
14
17
  # In order to take advantage of this middleware, the response body must
15
18
  # respond to +to_path+ and the request must include an X-Sendfile-Type
16
- # header. Rack::File and other components implement +to_path+ so there's
19
+ # header. Rack::Files and other components implement +to_path+ so there's
17
20
  # rarely anything you need to do in your application. The X-Sendfile-Type
18
21
  # header is typically set in your web servers configuration. The following
19
22
  # sections attempt to document
@@ -22,7 +25,7 @@ module Rack
22
25
  #
23
26
  # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
24
27
  # but requires parts of the filesystem to be mapped into a private URL
25
- # hierarachy.
28
+ # hierarchy.
26
29
  #
27
30
  # The following example shows the Nginx configuration required to create
28
31
  # a private "/files/" area, enable X-Accel-Redirect, and pass the special
@@ -52,7 +55,7 @@ module Rack
52
55
  # that it maps to. The middleware performs a simple substitution on the
53
56
  # resulting path.
54
57
  #
55
- # See Also: http://wiki.codemongers.com/NginxXSendfile
58
+ # See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
56
59
  #
57
60
  # === lighttpd
58
61
  #
@@ -89,13 +92,21 @@ module Rack
89
92
  # RequestHeader Set X-Sendfile-Type X-Sendfile
90
93
  # ProxyPassReverse / http://localhost:8001/
91
94
  # XSendFile on
95
+ #
96
+ # === Mapping parameter
97
+ #
98
+ # The third parameter allows for an overriding extension of the
99
+ # X-Accel-Mapping header. Mappings should be provided in tuples of internal to
100
+ # external. The internal values may contain regular expression syntax, they
101
+ # will be matched with case indifference.
92
102
 
93
103
  class Sendfile
94
- F = ::File
95
-
96
- def initialize(app, variation=nil)
104
+ def initialize(app, variation = nil, mappings = [])
97
105
  @app = app
98
106
  @variation = variation
107
+ @mappings = mappings.map do |internal, external|
108
+ [/^#{internal}/i, external]
109
+ end
99
110
  end
100
111
 
101
112
  def call(env)
@@ -103,22 +114,29 @@ module Rack
103
114
  if body.respond_to?(:to_path)
104
115
  case type = variation(env)
105
116
  when 'X-Accel-Redirect'
106
- path = F.expand_path(body.to_path)
117
+ path = ::File.expand_path(body.to_path)
107
118
  if url = map_accel_path(env, path)
108
- headers['Content-Length'] = '0'
109
- headers[type] = url
110
- body = []
119
+ headers[CONTENT_LENGTH] = '0'
120
+ # '?' must be percent-encoded because it is not query string but a part of path
121
+ headers[type] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
122
+ obody = body
123
+ body = Rack::BodyProxy.new([]) do
124
+ obody.close if obody.respond_to?(:close)
125
+ end
111
126
  else
112
- env['rack.errors'].puts "X-Accel-Mapping header missing"
127
+ env[RACK_ERRORS].puts "X-Accel-Mapping header missing"
113
128
  end
114
129
  when 'X-Sendfile', 'X-Lighttpd-Send-File'
115
- path = F.expand_path(body.to_path)
116
- headers['Content-Length'] = '0'
130
+ path = ::File.expand_path(body.to_path)
131
+ headers[CONTENT_LENGTH] = '0'
117
132
  headers[type] = path
118
- body = []
133
+ obody = body
134
+ body = Rack::BodyProxy.new([]) do
135
+ obody.close if obody.respond_to?(:close)
136
+ end
119
137
  when '', nil
120
138
  else
121
- env['rack.errors'].puts "Unknown x-sendfile variation: '#{variation}'.\n"
139
+ env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n"
122
140
  end
123
141
  end
124
142
  [status, headers, body]
@@ -131,10 +149,16 @@ module Rack
131
149
  env['HTTP_X_SENDFILE_TYPE']
132
150
  end
133
151
 
134
- def map_accel_path(env, file)
135
- if mapping = env['HTTP_X_ACCEL_MAPPING']
136
- internal, external = mapping.split('=', 2).map{ |p| p.strip }
137
- file.sub(/^#{internal}/i, external)
152
+ def map_accel_path(env, path)
153
+ if mapping = @mappings.find { |internal, _| internal =~ path }
154
+ path.sub(*mapping)
155
+ elsif mapping = env['HTTP_X_ACCEL_MAPPING']
156
+ mapping.split(',').map(&:strip).each do |m|
157
+ internal, external = m.split('=', 2).map(&:strip)
158
+ new_path = path.sub(/^#{internal}/i, external)
159
+ return new_path unless path == new_path
160
+ end
161
+ path
138
162
  end
139
163
  end
140
164
  end