actionpack 7.2.2.1 → 8.0.1

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 +123 -109
  3. data/lib/abstract_controller/rendering.rb +0 -1
  4. data/lib/action_controller/base.rb +1 -1
  5. data/lib/action_controller/form_builder.rb +3 -3
  6. data/lib/action_controller/metal/allow_browser.rb +11 -1
  7. data/lib/action_controller/metal/conditional_get.rb +5 -1
  8. data/lib/action_controller/metal/data_streaming.rb +4 -2
  9. data/lib/action_controller/metal/instrumentation.rb +1 -2
  10. data/lib/action_controller/metal/live.rb +13 -4
  11. data/lib/action_controller/metal/rate_limiting.rb +13 -4
  12. data/lib/action_controller/metal/redirecting.rb +2 -1
  13. data/lib/action_controller/metal/renderers.rb +2 -3
  14. data/lib/action_controller/metal/streaming.rb +5 -84
  15. data/lib/action_controller/metal/strong_parameters.rb +277 -89
  16. data/lib/action_controller/railtie.rb +1 -7
  17. data/lib/action_controller/test_case.rb +4 -2
  18. data/lib/action_dispatch/http/cache.rb +27 -10
  19. data/lib/action_dispatch/http/content_security_policy.rb +1 -0
  20. data/lib/action_dispatch/http/param_builder.rb +186 -0
  21. data/lib/action_dispatch/http/param_error.rb +26 -0
  22. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  23. data/lib/action_dispatch/http/query_parser.rb +53 -0
  24. data/lib/action_dispatch/http/request.rb +60 -16
  25. data/lib/action_dispatch/journey/parser.rb +99 -196
  26. data/lib/action_dispatch/journey/scanner.rb +44 -42
  27. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  28. data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -3
  29. data/lib/action_dispatch/middleware/debug_view.rb +0 -5
  30. data/lib/action_dispatch/middleware/exception_wrapper.rb +0 -6
  31. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  32. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  33. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
  34. data/lib/action_dispatch/railtie.rb +8 -0
  35. data/lib/action_dispatch/request/session.rb +1 -0
  36. data/lib/action_dispatch/request/utils.rb +9 -3
  37. data/lib/action_dispatch/routing/inspector.rb +1 -1
  38. data/lib/action_dispatch/routing/mapper.rb +90 -62
  39. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  40. data/lib/action_dispatch/routing/route_set.rb +20 -8
  41. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  42. data/lib/action_dispatch/testing/assertions/response.rb +12 -2
  43. data/lib/action_dispatch/testing/assertions/routing.rb +4 -4
  44. data/lib/action_dispatch/testing/integration.rb +11 -1
  45. data/lib/action_dispatch/testing/test_process.rb +1 -2
  46. data/lib/action_dispatch.rb +6 -4
  47. data/lib/action_pack/gem_version.rb +4 -4
  48. metadata +15 -34
  49. data/lib/action_dispatch/journey/parser.y +0 -50
  50. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -48,11 +48,6 @@ module ActionController
48
48
  end
49
49
 
50
50
  ActionController::Parameters.action_on_unpermitted_parameters = action_on_unpermitted_parameters
51
-
52
- unless options.allow_deprecated_parameters_hash_equality.nil?
53
- ActionController::Parameters.allow_deprecated_parameters_hash_equality =
54
- options.allow_deprecated_parameters_hash_equality
55
- end
56
51
  end
57
52
  end
58
53
 
@@ -85,7 +80,6 @@ module ActionController
85
80
  :action_on_unpermitted_parameters,
86
81
  :always_permitted_parameters,
87
82
  :wrap_parameters_by_default,
88
- :allow_deprecated_parameters_hash_equality
89
83
  )
90
84
 
91
85
  filtered_options.each do |k, v|
@@ -123,7 +117,7 @@ module ActionController
123
117
  app.config.active_record.query_log_tags |= [:action]
124
118
 
125
119
  ActiveSupport.on_load(:active_record) do
126
- ActiveRecord::QueryLogs.taggings.merge!(
120
+ ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
127
121
  controller: ->(context) { context[:controller]&.controller_name },
128
122
  action: ->(context) { context[:controller]&.action_name },
129
123
  namespaced_controller: ->(context) {
@@ -22,6 +22,8 @@ module ActionController
22
22
  # database on the main thread, so they could open a txn, then the controller
23
23
  # thread will open a new connection and try to access data that's only visible
24
24
  # to the main thread's txn. This is the problem in #23483.
25
+ alias_method :original_new_controller_thread, :new_controller_thread
26
+
25
27
  silence_redefinition_of_method :new_controller_thread
26
28
  def new_controller_thread # :nodoc:
27
29
  yield
@@ -106,7 +108,7 @@ module ActionController
106
108
  set_header k, "application/x-www-form-urlencoded"
107
109
  end
108
110
 
109
- case content_mime_type.to_sym
111
+ case content_mime_type&.to_sym
110
112
  when nil
111
113
  raise "Unknown Content-Type: #{content_type}"
112
114
  when :json
@@ -121,7 +123,7 @@ module ActionController
121
123
  end
122
124
  end
123
125
 
124
- data_stream = StringIO.new(data)
126
+ data_stream = StringIO.new(data.b)
125
127
  set_header "CONTENT_LENGTH", data_stream.length.to_s
126
128
  set_header "rack.input", data_stream
127
129
  end
@@ -9,6 +9,8 @@ module ActionDispatch
9
9
  HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
10
10
  HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
11
11
 
12
+ mattr_accessor :strict_freshness, default: false
13
+
12
14
  def if_modified_since
13
15
  if since = get_header(HTTP_IF_MODIFIED_SINCE)
14
16
  Time.rfc2822(since) rescue nil
@@ -34,19 +36,32 @@ module ActionDispatch
34
36
  end
35
37
  end
36
38
 
37
- # Check response freshness (`Last-Modified` and ETag) against request
38
- # `If-Modified-Since` and `If-None-Match` conditions. If both headers are
39
- # supplied, both must match, or the request is not considered fresh.
39
+ # Check response freshness (`Last-Modified` and `ETag`) against request
40
+ # `If-Modified-Since` and `If-None-Match` conditions.
41
+ # If both headers are supplied, based on configuration, either `ETag` is preferred over `Last-Modified`
42
+ # or both are considered equally. You can adjust the preference with
43
+ # `config.action_dispatch.strict_freshness`.
44
+ # Reference: http://tools.ietf.org/html/rfc7232#section-6
40
45
  def fresh?(response)
41
- last_modified = if_modified_since
42
- etag = if_none_match
46
+ if Request.strict_freshness
47
+ if if_none_match
48
+ etag_matches?(response.etag)
49
+ elsif if_modified_since
50
+ not_modified?(response.last_modified)
51
+ else
52
+ false
53
+ end
54
+ else
55
+ last_modified = if_modified_since
56
+ etag = if_none_match
43
57
 
44
- return false unless last_modified || etag
58
+ return false unless last_modified || etag
45
59
 
46
- success = true
47
- success &&= not_modified?(response.last_modified) if last_modified
48
- success &&= etag_matches?(response.etag) if etag
49
- success
60
+ success = true
61
+ success &&= not_modified?(response.last_modified) if last_modified
62
+ success &&= etag_matches?(response.etag) if etag
63
+ success
64
+ end
50
65
  end
51
66
  end
52
67
 
@@ -171,6 +186,7 @@ module ActionDispatch
171
186
  PUBLIC = "public"
172
187
  PRIVATE = "private"
173
188
  MUST_REVALIDATE = "must-revalidate"
189
+ IMMUTABLE = "immutable"
174
190
 
175
191
  def handle_conditional_get!
176
192
  # Normally default cache control setting is handled by ETag middleware. But, if
@@ -221,6 +237,7 @@ module ActionDispatch
221
237
  options << MUST_REVALIDATE if control[:must_revalidate]
222
238
  options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
223
239
  options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
240
+ options << IMMUTABLE if control[:immutable]
224
241
  options.concat(extras) if extras
225
242
  end
226
243
 
@@ -128,6 +128,7 @@ module ActionDispatch # :nodoc:
128
128
  MAPPINGS = {
129
129
  self: "'self'",
130
130
  unsafe_eval: "'unsafe-eval'",
131
+ wasm_unsafe_eval: "'wasm-unsafe-eval'",
131
132
  unsafe_hashes: "'unsafe-hashes'",
132
133
  unsafe_inline: "'unsafe-inline'",
133
134
  none: "'none'",
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ class ParamBuilder
5
+ # --
6
+ # This implementation is based on Rack::QueryParser,
7
+ # Copyright (C) 2007-2021 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
8
+
9
+ def self.make_default(param_depth_limit)
10
+ new param_depth_limit
11
+ end
12
+
13
+ attr_reader :param_depth_limit
14
+
15
+ def initialize(param_depth_limit)
16
+ @param_depth_limit = param_depth_limit
17
+ end
18
+
19
+ cattr_accessor :ignore_leading_brackets
20
+
21
+ LEADING_BRACKETS_COMPAT = defined?(::Rack::RELEASE) && ::Rack::RELEASE.to_s.start_with?("2.")
22
+
23
+ cattr_accessor :default
24
+ self.default = make_default(100)
25
+
26
+ class << self
27
+ delegate :from_query_string, :from_pairs, :from_hash, to: :default
28
+ end
29
+
30
+ def from_query_string(qs, separator: nil, encoding_template: nil)
31
+ from_pairs QueryParser.each_pair(qs, separator), encoding_template: encoding_template
32
+ end
33
+
34
+ def from_pairs(pairs, encoding_template: nil)
35
+ params = make_params
36
+
37
+ pairs.each do |k, v|
38
+ if Hash === v
39
+ v = ActionDispatch::Http::UploadedFile.new(v)
40
+ end
41
+
42
+ store_nested_param(params, k, v, 0, encoding_template)
43
+ end
44
+
45
+ params
46
+ rescue ArgumentError => e
47
+ raise InvalidParameterError, e.message, e.backtrace
48
+ end
49
+
50
+ def from_hash(hash, encoding_template: nil)
51
+ # Force encodings from encoding template
52
+ hash = Request::Utils::CustomParamEncoder.encode_for_template(hash, encoding_template)
53
+
54
+ # Assert valid encoding
55
+ Request::Utils.check_param_encoding(hash)
56
+
57
+ # Convert hashes to HWIA (or UploadedFile), and deep-munge nils
58
+ # out of arrays
59
+ hash = Request::Utils.normalize_encode_params(hash)
60
+
61
+ hash
62
+ end
63
+
64
+ private
65
+ def store_nested_param(params, name, v, depth, encoding_template = nil)
66
+ raise ParamsTooDeepError if depth >= param_depth_limit
67
+
68
+ if !name
69
+ # nil name, treat same as empty string (required by tests)
70
+ k = after = ""
71
+ elsif depth == 0
72
+ if ignore_leading_brackets || (ignore_leading_brackets.nil? && LEADING_BRACKETS_COMPAT)
73
+ # Rack 2 compatible behavior, ignore leading brackets
74
+ if name =~ /\A[\[\]]*([^\[\]]+)\]*/
75
+ k = $1
76
+ after = $' || ""
77
+
78
+ if !ignore_leading_brackets && (k != $& || !after.empty? && !after.start_with?("["))
79
+ ActionDispatch.deprecator.warn("Skipping over leading brackets in parameter name #{name.inspect} is deprecated and will parse differently in Rails 8.1 or Rack 3.0.")
80
+ end
81
+ else
82
+ k = name
83
+ after = ""
84
+ end
85
+ else
86
+ # Start of parsing, don't treat [] or [ at start of string specially
87
+ if start = name.index("[", 1)
88
+ # Start of parameter nesting, use part before brackets as key
89
+ k = name[0, start]
90
+ after = name[start, name.length]
91
+ else
92
+ # Plain parameter with no nesting
93
+ k = name
94
+ after = ""
95
+ end
96
+ end
97
+ elsif name.start_with?("[]")
98
+ # Array nesting
99
+ k = "[]"
100
+ after = name[2, name.length]
101
+ elsif name.start_with?("[") && (start = name.index("]", 1))
102
+ # Hash nesting, use the part inside brackets as the key
103
+ k = name[1, start - 1]
104
+ after = name[start + 1, name.length]
105
+ else
106
+ # Probably malformed input, nested but not starting with [
107
+ # treat full name as key for backwards compatibility.
108
+ k = name
109
+ after = ""
110
+ end
111
+
112
+ return if k.empty?
113
+
114
+ if depth == 0 && String === v
115
+ # We have to wait until we've found the top part of the name,
116
+ # because that's what the encoding template is configured with
117
+ if encoding_template && (designated_encoding = encoding_template[k]) && !v.frozen?
118
+ v.force_encoding(designated_encoding)
119
+ end
120
+
121
+ # ... and we can't validate the encoding until after we've
122
+ # applied any template override
123
+ unless v.valid_encoding?
124
+ raise InvalidParameterError, "Invalid encoding for parameter: #{v.scrub}"
125
+ end
126
+ end
127
+
128
+ if after == ""
129
+ if k == "[]" && depth != 0
130
+ return (v || !ActionDispatch::Request::Utils.perform_deep_munge) ? [v] : []
131
+ else
132
+ params[k] = v
133
+ end
134
+ elsif after == "["
135
+ params[name] = v
136
+ elsif after == "[]"
137
+ params[k] ||= []
138
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
139
+ params[k] << v if v || !ActionDispatch::Request::Utils.perform_deep_munge
140
+ elsif after.start_with?("[]")
141
+ # Recognize x[][y] (hash inside array) parameters
142
+ unless after[2] == "[" && after.end_with?("]") && (child_key = after[3, after.length - 4]) && !child_key.empty? && !child_key.index("[") && !child_key.index("]")
143
+ # Handle other nested array parameters
144
+ child_key = after[2, after.length]
145
+ end
146
+ params[k] ||= []
147
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
148
+ if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
149
+ store_nested_param(params[k].last, child_key, v, depth + 1)
150
+ else
151
+ params[k] << store_nested_param(make_params, child_key, v, depth + 1)
152
+ end
153
+ else
154
+ params[k] ||= make_params
155
+ raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
156
+ params[k] = store_nested_param(params[k], after, v, depth + 1)
157
+ end
158
+
159
+ params
160
+ end
161
+
162
+ def make_params
163
+ ActiveSupport::HashWithIndifferentAccess.new
164
+ end
165
+
166
+ def new_depth_limit(param_depth_limit)
167
+ self.class.new @params_class, param_depth_limit
168
+ end
169
+
170
+ def params_hash_type?(obj)
171
+ Hash === obj
172
+ end
173
+
174
+ def params_hash_has_key?(hash, key)
175
+ return false if key.include?("[]")
176
+
177
+ key.split(/[\[\]]+/).inject(hash) do |h, part|
178
+ next h if part == ""
179
+ return false unless params_hash_type?(h) && h.key?(part)
180
+ h[part]
181
+ end
182
+
183
+ true
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ class ParamError < ActionDispatch::Http::Parameters::ParseError
5
+ def initialize(message = nil)
6
+ super
7
+ end
8
+
9
+ def self.===(other)
10
+ super || (
11
+ defined?(Rack::Utils::ParameterTypeError) && Rack::Utils::ParameterTypeError === other ||
12
+ defined?(Rack::Utils::InvalidParameterError) && Rack::Utils::InvalidParameterError === other ||
13
+ defined?(Rack::QueryParser::ParamsTooDeepError) && Rack::QueryParser::ParamsTooDeepError === other
14
+ )
15
+ end
16
+ end
17
+
18
+ class ParameterTypeError < ParamError
19
+ end
20
+
21
+ class InvalidParameterError < ParamError
22
+ end
23
+
24
+ class ParamsTooDeepError < ParamError
25
+ end
26
+ end
@@ -86,12 +86,14 @@ module ActionDispatch # :nodoc:
86
86
  ambient_light_sensor: "ambient-light-sensor",
87
87
  autoplay: "autoplay",
88
88
  camera: "camera",
89
+ display_capture: "display-capture",
89
90
  encrypted_media: "encrypted-media",
90
91
  fullscreen: "fullscreen",
91
92
  geolocation: "geolocation",
92
93
  gyroscope: "gyroscope",
93
94
  hid: "hid",
94
95
  idle_detection: "idle-detection",
96
+ keyboard_map: "keyboard-map",
95
97
  magnetometer: "magnetometer",
96
98
  microphone: "microphone",
97
99
  midi: "midi",
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "rack"
5
+
6
+ module ActionDispatch
7
+ class QueryParser
8
+ DEFAULT_SEP = /& */n
9
+ COMPAT_SEP = /[&;] */n
10
+ COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n, "&;" => /[&;] */n }
11
+
12
+ cattr_accessor :strict_query_string_separator
13
+
14
+ SEMICOLON_COMPAT = defined?(::Rack::QueryParser::DEFAULT_SEP) && ::Rack::QueryParser::DEFAULT_SEP.to_s.include?(";")
15
+
16
+ #--
17
+ # Note this departs from WHATWG's specified parsing algorithm by
18
+ # giving a nil value for keys that do not use '='. Callers that need
19
+ # the standard's interpretation can use `v.to_s`.
20
+ def self.each_pair(s, separator = nil)
21
+ return enum_for(:each_pair, s, separator) unless block_given?
22
+
23
+ s ||= ""
24
+
25
+ splitter =
26
+ if separator
27
+ COMMON_SEP[separator] || /[#{separator}] */n
28
+ elsif strict_query_string_separator
29
+ DEFAULT_SEP
30
+ elsif SEMICOLON_COMPAT && s.include?(";")
31
+ if strict_query_string_separator.nil?
32
+ ActionDispatch.deprecator.warn("Using semicolon as a query string separator is deprecated and will not be supported in Rails 8.1 or Rack 3.0. Use `&` instead.")
33
+ end
34
+ COMPAT_SEP
35
+ else
36
+ DEFAULT_SEP
37
+ end
38
+
39
+ s.split(splitter).each do |part|
40
+ next if part.empty?
41
+
42
+ k, v = part.split("=", 2)
43
+
44
+ k = URI.decode_www_form_component(k)
45
+ v &&= URI.decode_www_form_component(v)
46
+
47
+ yield k, v
48
+ end
49
+
50
+ nil
51
+ end
52
+ end
53
+ end
@@ -55,12 +55,17 @@ module ActionDispatch
55
55
  METHOD
56
56
  end
57
57
 
58
+ TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc:
59
+
58
60
  def self.empty
59
61
  new({})
60
62
  end
61
63
 
62
64
  def initialize(env)
63
65
  super
66
+
67
+ @rack_request = Rack::Request.new(env)
68
+
64
69
  @method = nil
65
70
  @request_method = nil
66
71
  @remote_ip = nil
@@ -69,6 +74,8 @@ module ActionDispatch
69
74
  @ip = nil
70
75
  end
71
76
 
77
+ attr_reader :rack_request
78
+
72
79
  def commit_cookie_jar! # :nodoc:
73
80
  end
74
81
 
@@ -282,7 +289,7 @@ module ActionDispatch
282
289
 
283
290
  # Returns the content length of the request as an integer.
284
291
  def content_length
285
- return raw_post.bytesize if headers.key?("Transfer-Encoding")
292
+ return raw_post.bytesize if has_header?(TRANSFER_ENCODING)
286
293
  super.to_i
287
294
  end
288
295
 
@@ -386,15 +393,12 @@ module ActionDispatch
386
393
  # Override Rack's GET method to support indifferent access.
387
394
  def GET
388
395
  fetch_header("action_dispatch.request.query_parameters") do |k|
389
- rack_query_params = super || {}
390
- controller = path_parameters[:controller]
391
- action = path_parameters[:action]
392
- rack_query_params = Request::Utils.set_binary_encoding(self, rack_query_params, controller, action)
393
- # Check for non UTF-8 parameter values, which would cause errors later
394
- Request::Utils.check_param_encoding(rack_query_params)
395
- set_header k, Request::Utils.normalize_encode_params(rack_query_params)
396
+ encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action])
397
+ rack_query_params = ActionDispatch::ParamBuilder.from_query_string(rack_request.query_string, encoding_template: encoding_template)
398
+
399
+ set_header k, rack_query_params
396
400
  end
397
- rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError, Rack::QueryParser::ParamsTooDeepError => e
401
+ rescue ActionDispatch::ParamError => e
398
402
  raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}")
399
403
  end
400
404
  alias :query_parameters :GET
@@ -402,18 +406,54 @@ module ActionDispatch
402
406
  # Override Rack's POST method to support indifferent access.
403
407
  def POST
404
408
  fetch_header("action_dispatch.request.request_parameters") do
405
- pr = parse_formatted_parameters(params_parsers) do |params|
406
- super || {}
409
+ encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action])
410
+
411
+ param_list = nil
412
+ pr = parse_formatted_parameters(params_parsers) do
413
+ if param_list = request_parameters_list
414
+ ActionDispatch::ParamBuilder.from_pairs(param_list, encoding_template: encoding_template)
415
+ else
416
+ # We're not using a version of Rack that provides raw form
417
+ # pairs; we must use its hash (and thus post-process it below).
418
+ fallback_request_parameters
419
+ end
407
420
  end
408
- pr = Request::Utils.set_binary_encoding(self, pr, path_parameters[:controller], path_parameters[:action])
409
- Request::Utils.check_param_encoding(pr)
410
- self.request_parameters = Request::Utils.normalize_encode_params(pr)
421
+
422
+ # If the request body was parsed by a custom parser like JSON
423
+ # (and thus the above block was not run), we need to
424
+ # post-process the result hash.
425
+ if param_list.nil?
426
+ pr = ActionDispatch::ParamBuilder.from_hash(pr, encoding_template: encoding_template)
427
+ end
428
+
429
+ self.request_parameters = pr
411
430
  end
412
- rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError, Rack::QueryParser::ParamsTooDeepError, EOFError => e
431
+ rescue ActionDispatch::ParamError, EOFError => e
413
432
  raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
414
433
  end
415
434
  alias :request_parameters :POST
416
435
 
436
+ def request_parameters_list
437
+ # We don't use Rack's parse result, but we must call it so Rack
438
+ # can populate the rack.request.* keys we need.
439
+ rack_post = rack_request.POST
440
+
441
+ if form_pairs = get_header("rack.request.form_pairs")
442
+ # Multipart
443
+ form_pairs
444
+ elsif form_vars = get_header("rack.request.form_vars")
445
+ # URL-encoded
446
+ ActionDispatch::QueryParser.each_pair(form_vars)
447
+ elsif rack_post && !rack_post.empty?
448
+ # It was multipart, but Rack did not preserve a pair list
449
+ # (probably too old). Flat parameter list is not available.
450
+ nil
451
+ else
452
+ # No request body, or not a format Rack knows
453
+ []
454
+ end
455
+ end
456
+
417
457
  # Returns the authorization header regardless of whether it was specified
418
458
  # directly or through one of the proxy alternatives.
419
459
  def authorization
@@ -468,7 +508,7 @@ module ActionDispatch
468
508
  def read_body_stream
469
509
  if body_stream
470
510
  reset_stream(body_stream) do
471
- if headers.key?("Transfer-Encoding")
511
+ if has_header?(TRANSFER_ENCODING)
472
512
  body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
473
513
  else
474
514
  body_stream.read(content_length)
@@ -490,6 +530,10 @@ module ActionDispatch
490
530
  yield
491
531
  end
492
532
  end
533
+
534
+ def fallback_request_parameters
535
+ rack_request.POST
536
+ end
493
537
  end
494
538
  end
495
539