rack 2.0.9 → 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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +77 -117
  5. data/Rakefile +25 -18
  6. data/SPEC +3 -4
  7. data/bin/rackup +1 -0
  8. data/example/lobster.ru +2 -0
  9. data/example/protectedlobster.rb +3 -1
  10. data/example/protectedlobster.ru +2 -0
  11. data/lib/rack.rb +63 -60
  12. data/lib/rack/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +2 -0
  14. data/lib/rack/auth/basic.rb +4 -1
  15. data/lib/rack/auth/digest/md5.rb +9 -7
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +4 -2
  18. data/lib/rack/auth/digest/request.rb +2 -0
  19. data/lib/rack/body_proxy.rb +3 -6
  20. data/lib/rack/builder.rb +39 -15
  21. data/lib/rack/cascade.rb +6 -5
  22. data/lib/rack/chunked.rb +29 -6
  23. data/lib/rack/common_logger.rb +9 -8
  24. data/lib/rack/conditional_get.rb +3 -1
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +3 -1
  27. data/lib/rack/content_type.rb +3 -1
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +32 -17
  30. data/lib/rack/directory.rb +19 -16
  31. data/lib/rack/etag.rb +3 -1
  32. data/lib/rack/events.rb +5 -3
  33. data/lib/rack/file.rb +4 -173
  34. data/lib/rack/files.rb +178 -0
  35. data/lib/rack/handler.rb +7 -2
  36. data/lib/rack/handler/cgi.rb +3 -1
  37. data/lib/rack/handler/fastcgi.rb +4 -2
  38. data/lib/rack/handler/lsws.rb +3 -1
  39. data/lib/rack/handler/scgi.rb +9 -6
  40. data/lib/rack/handler/thin.rb +3 -1
  41. data/lib/rack/handler/webrick.rb +4 -2
  42. data/lib/rack/head.rb +2 -0
  43. data/lib/rack/lint.rb +14 -11
  44. data/lib/rack/lobster.rb +7 -5
  45. data/lib/rack/lock.rb +2 -0
  46. data/lib/rack/logger.rb +2 -0
  47. data/lib/rack/media_type.rb +10 -5
  48. data/lib/rack/method_override.rb +4 -2
  49. data/lib/rack/mime.rb +9 -1
  50. data/lib/rack/mock.rb +74 -15
  51. data/lib/rack/multipart.rb +5 -3
  52. data/lib/rack/multipart/generator.rb +6 -7
  53. data/lib/rack/multipart/parser.rb +51 -45
  54. data/lib/rack/multipart/uploaded_file.rb +2 -0
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +51 -25
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +10 -4
  59. data/lib/rack/request.rb +79 -26
  60. data/lib/rack/response.rb +71 -31
  61. data/lib/rack/rewindable_input.rb +4 -2
  62. data/lib/rack/runtime.rb +4 -2
  63. data/lib/rack/sendfile.rb +15 -8
  64. data/lib/rack/server.rb +88 -18
  65. data/lib/rack/session/abstract/id.rb +30 -20
  66. data/lib/rack/session/cookie.rb +10 -9
  67. data/lib/rack/session/memcache.rb +4 -93
  68. data/lib/rack/session/pool.rb +4 -2
  69. data/lib/rack/show_exceptions.rb +15 -9
  70. data/lib/rack/show_status.rb +4 -2
  71. data/lib/rack/static.rb +15 -10
  72. data/lib/rack/tempfile_reaper.rb +2 -0
  73. data/lib/rack/urlmap.rb +11 -2
  74. data/lib/rack/utils.rb +59 -72
  75. data/rack.gemspec +17 -7
  76. metadata +33 -175
  77. data/HISTORY.md +0 -505
  78. data/test/builder/an_underscore_app.rb +0 -5
  79. data/test/builder/anything.rb +0 -5
  80. data/test/builder/comment.ru +0 -4
  81. data/test/builder/end.ru +0 -5
  82. data/test/builder/line.ru +0 -1
  83. data/test/builder/options.ru +0 -2
  84. data/test/cgi/assets/folder/test.js +0 -1
  85. data/test/cgi/assets/fonts/font.eot +0 -1
  86. data/test/cgi/assets/images/image.png +0 -1
  87. data/test/cgi/assets/index.html +0 -1
  88. data/test/cgi/assets/javascripts/app.js +0 -1
  89. data/test/cgi/assets/stylesheets/app.css +0 -1
  90. data/test/cgi/lighttpd.conf +0 -26
  91. data/test/cgi/rackup_stub.rb +0 -6
  92. data/test/cgi/sample_rackup.ru +0 -5
  93. data/test/cgi/test +0 -9
  94. data/test/cgi/test+directory/test+file +0 -1
  95. data/test/cgi/test.fcgi +0 -9
  96. data/test/cgi/test.gz +0 -0
  97. data/test/cgi/test.ru +0 -5
  98. data/test/gemloader.rb +0 -10
  99. data/test/helper.rb +0 -34
  100. data/test/multipart/bad_robots +0 -259
  101. data/test/multipart/binary +0 -0
  102. data/test/multipart/content_type_and_no_filename +0 -6
  103. data/test/multipart/empty +0 -10
  104. data/test/multipart/fail_16384_nofile +0 -814
  105. data/test/multipart/file1.txt +0 -1
  106. data/test/multipart/filename_and_modification_param +0 -7
  107. data/test/multipart/filename_and_no_name +0 -6
  108. data/test/multipart/filename_with_encoded_words +0 -7
  109. data/test/multipart/filename_with_escaped_quotes +0 -6
  110. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  111. data/test/multipart/filename_with_null_byte +0 -7
  112. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  113. data/test/multipart/filename_with_single_quote +0 -7
  114. data/test/multipart/filename_with_unescaped_percentages +0 -6
  115. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  116. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  117. data/test/multipart/filename_with_unescaped_quotes +0 -6
  118. data/test/multipart/ie +0 -6
  119. data/test/multipart/invalid_character +0 -6
  120. data/test/multipart/mixed_files +0 -21
  121. data/test/multipart/nested +0 -10
  122. data/test/multipart/none +0 -9
  123. data/test/multipart/quoted +0 -15
  124. data/test/multipart/rack-logo.png +0 -0
  125. data/test/multipart/semicolon +0 -6
  126. data/test/multipart/text +0 -15
  127. data/test/multipart/three_files_three_fields +0 -31
  128. data/test/multipart/unity3d_wwwform +0 -11
  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_basic.rb +0 -89
  133. data/test/spec_auth_digest.rb +0 -260
  134. data/test/spec_body_proxy.rb +0 -85
  135. data/test/spec_builder.rb +0 -233
  136. data/test/spec_cascade.rb +0 -63
  137. data/test/spec_cgi.rb +0 -84
  138. data/test/spec_chunked.rb +0 -103
  139. data/test/spec_common_logger.rb +0 -95
  140. data/test/spec_conditional_get.rb +0 -103
  141. data/test/spec_config.rb +0 -23
  142. data/test/spec_content_length.rb +0 -86
  143. data/test/spec_content_type.rb +0 -46
  144. data/test/spec_deflater.rb +0 -375
  145. data/test/spec_directory.rb +0 -148
  146. data/test/spec_etag.rb +0 -108
  147. data/test/spec_events.rb +0 -133
  148. data/test/spec_fastcgi.rb +0 -85
  149. data/test/spec_file.rb +0 -264
  150. data/test/spec_handler.rb +0 -57
  151. data/test/spec_head.rb +0 -46
  152. data/test/spec_lint.rb +0 -515
  153. data/test/spec_lobster.rb +0 -59
  154. data/test/spec_lock.rb +0 -204
  155. data/test/spec_logger.rb +0 -24
  156. data/test/spec_media_type.rb +0 -42
  157. data/test/spec_method_override.rb +0 -110
  158. data/test/spec_mime.rb +0 -51
  159. data/test/spec_mock.rb +0 -359
  160. data/test/spec_multipart.rb +0 -722
  161. data/test/spec_null_logger.rb +0 -21
  162. data/test/spec_recursive.rb +0 -75
  163. data/test/spec_request.rb +0 -1407
  164. data/test/spec_response.rb +0 -528
  165. data/test/spec_rewindable_input.rb +0 -128
  166. data/test/spec_runtime.rb +0 -50
  167. data/test/spec_sendfile.rb +0 -125
  168. data/test/spec_server.rb +0 -193
  169. data/test/spec_session_abstract_id.rb +0 -31
  170. data/test/spec_session_abstract_session_hash.rb +0 -45
  171. data/test/spec_session_cookie.rb +0 -442
  172. data/test/spec_session_memcache.rb +0 -357
  173. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  174. data/test/spec_session_pool.rb +0 -247
  175. data/test/spec_show_exceptions.rb +0 -93
  176. data/test/spec_show_status.rb +0 -104
  177. data/test/spec_static.rb +0 -184
  178. data/test/spec_tempfile_reaper.rb +0 -64
  179. data/test/spec_thin.rb +0 -96
  180. data/test/spec_urlmap.rb +0 -237
  181. data/test/spec_utils.rb +0 -742
  182. data/test/spec_version.rb +0 -11
  183. data/test/spec_webrick.rb +0 -206
  184. data/test/static/another/index.html +0 -1
  185. data/test/static/foo.html +0 -1
  186. data/test/static/index.html +0 -1
  187. data/test/testrequest.rb +0 -78
  188. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  189. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
 
3
5
  module Rack
@@ -10,14 +12,14 @@ module Rack
10
12
  class ForwardRequest < Exception
11
13
  attr_reader :url, :env
12
14
 
13
- def initialize(url, env={})
15
+ def initialize(url, env = {})
14
16
  @url = URI(url)
15
17
  @env = env
16
18
 
17
- @env[PATH_INFO] = @url.path
18
- @env[QUERY_STRING] = @url.query if @url.query
19
- @env[HTTP_HOST] = @url.host if @url.host
20
- @env["HTTP_PORT"] = @url.port if @url.port
19
+ @env[PATH_INFO] = @url.path
20
+ @env[QUERY_STRING] = @url.query if @url.query
21
+ @env[HTTP_HOST] = @url.host if @url.host
22
+ @env["HTTP_PORT"] = @url.port if @url.port
21
23
  @env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
22
24
 
23
25
  super "forwarding to #{url}"
@@ -1,9 +1,13 @@
1
- # Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
2
- # Rack::Reloader is subject to the terms of an MIT-style license.
3
- # See COPYING or http://www.opensource.org/licenses/mit-license.php.
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2009-2018 Michael Fellinger <m.fellinger@gmail.com>
4
+ # Rack::Reloader is subject to the terms of an MIT-style license.
5
+ # See MIT-LICENSE or https://opensource.org/licenses/MIT.
4
6
 
5
7
  require 'pathname'
6
8
 
9
+ require_relative 'core_ext/regexp'
10
+
7
11
  module Rack
8
12
 
9
13
  # High performant source reloader
@@ -20,6 +24,8 @@ module Rack
20
24
  # It is performing a check/reload cycle at the start of every request, but
21
25
  # also respects a cool down time, during which nothing will be done.
22
26
  class Reloader
27
+ using ::Rack::RegexpExtensions
28
+
23
29
  def initialize(app, cooldown = 10, backend = Stat)
24
30
  @app = app
25
31
  @cooldown = cooldown
@@ -69,7 +75,7 @@ module Rack
69
75
  paths = ['./', *$LOAD_PATH].uniq
70
76
 
71
77
  files.map{|file|
72
- next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
78
+ next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files
73
79
 
74
80
  found, stat = figure_path(file, paths)
75
81
  next unless found && stat && mtime = stat.mtime
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
  require 'rack/media_type'
3
5
 
6
+ require_relative 'core_ext/regexp'
7
+
4
8
  module Rack
5
9
  # Rack::Request provides a convenient interface to a Rack
6
10
  # environment. It is stateless, the environment +env+ passed to the
@@ -11,7 +15,18 @@ module Rack
11
15
  # req.params["data"]
12
16
 
13
17
  class Request
14
- SCHEME_WHITELIST = %w(https http).freeze
18
+ using ::Rack::RegexpExtensions
19
+
20
+ class << self
21
+ attr_accessor :ip_filter
22
+ end
23
+
24
+ self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) }
25
+ ALLOWED_SCHEMES = %w(https http).freeze
26
+ SCHEME_WHITELIST = ALLOWED_SCHEMES
27
+ if Object.respond_to?(:deprecate_constant)
28
+ deprecate_constant :SCHEME_WHITELIST
29
+ end
15
30
 
16
31
  def initialize(env)
17
32
  @params = nil
@@ -100,7 +115,7 @@ module Rack
100
115
 
101
116
  module Helpers
102
117
  # The set of form-data media-types. Requests that do not indicate
103
- # one of the media types presents in this list will not be eligible
118
+ # one of the media types present in this list will not be eligible
104
119
  # for form-data / param parsing.
105
120
  FORM_DATA_MEDIA_TYPES = [
106
121
  'application/x-www-form-urlencoded',
@@ -108,7 +123,7 @@ module Rack
108
123
  ]
109
124
 
110
125
  # The set of media-types. Requests that do not indicate
111
- # one of the media types presents in this list will not be eligible
126
+ # one of the media types present in this list will not be eligible
112
127
  # for param parsing like soap attachments or generic multiparts
113
128
  PARSEABLE_DATA_MEDIA_TYPES = [
114
129
  'multipart/related',
@@ -119,11 +134,11 @@ module Rack
119
134
  # to include the port in a generated URI.
120
135
  DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
121
136
 
122
- HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'.freeze
123
- HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'.freeze
124
- HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'.freeze
125
- HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'.freeze
126
- HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'.freeze
137
+ HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
138
+ HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
139
+ HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
140
+ HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
141
+ HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
127
142
 
128
143
  def body; get_header(RACK_INPUT) end
129
144
  def script_name; get_header(SCRIPT_NAME).to_s end
@@ -159,10 +174,10 @@ module Rack
159
174
  def delete?; request_method == DELETE end
160
175
 
161
176
  # Checks the HTTP request method (or verb) to see if it was of type GET
162
- def get?; request_method == GET end
177
+ def get?; request_method == GET end
163
178
 
164
179
  # Checks the HTTP request method (or verb) to see if it was of type HEAD
165
- def head?; request_method == HEAD end
180
+ def head?; request_method == HEAD end
166
181
 
167
182
  # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
168
183
  def options?; request_method == OPTIONS end
@@ -208,7 +223,7 @@ module Rack
208
223
  string = get_header HTTP_COOKIE
209
224
 
210
225
  return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
211
- hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE
226
+ hash.replace Utils.parse_cookies_header string
212
227
  set_header(RACK_REQUEST_COOKIE_STRING, string)
213
228
  hash
214
229
  end
@@ -232,18 +247,23 @@ module Rack
232
247
 
233
248
  def host
234
249
  # Remove port number.
235
- host_with_port.to_s.sub(/:\d+\z/, '')
250
+ h = host_with_port
251
+ if colon_index = h.index(":")
252
+ h[0, colon_index]
253
+ else
254
+ h
255
+ end
236
256
  end
237
257
 
238
258
  def port
239
- if port = host_with_port.split(/:/)[1]
259
+ if port = extract_port(host_with_port)
240
260
  port.to_i
241
261
  elsif port = get_header(HTTP_X_FORWARDED_PORT)
242
262
  port.to_i
243
263
  elsif has_header?(HTTP_X_FORWARDED_HOST)
244
264
  DEFAULT_PORTS[scheme]
245
265
  elsif has_header?(HTTP_X_FORWARDED_PROTO)
246
- DEFAULT_PORTS[get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]]
266
+ DEFAULT_PORTS[extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO))]
247
267
  else
248
268
  get_header(SERVER_PORT).to_i
249
269
  end
@@ -260,6 +280,7 @@ module Rack
260
280
  return remote_addrs.first if remote_addrs.any?
261
281
 
262
282
  forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
283
+ .map { |ip| strip_port(ip) }
263
284
 
264
285
  return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
265
286
  end
@@ -337,7 +358,7 @@ module Rack
337
358
 
338
359
  # Fix for Safari Ajax postings that always append \0
339
360
  # form_vars.sub!(/\0\z/, '') # performance replacement:
340
- form_vars.slice!(-1) if form_vars[-1] == ?\0
361
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
341
362
 
342
363
  set_header RACK_REQUEST_FORM_VARS, form_vars
343
364
  set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
@@ -386,12 +407,13 @@ module Rack
386
407
  #
387
408
  # <tt>env['rack.input']</tt> is not touched.
388
409
  def delete_param(k)
389
- [ self.POST.delete(k), self.GET.delete(k) ].compact.first
410
+ post_value, get_value = self.POST.delete(k), self.GET.delete(k)
411
+ post_value || get_value
390
412
  end
391
413
 
392
414
  def base_url
393
415
  url = "#{scheme}://#{host}"
394
- url << ":#{port}" if port != DEFAULT_PORTS[scheme]
416
+ url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
395
417
  url
396
418
  end
397
419
 
@@ -417,7 +439,7 @@ module Rack
417
439
  end
418
440
 
419
441
  def trusted_proxy?(ip)
420
- ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
442
+ Rack::Request.ip_filter.call(ip)
421
443
  end
422
444
 
423
445
  # shortcut for <tt>request.params[key]</tt>
@@ -464,7 +486,7 @@ module Rack
464
486
  Utils.default_query_parser
465
487
  end
466
488
 
467
- def parse_query(qs, d='&')
489
+ def parse_query(qs, d = '&')
468
490
  query_parser.parse_nested_query(qs, d)
469
491
  end
470
492
 
@@ -476,21 +498,52 @@ module Rack
476
498
  ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
477
499
  end
478
500
 
501
+ def strip_port(ip_address)
502
+ # IPv6 format with optional port: "[2001:db8:cafe::17]:47011"
503
+ # returns: "2001:db8:cafe::17"
504
+ sep_start = ip_address.index('[')
505
+ sep_end = ip_address.index(']')
506
+ if (sep_start && sep_end)
507
+ return ip_address[sep_start + 1, sep_end - 1]
508
+ end
509
+
510
+ # IPv4 format with optional port: "192.0.2.43:47011"
511
+ # returns: "192.0.2.43"
512
+ sep = ip_address.index(':')
513
+ if (sep && ip_address.count(':') == 1)
514
+ return ip_address[0, sep]
515
+ end
516
+
517
+ ip_address
518
+ end
519
+
479
520
  def reject_trusted_ip_addresses(ip_addresses)
480
521
  ip_addresses.reject { |ip| trusted_proxy?(ip) }
481
522
  end
482
523
 
483
524
  def forwarded_scheme
484
- scheme_headers = [
485
- get_header(HTTP_X_FORWARDED_SCHEME),
486
- get_header(HTTP_X_FORWARDED_PROTO).to_s.split(',')[0]
487
- ]
525
+ allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
526
+ allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
527
+ end
488
528
 
489
- scheme_headers.each do |header|
490
- return header if SCHEME_WHITELIST.include?(header)
529
+ def allowed_scheme(header)
530
+ header if ALLOWED_SCHEMES.include?(header)
531
+ end
532
+
533
+ def extract_proto_header(header)
534
+ if header
535
+ if (comma_index = header.index(','))
536
+ header[0, comma_index]
537
+ else
538
+ header
539
+ end
491
540
  end
541
+ end
492
542
 
493
- nil
543
+ def extract_port(uri)
544
+ if (colon_index = uri.index(':'))
545
+ uri[colon_index + 1, uri.length]
546
+ end
494
547
  end
495
548
  end
496
549
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/request'
2
4
  require 'rack/utils'
3
5
  require 'rack/body_proxy'
@@ -23,32 +25,34 @@ module Rack
23
25
  attr_reader :header
24
26
  alias headers header
25
27
 
26
- CHUNKED = 'chunked'.freeze
28
+ CHUNKED = 'chunked'
29
+ STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
27
30
 
28
- def initialize(body=[], status=200, header={})
31
+ def initialize(body = nil, status = 200, header = {})
29
32
  @status = status.to_i
30
- @header = Utils::HeaderHash.new.merge(header)
33
+ @header = Utils::HeaderHash.new(header)
31
34
 
32
- @writer = lambda { |x| @body << x }
33
- @block = nil
34
- @length = 0
35
+ @writer = self.method(:append)
35
36
 
36
- @body = []
37
+ @block = nil
38
+ @length = 0
37
39
 
38
- if body.respond_to? :to_str
39
- write body.to_str
40
- elsif body.respond_to?(:each)
41
- body.each { |part|
42
- write part.to_s
43
- }
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
44
47
  else
45
- raise TypeError, "stringable or iterable required"
48
+ @body = body
49
+ @buffered = false
46
50
  end
47
51
 
48
- yield self if block_given?
52
+ yield self if block_given?
49
53
  end
50
54
 
51
- def redirect(target, status=302)
55
+ def redirect(target, status = 302)
52
56
  self.status = status
53
57
  self.location = target
54
58
  end
@@ -58,41 +62,45 @@ module Rack
58
62
  end
59
63
 
60
64
  def finish(&block)
61
- @block = block
62
-
63
- if [204, 304].include?(status.to_i)
65
+ if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
64
66
  delete_header CONTENT_TYPE
65
67
  delete_header CONTENT_LENGTH
66
68
  close
67
69
  [status.to_i, header, []]
68
70
  else
69
- [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
70
77
  end
71
78
  end
79
+
72
80
  alias to_a finish # For *response
73
- alias to_ary finish # For implicit-splat on Ruby 1.9.2
74
81
 
75
82
  def each(&callback)
76
83
  @body.each(&callback)
77
- @writer = callback
78
- @block.call(self) if @block
84
+ @buffered = true
85
+
86
+ if @block
87
+ @writer = callback
88
+ @block.call(self)
89
+ end
79
90
  end
80
91
 
81
92
  # Append to body and update Content-Length.
82
93
  #
83
94
  # NOTE: Do not mix #write and direct #body access!
84
95
  #
85
- def write(str)
86
- s = str.to_s
87
- @length += s.bytesize unless chunked?
88
- @writer.call s
96
+ def write(chunk)
97
+ buffered_body!
89
98
 
90
- set_header(CONTENT_LENGTH, @length.to_s) unless chunked?
91
- str
99
+ @writer.call(chunk.to_s)
92
100
  end
93
101
 
94
102
  def close
95
- body.close if body.respond_to?(:close)
103
+ @body.close if @body.respond_to?(:close)
96
104
  end
97
105
 
98
106
  def empty?
@@ -184,7 +192,7 @@ module Rack
184
192
  set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
185
193
  end
186
194
 
187
- def delete_cookie(key, value={})
195
+ def delete_cookie(key, value = {})
188
196
  set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
189
197
  end
190
198
 
@@ -211,6 +219,38 @@ module Rack
211
219
  def etag= v
212
220
  set_header ETAG, v
213
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
253
+ end
214
254
  end
215
255
 
216
256
  include Helpers
@@ -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.
@@ -72,7 +74,7 @@ module Rack
72
74
  @unlinked = true
73
75
  end
74
76
 
75
- buffer = ""
77
+ buffer = "".dup
76
78
  while @io.read(1024 * 4, buffer)
77
79
  entire_buffer_written_out = false
78
80
  while !entire_buffer_written_out