rack 3.0.0 → 3.2.7

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +592 -1
  3. data/CONTRIBUTING.md +11 -9
  4. data/README.md +120 -29
  5. data/SPEC.rdoc +206 -288
  6. data/lib/rack/auth/abstract/request.rb +2 -0
  7. data/lib/rack/auth/basic.rb +1 -4
  8. data/lib/rack/bad_request.rb +8 -0
  9. data/lib/rack/body_proxy.rb +18 -2
  10. data/lib/rack/builder.rb +61 -40
  11. data/lib/rack/cascade.rb +0 -3
  12. data/lib/rack/common_logger.rb +3 -2
  13. data/lib/rack/conditional_get.rb +4 -3
  14. data/lib/rack/constants.rb +6 -1
  15. data/lib/rack/directory.rb +6 -3
  16. data/lib/rack/etag.rb +3 -0
  17. data/lib/rack/events.rb +21 -6
  18. data/lib/rack/files.rb +1 -1
  19. data/lib/rack/head.rb +2 -3
  20. data/lib/rack/headers.rb +98 -14
  21. data/lib/rack/lint.rb +491 -428
  22. data/lib/rack/media_type.rb +18 -9
  23. data/lib/rack/method_override.rb +2 -2
  24. data/lib/rack/mime.rb +6 -5
  25. data/lib/rack/mock_request.rb +10 -15
  26. data/lib/rack/mock_response.rb +50 -20
  27. data/lib/rack/multipart/parser.rb +279 -80
  28. data/lib/rack/multipart/uploaded_file.rb +42 -5
  29. data/lib/rack/multipart.rb +34 -1
  30. data/lib/rack/query_parser.rb +88 -80
  31. data/lib/rack/request.rb +98 -65
  32. data/lib/rack/response.rb +35 -20
  33. data/lib/rack/rewindable_input.rb +4 -1
  34. data/lib/rack/sendfile.rb +53 -23
  35. data/lib/rack/show_exceptions.rb +10 -4
  36. data/lib/rack/show_status.rb +0 -2
  37. data/lib/rack/static.rb +9 -4
  38. data/lib/rack/urlmap.rb +1 -1
  39. data/lib/rack/utils.rb +205 -133
  40. data/lib/rack/version.rb +3 -20
  41. data/lib/rack.rb +11 -17
  42. metadata +6 -15
  43. data/lib/rack/auth/digest/md5.rb +0 -1
  44. data/lib/rack/auth/digest/nonce.rb +0 -1
  45. data/lib/rack/auth/digest/params.rb +0 -1
  46. data/lib/rack/auth/digest/request.rb +0 -1
  47. data/lib/rack/auth/digest.rb +0 -256
  48. data/lib/rack/chunked.rb +0 -120
  49. data/lib/rack/file.rb +0 -9
  50. data/lib/rack/logger.rb +0 -22
@@ -1,40 +1,69 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'bad_request'
4
+ require 'uri'
5
+
3
6
  module Rack
4
7
  class QueryParser
5
- DEFAULT_SEP = /[&] */n
6
- COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
8
+ DEFAULT_SEP = /& */n
9
+ COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n }
7
10
 
8
11
  # ParameterTypeError is the error that is raised when incoming structural
9
12
  # parameters (parsed by parse_nested_query) contain conflicting types.
10
- class ParameterTypeError < TypeError; end
13
+ class ParameterTypeError < TypeError
14
+ include BadRequest
15
+ end
11
16
 
12
17
  # InvalidParameterError is the error that is raised when incoming structural
13
18
  # parameters (parsed by parse_nested_query) contain invalid format or byte
14
19
  # sequence.
15
- class InvalidParameterError < ArgumentError; end
20
+ class InvalidParameterError < ArgumentError
21
+ include BadRequest
22
+ end
16
23
 
17
- # ParamsTooDeepError is the error that is raised when params are recursively
18
- # nested over the specified limit.
19
- class ParamsTooDeepError < RangeError; end
24
+ # QueryLimitError is for errors raised when the query provided exceeds one
25
+ # of the query parser limits.
26
+ class QueryLimitError < RangeError
27
+ include BadRequest
28
+ end
20
29
 
21
- def self.make_default(_key_space_limit=(not_deprecated = true; nil), param_depth_limit)
22
- unless not_deprecated
23
- warn("`first argument `key_space limit` is deprecated and no longer has an effect. Please call with only one argument, which will be required in a future version of Rack", uplevel: 1)
24
- end
30
+ # ParamsTooDeepError is the old name for the error that is raised when params
31
+ # are recursively nested over the specified limit. Make it the same as
32
+ # as QueryLimitError, so that code that rescues ParamsTooDeepError error
33
+ # to handle bad query strings also now handles other limits.
34
+ ParamsTooDeepError = QueryLimitError
25
35
 
26
- new Params, param_depth_limit
36
+ def self.make_default(param_depth_limit, **options)
37
+ new(Params, param_depth_limit, **options)
27
38
  end
28
39
 
29
40
  attr_reader :param_depth_limit
30
41
 
31
- def initialize(params_class, _key_space_limit=(not_deprecated = true; nil), param_depth_limit)
32
- unless not_deprecated
33
- warn("`second argument `key_space limit` is deprecated and no longer has an effect. Please call with only two arguments, which will be required in a future version of Rack", uplevel: 1)
42
+ env_int = lambda do |key, val|
43
+ if str_val = ENV[key]
44
+ begin
45
+ val = Integer(str_val, 10)
46
+ rescue ArgumentError
47
+ raise ArgumentError, "non-integer value provided for environment variable #{key}"
48
+ end
34
49
  end
35
50
 
51
+ val
52
+ end
53
+
54
+ BYTESIZE_LIMIT = env_int.call("RACK_QUERY_PARSER_BYTESIZE_LIMIT", 4194304)
55
+ private_constant :BYTESIZE_LIMIT
56
+
57
+ PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
58
+ private_constant :PARAMS_LIMIT
59
+
60
+ attr_reader :bytesize_limit
61
+
62
+ def initialize(params_class, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
36
63
  @params_class = params_class
37
64
  @param_depth_limit = param_depth_limit
65
+ @bytesize_limit = bytesize_limit
66
+ @params_limit = params_limit
38
67
  end
39
68
 
40
69
  # Stolen from Mongrel, with some small modifications:
@@ -42,14 +71,9 @@ module Rack
42
71
  # to parse cookies by changing the characters used in the second parameter
43
72
  # (which defaults to '&').
44
73
  def parse_query(qs, separator = nil, &unescaper)
45
- unescaper ||= method(:unescape)
46
-
47
74
  params = make_params
48
75
 
49
- (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
50
- next if p.empty?
51
- k, v = p.split('=', 2).map!(&unescaper)
52
-
76
+ each_query_pair(qs, separator, unescaper) do |k, v|
53
77
  if cur = params[k]
54
78
  if cur.class == Array
55
79
  params[k] << v
@@ -64,6 +88,19 @@ module Rack
64
88
  return params.to_h
65
89
  end
66
90
 
91
+ # Parses a query string by breaking it up at the '&', returning all key-value
92
+ # pairs as an array of [key, value] arrays. Unlike parse_query, this preserves
93
+ # all duplicate keys rather than collapsing them.
94
+ def parse_query_pairs(qs, separator = nil)
95
+ pairs = []
96
+
97
+ each_query_pair(qs, separator) do |k, v|
98
+ pairs << [k, v]
99
+ end
100
+
101
+ pairs
102
+ end
103
+
67
104
  # parse_nested_query expands a query string into structural types. Supported
68
105
  # types are Arrays, Hashes and basic value types. It is possible to supply
69
106
  # query strings with parameters of conflicting types, in this case a
@@ -72,17 +109,11 @@ module Rack
72
109
  def parse_nested_query(qs, separator = nil)
73
110
  params = make_params
74
111
 
75
- unless qs.nil? || qs.empty?
76
- (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
77
- k, v = p.split('=', 2).map! { |s| unescape(s) }
78
-
79
- _normalize_params(params, k, v, 0)
80
- end
112
+ each_query_pair(qs, separator) do |k, v|
113
+ _normalize_params(params, k, v, 0)
81
114
  end
82
115
 
83
116
  return params.to_h
84
- rescue ArgumentError => e
85
- raise InvalidParameterError, e.message, e.backtrace
86
117
  end
87
118
 
88
119
  # normalize_params recursively expands parameters into structural types. If
@@ -128,8 +159,6 @@ module Rack
128
159
 
129
160
  return if k.empty?
130
161
 
131
- v ||= String.new
132
-
133
162
  if after == ''
134
163
  if k == '[]' && depth != 0
135
164
  return [v]
@@ -190,63 +219,42 @@ module Rack
190
219
  true
191
220
  end
192
221
 
193
- def unescape(s)
194
- Utils.unescape(s)
195
- end
196
-
197
- class Params
198
- def initialize
199
- @size = 0
200
- @params = {}
201
- end
222
+ def each_query_pair(qs, separator, unescaper = nil)
223
+ return if !qs || qs.empty?
202
224
 
203
- def [](key)
204
- @params[key]
225
+ if qs.bytesize > @bytesize_limit
226
+ raise QueryLimitError, "total query size exceeds limit (#{@bytesize_limit})"
205
227
  end
206
228
 
207
- def []=(key, value)
208
- @params[key] = value
209
- end
229
+ pairs = qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP, @params_limit + 1)
210
230
 
211
- def key?(key)
212
- @params.key?(key)
231
+ if pairs.size > @params_limit
232
+ param_count = pairs.size + pairs.last.count(separator || "&")
233
+ raise QueryLimitError, "total number of query parameters (#{param_count}) exceeds limit (#{@params_limit})"
213
234
  end
214
235
 
215
- # Recursively unwraps nested `Params` objects and constructs an object
216
- # of the same shape, but using the objects' internal representations
217
- # (Ruby hashes) in place of the objects. The result is a hash consisting
218
- # purely of Ruby primitives.
219
- #
220
- # Mutation warning!
221
- #
222
- # 1. This method mutates the internal representation of the `Params`
223
- # objects in order to save object allocations.
224
- #
225
- # 2. The value you get back is a reference to the internal hash
226
- # representation, not a copy.
227
- #
228
- # 3. Because the `Params` object's internal representation is mutable
229
- # through the `#[]=` method, it is not thread safe. The result of
230
- # getting the hash representation while another thread is adding a
231
- # key to it is non-deterministic.
232
- #
233
- def to_h
234
- @params.each do |key, value|
235
- case value
236
- when self
237
- # Handle circular references gracefully.
238
- @params[key] = @params
239
- when Params
240
- @params[key] = value.to_h
241
- when Array
242
- value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
243
- else
244
- # Ignore anything that is not a `Params` object or
245
- # a collection that can contain one.
246
- end
236
+ if unescaper
237
+ pairs.each do |p|
238
+ next if p.empty?
239
+ k, v = p.split('=', 2).map!(&unescaper)
240
+ yield k, v
241
+ end
242
+ else
243
+ pairs.each do |p|
244
+ next if p.empty?
245
+ k, v = p.split('=', 2).map! { |s| unescape(s) }
246
+ yield k, v
247
247
  end
248
- @params
249
248
  end
249
+ rescue ArgumentError => e
250
+ raise InvalidParameterError, e.message, e.backtrace
251
+ end
252
+
253
+ def unescape(string, encoding = Encoding::UTF_8)
254
+ URI.decode_www_form_component(string, encoding)
255
+ end
256
+
257
+ class Params < Hash
250
258
  alias_method :to_params_hash, :to_h
251
259
  end
252
260
  end
data/lib/rack/request.rb CHANGED
@@ -44,7 +44,7 @@ module Rack
44
44
  @x_forwarded_proto_priority = [:proto, :scheme]
45
45
 
46
46
  valid_ipv4_octet = /\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/
47
-
47
+
48
48
  trusted_proxies = Regexp.union(
49
49
  /\A127#{valid_ipv4_octet}{3}\z/, # localhost IPv4 range 127.x.x.x, per RFC-3330
50
50
  /\A::1\z/, # localhost IPv6 ::1
@@ -54,16 +54,21 @@ module Rack
54
54
  /\A192\.168#{valid_ipv4_octet}{2}\z/, # private IPv4 range 192.168.x.x
55
55
  /\Alocalhost\z|\Aunix(\z|:)/i, # localhost hostname, and unix domain sockets
56
56
  )
57
-
57
+
58
58
  self.ip_filter = lambda { |ip| trusted_proxies.match?(ip) }
59
59
 
60
60
  ALLOWED_SCHEMES = %w(https http wss ws).freeze
61
61
 
62
62
  def initialize(env)
63
63
  @env = env
64
+ @ip = nil
64
65
  @params = nil
65
66
  end
66
67
 
68
+ def ip
69
+ @ip ||= super
70
+ end
71
+
67
72
  def params
68
73
  @params ||= super
69
74
  end
@@ -398,8 +403,8 @@ module Rack
398
403
  return forwarded.last
399
404
  end
400
405
  when :x_forwarded
401
- if value = get_header(HTTP_X_FORWARDED_HOST)
402
- return wrap_ipv6(split_header(value).last)
406
+ if (value = get_header(HTTP_X_FORWARDED_HOST)) && (x_forwarded_host = split_header(value).last)
407
+ return wrap_ipv6(x_forwarded_host)
403
408
  end
404
409
  end
405
410
  end
@@ -413,10 +418,9 @@ module Rack
413
418
 
414
419
  def ip
415
420
  remote_addresses = split_header(get_header('REMOTE_ADDR'))
416
- external_addresses = reject_trusted_ip_addresses(remote_addresses)
417
421
 
418
- unless external_addresses.empty?
419
- return external_addresses.last
422
+ remote_addresses.reverse_each do |ip|
423
+ return ip unless trusted_proxy?(ip)
420
424
  end
421
425
 
422
426
  if (forwarded_for = self.forwarded_for) && !forwarded_for.empty?
@@ -424,7 +428,10 @@ module Rack
424
428
  # So we reject all the trusted addresses (proxy*) and return the
425
429
  # last client. Or if we trust everyone, we just return the first
426
430
  # address.
427
- return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first
431
+ forwarded_for.reverse_each do |ip|
432
+ return ip unless trusted_proxy?(ip)
433
+ end
434
+ return forwarded_for.first
428
435
  end
429
436
 
430
437
  # If all the addresses are trusted, and we aren't forwarded, just return
@@ -482,12 +489,49 @@ module Rack
482
489
 
483
490
  # Returns the data received in the query string.
484
491
  def GET
485
- if get_header(RACK_REQUEST_QUERY_STRING) == query_string
486
- get_header(RACK_REQUEST_QUERY_HASH)
487
- else
488
- query_hash = parse_query(query_string, '&')
489
- set_header(RACK_REQUEST_QUERY_STRING, query_string)
490
- set_header(RACK_REQUEST_QUERY_HASH, query_hash)
492
+ get_header(RACK_REQUEST_QUERY_HASH) || set_header(RACK_REQUEST_QUERY_HASH, parse_query(query_string, '&'))
493
+ end
494
+
495
+ # Returns the form data pairs received in the request body.
496
+ #
497
+ # This method support both application/x-www-form-urlencoded and
498
+ # multipart/form-data.
499
+ def form_pairs
500
+ if pairs = get_header(RACK_REQUEST_FORM_PAIRS)
501
+ return pairs
502
+ elsif error = get_header(RACK_REQUEST_FORM_ERROR)
503
+ raise error.class, error.message, cause: error.cause
504
+ end
505
+
506
+ begin
507
+ rack_input = get_header(RACK_INPUT)
508
+
509
+ # Otherwise, figure out how to parse the input:
510
+ if rack_input.nil?
511
+ set_header(RACK_REQUEST_FORM_PAIRS, [])
512
+ elsif form_data? || parseable_data?
513
+ if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
514
+ set_header RACK_REQUEST_FORM_PAIRS, pairs
515
+ else
516
+ # Add 2 bytes. One to check whether it is over the limit, and a second
517
+ # in case the slice! call below removes the last byte
518
+ # If read returns nil, use the empty string
519
+ form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
520
+
521
+ # Fix for Safari Ajax postings that always append \0
522
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
523
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
524
+
525
+ set_header RACK_REQUEST_FORM_VARS, form_vars
526
+ pairs = query_parser.parse_query_pairs(form_vars, '&')
527
+ set_header(RACK_REQUEST_FORM_PAIRS, pairs)
528
+ end
529
+ else
530
+ set_header(RACK_REQUEST_FORM_PAIRS, [])
531
+ end
532
+ rescue => error
533
+ set_header(RACK_REQUEST_FORM_ERROR, error)
534
+ raise
491
535
  end
492
536
  end
493
537
 
@@ -496,27 +540,14 @@ module Rack
496
540
  # This method support both application/x-www-form-urlencoded and
497
541
  # multipart/form-data.
498
542
  def POST
499
- if get_header(RACK_INPUT).nil?
500
- raise "Missing rack.input"
501
- elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
502
- get_header(RACK_REQUEST_FORM_HASH)
503
- elsif form_data? || parseable_data?
504
- unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
505
- form_vars = get_header(RACK_INPUT).read
506
-
507
- # Fix for Safari Ajax postings that always append \0
508
- # form_vars.sub!(/\0\z/, '') # performance replacement:
509
- form_vars.slice!(-1) if form_vars.end_with?("\0")
510
-
511
- set_header RACK_REQUEST_FORM_VARS, form_vars
512
- set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
513
- end
514
- set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
515
- get_header RACK_REQUEST_FORM_HASH
516
- else
517
- set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
518
- set_header(RACK_REQUEST_FORM_HASH, {})
543
+ if form_hash = get_header(RACK_REQUEST_FORM_HASH)
544
+ return form_hash
545
+ elsif error = get_header(RACK_REQUEST_FORM_ERROR)
546
+ raise error.class, error.message, cause: error.cause
519
547
  end
548
+
549
+ pairs = form_pairs
550
+ set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
520
551
  end
521
552
 
522
553
  # The union of GET and POST data.
@@ -526,6 +557,10 @@ module Rack
526
557
  self.GET.merge(self.POST)
527
558
  end
528
559
 
560
+ # Allow overriding the query parser that the receiver will use.
561
+ # By default Rack::Utils.default_query_parser is used.
562
+ attr_writer :query_parser
563
+
529
564
  # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
530
565
  #
531
566
  # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
@@ -585,27 +620,6 @@ module Rack
585
620
  Rack::Request.ip_filter.call(ip)
586
621
  end
587
622
 
588
- # shortcut for <tt>request.params[key]</tt>
589
- def [](key)
590
- warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
591
-
592
- params[key.to_s]
593
- end
594
-
595
- # shortcut for <tt>request.params[key] = value</tt>
596
- #
597
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
598
- def []=(key, value)
599
- warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
600
-
601
- params[key.to_s] = value
602
- end
603
-
604
- # like Hash#values_at
605
- def values_at(*keys)
606
- keys.map { |key| params[key] }
607
- end
608
-
609
623
  private
610
624
 
611
625
  def default_session; {}; end
@@ -625,14 +639,26 @@ module Rack
625
639
  end
626
640
 
627
641
  def parse_http_accept_header(header)
628
- header.to_s.split(/\s*,\s*/).map do |part|
629
- attribute, parameters = part.split(/\s*;\s*/, 2)
642
+ # It would be nice to use filter_map here, but it's Ruby 2.7+
643
+ parts = header.to_s.split(',')
644
+
645
+ parts.map! do |part|
646
+ part.strip!
647
+ next if part.empty?
648
+
649
+ attribute, parameters = part.split(';', 2)
650
+ attribute.strip!
651
+ parameters&.strip!
630
652
  quality = 1.0
631
653
  if parameters and /\Aq=([\d.]+)/ =~ parameters
632
654
  quality = $1.to_f
633
655
  end
634
656
  [attribute, quality]
635
657
  end
658
+
659
+ parts.compact!
660
+
661
+ parts
636
662
  end
637
663
 
638
664
  # Get an array of values set in the RFC 7239 `Forwarded` request header.
@@ -641,7 +667,7 @@ module Rack
641
667
  end
642
668
 
643
669
  def query_parser
644
- Utils.default_query_parser
670
+ @query_parser || Utils.default_query_parser
645
671
  end
646
672
 
647
673
  def parse_query(qs, d = '&')
@@ -649,11 +675,22 @@ module Rack
649
675
  end
650
676
 
651
677
  def parse_multipart
678
+ warn "Rack::Request#parse_multipart is deprecated and will be removed in a future version of Rack.", uplevel: 1
652
679
  Rack::Multipart.extract_multipart(self, query_parser)
653
680
  end
654
681
 
682
+ def expand_param_pairs(pairs, query_parser = query_parser())
683
+ params = query_parser.make_params
684
+
685
+ pairs.each do |k, v|
686
+ query_parser.normalize_params(params, k, v)
687
+ end
688
+
689
+ params.to_params_hash
690
+ end
691
+
655
692
  def split_header(value)
656
- value ? value.strip.split(/[,\s]+/) : []
693
+ value ? value.strip.split(/[, \t]+/) : []
657
694
  end
658
695
 
659
696
  # ipv6 extracted from resolv stdlib, simplified
@@ -686,8 +723,8 @@ module Rack
686
723
  # Match IPv6 as a string of hex digits and colons in square brackets
687
724
  \[(?<address>#{ipv6})\]
688
725
  |
689
- # Match any other printable string (except square brackets) as a hostname
690
- (?<address>[[[:graph:]&&[^\[\]]]]*?)
726
+ # Match characters allowed by RFC 3986 Section 3.2.2
727
+ (?<address>[-a-zA-Z0-9._~%!$&'()*+,;=]*?)
691
728
  )
692
729
  (:(?<port>\d+))?
693
730
  \z
@@ -701,10 +738,6 @@ module Rack
701
738
  return match[:host], match[:address], match[:port]&.to_i
702
739
  end
703
740
 
704
- def reject_trusted_ip_addresses(ip_addresses)
705
- ip_addresses.reject { |ip| trusted_proxy?(ip) }
706
- end
707
-
708
741
  FORWARDED_SCHEME_HEADERS = {
709
742
  proto: HTTP_X_FORWARDED_PROTO,
710
743
  scheme: HTTP_X_FORWARDED_SCHEME
data/lib/rack/response.rb CHANGED
@@ -31,19 +31,12 @@ module Rack
31
31
  attr_accessor :length, :status, :body
32
32
  attr_reader :headers
33
33
 
34
- # Deprecated, use headers instead.
35
- def header
36
- warn 'Rack::Response#header is deprecated and will be removed in Rack 3.1', uplevel: 1
37
-
38
- headers
39
- end
40
-
41
34
  # Initialize the response object with the specified +body+, +status+
42
35
  # and +headers+.
43
36
  #
44
37
  # If the +body+ is +nil+, construct an empty response object with internal
45
38
  # buffering.
46
- #
39
+ #
47
40
  # If the +body+ responds to +to_str+, assume it's a string-like object and
48
41
  # construct a buffered response object containing using that string as the
49
42
  # initial contents of the buffer.
@@ -62,7 +55,7 @@ module Rack
62
55
  @status = status.to_i
63
56
 
64
57
  unless headers.is_a?(Hash)
65
- warn "Providing non-hash headers to Rack::Response is deprecated and will be removed in Rack 3.1", uplevel: 1
58
+ raise ArgumentError, "Headers must be a Hash!"
66
59
  end
67
60
 
68
61
  @headers = Headers.new
@@ -79,7 +72,8 @@ module Rack
79
72
  if body.nil?
80
73
  @body = []
81
74
  @buffered = true
82
- @length = 0
75
+ # Body is unspecified - it may be a buffered response, or it may be a HEAD response.
76
+ @length = nil
83
77
  elsif body.respond_to?(:to_str)
84
78
  @body = [body]
85
79
  @buffered = true
@@ -87,7 +81,7 @@ module Rack
87
81
  else
88
82
  @body = body
89
83
  @buffered = nil # undetermined as of yet.
90
- @length = 0
84
+ @length = nil
91
85
  end
92
86
 
93
87
  yield self if block_given?
@@ -102,20 +96,30 @@ module Rack
102
96
  CHUNKED == get_header(TRANSFER_ENCODING)
103
97
  end
104
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
+
105
104
  # Generate a response array consistent with the requirements of the SPEC.
106
105
  # @return [Array] a 3-tuple suitable of `[status, headers, body]`
107
106
  # which is suitable to be returned from the middleware `#call(env)` method.
108
107
  def finish(&block)
109
- if STATUS_WITH_NO_ENTITY_BODY[@status]
108
+ if no_entity_body?
110
109
  delete_header CONTENT_TYPE
111
110
  delete_header CONTENT_LENGTH
112
111
  close
113
112
  return [@status, @headers, []]
114
113
  else
115
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.
116
116
  @block = block
117
117
  return [@status, @headers, self]
118
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
119
123
  return [@status, @headers, @body]
120
124
  end
121
125
  end
@@ -133,7 +137,9 @@ module Rack
133
137
  end
134
138
  end
135
139
 
136
- # 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.
137
143
  #
138
144
  # NOTE: Do not mix #write and direct #body access!
139
145
  #
@@ -315,27 +321,34 @@ module Rack
315
321
 
316
322
  protected
317
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.
318
332
  def buffered_body!
319
333
  if @buffered.nil?
320
334
  if @body.is_a?(Array)
321
335
  # The user supplied body was an array:
322
336
  @body = @body.compact
323
- @body.each do |part|
324
- @length += part.to_s.bytesize
325
- end
337
+ @length = @body.sum{|part| part.bytesize}
338
+ @buffered = true
326
339
  elsif @body.respond_to?(:each)
327
340
  # Turn the user supplied body into a buffered array:
328
341
  body = @body
329
342
  @body = Array.new
343
+ @buffered = true
330
344
 
331
345
  body.each do |part|
332
346
  @writer.call(part.to_s)
333
347
  end
334
348
 
335
349
  body.close if body.respond_to?(:close)
336
-
337
- @buffered = true
338
350
  else
351
+ # We don't know how to buffer the user-supplied body:
339
352
  @buffered = false
340
353
  end
341
354
  end
@@ -344,11 +357,13 @@ module Rack
344
357
  end
345
358
 
346
359
  def append(chunk)
360
+ chunk = chunk.dup unless chunk.frozen?
347
361
  @body << chunk
348
362
 
349
- unless chunked?
363
+ if @length
350
364
  @length += chunk.bytesize
351
- set_header(CONTENT_LENGTH, @length.to_s)
365
+ elsif @buffered
366
+ @length = chunk.bytesize
352
367
  end
353
368
 
354
369
  return chunk
@@ -21,7 +21,10 @@ module Rack
21
21
  end
22
22
 
23
23
  def call(env)
24
- env[RACK_INPUT] = RewindableInput.new(env[RACK_INPUT])
24
+ if (input = env[RACK_INPUT])
25
+ env[RACK_INPUT] = RewindableInput.new(input)
26
+ end
27
+
25
28
  @app.call(env)
26
29
  end
27
30
  end