actionpack 7.2.1.1 → 8.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -100
  3. data/lib/abstract_controller/helpers.rb +2 -0
  4. data/lib/abstract_controller/rendering.rb +0 -1
  5. data/lib/action_controller/api.rb +1 -0
  6. data/lib/action_controller/form_builder.rb +3 -3
  7. data/lib/action_controller/metal/allow_browser.rb +12 -2
  8. data/lib/action_controller/metal/conditional_get.rb +6 -3
  9. data/lib/action_controller/metal/http_authentication.rb +6 -3
  10. data/lib/action_controller/metal/instrumentation.rb +1 -2
  11. data/lib/action_controller/metal/live.rb +19 -8
  12. data/lib/action_controller/metal/rate_limiting.rb +13 -4
  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 +274 -88
  16. data/lib/action_controller/railtie.rb +1 -7
  17. data/lib/action_controller/test_case.rb +6 -5
  18. data/lib/action_dispatch/http/cache.rb +27 -10
  19. data/lib/action_dispatch/http/content_security_policy.rb +5 -8
  20. data/lib/action_dispatch/http/filter_parameters.rb +4 -9
  21. data/lib/action_dispatch/http/filter_redirect.rb +2 -9
  22. data/lib/action_dispatch/http/param_builder.rb +163 -0
  23. data/lib/action_dispatch/http/param_error.rb +26 -0
  24. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  25. data/lib/action_dispatch/http/query_parser.rb +31 -0
  26. data/lib/action_dispatch/http/request.rb +60 -16
  27. data/lib/action_dispatch/journey/parser.rb +99 -196
  28. data/lib/action_dispatch/journey/scanner.rb +40 -42
  29. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  30. data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -3
  31. data/lib/action_dispatch/middleware/debug_view.rb +0 -5
  32. data/lib/action_dispatch/middleware/exception_wrapper.rb +0 -6
  33. data/lib/action_dispatch/middleware/remote_ip.rb +5 -6
  34. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  35. data/lib/action_dispatch/middleware/ssl.rb +14 -4
  36. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
  37. data/lib/action_dispatch/railtie.rb +2 -0
  38. data/lib/action_dispatch/request/utils.rb +9 -3
  39. data/lib/action_dispatch/routing/inspector.rb +1 -1
  40. data/lib/action_dispatch/routing/mapper.rb +91 -62
  41. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  42. data/lib/action_dispatch/routing/route_set.rb +20 -8
  43. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  44. data/lib/action_dispatch/testing/assertions/response.rb +12 -2
  45. data/lib/action_dispatch/testing/assertions/routing.rb +4 -4
  46. data/lib/action_dispatch/testing/integration.rb +11 -1
  47. data/lib/action_dispatch.rb +6 -0
  48. data/lib/action_pack/gem_version.rb +4 -4
  49. metadata +15 -34
  50. data/lib/action_dispatch/journey/parser.y +0 -50
  51. 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
@@ -427,9 +429,7 @@ module ActionController
427
429
  # Note that the request method is not verified. The different methods are
428
430
  # available to make the tests more expressive.
429
431
  def get(action, **args)
430
- res = process(action, method: "GET", **args)
431
- cookies.update res.cookies
432
- res
432
+ process(action, method: "GET", **args)
433
433
  end
434
434
 
435
435
  # Simulate a POST request with the given parameters and set/volley the response.
@@ -637,6 +637,7 @@ module ActionController
637
637
  unless @request.cookie_jar.committed?
638
638
  @request.cookie_jar.write(@response)
639
639
  cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
640
+ cookies.update(@response.cookies)
640
641
  end
641
642
  end
642
643
  @response.prepare!
@@ -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
 
@@ -8,8 +8,7 @@ require "active_support/core_ext/array/wrap"
8
8
  module ActionDispatch # :nodoc:
9
9
  # # Action Dispatch Content Security Policy
10
10
  #
11
- # Configures the HTTP [Content-Security-Policy]
12
- # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
11
+ # Configures the HTTP [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
13
12
  # response header to help protect against XSS and
14
13
  # injection attacks.
15
14
  #
@@ -126,6 +125,7 @@ module ActionDispatch # :nodoc:
126
125
  MAPPINGS = {
127
126
  self: "'self'",
128
127
  unsafe_eval: "'unsafe-eval'",
128
+ wasm_unsafe_eval: "'wasm-unsafe-eval'",
129
129
  unsafe_hashes: "'unsafe-hashes'",
130
130
  unsafe_inline: "'unsafe-inline'",
131
131
  none: "'none'",
@@ -226,8 +226,7 @@ module ActionDispatch # :nodoc:
226
226
  end
227
227
  end
228
228
 
229
- # Enable the [report-uri]
230
- # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
229
+ # Enable the [report-uri](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
231
230
  # directive. Violation reports will be sent to the
232
231
  # specified URI:
233
232
  #
@@ -237,8 +236,7 @@ module ActionDispatch # :nodoc:
237
236
  @directives["report-uri"] = [uri]
238
237
  end
239
238
 
240
- # Specify asset types for which [Subresource Integrity]
241
- # (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
239
+ # Specify asset types for which [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
242
240
  #
243
241
  # policy.require_sri_for :script, :style
244
242
  #
@@ -254,8 +252,7 @@ module ActionDispatch # :nodoc:
254
252
  end
255
253
  end
256
254
 
257
- # Specify whether a [sandbox]
258
- # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
255
+ # Specify whether a [sandbox](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
259
256
  # should be enabled for the requested resource:
260
257
  #
261
258
  # policy.sandbox
@@ -68,17 +68,12 @@ module ActionDispatch
68
68
  ActiveSupport::ParameterFilter.new(filters)
69
69
  end
70
70
 
71
+ KV_RE = "[^&;=]+"
72
+ PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
71
73
  def filtered_query_string # :doc:
72
- parts = query_string.split(/([&;])/)
73
- filtered_parts = parts.map do |part|
74
- if part.include?("=")
75
- key, value = part.split("=", 2)
76
- parameter_filter.filter(key => value).first.join("=")
77
- else
78
- part
79
- end
74
+ query_string.gsub(PAIR_RE) do |_|
75
+ parameter_filter.filter($1 => $2).first.join("=")
80
76
  end
81
- filtered_parts.join("")
82
77
  end
83
78
  end
84
79
  end
@@ -37,16 +37,9 @@ module ActionDispatch
37
37
  def parameter_filtered_location
38
38
  uri = URI.parse(location)
39
39
  unless uri.query.nil? || uri.query.empty?
40
- parts = uri.query.split(/([&;])/)
41
- filtered_parts = parts.map do |part|
42
- if part.include?("=")
43
- key, value = part.split("=", 2)
44
- request.parameter_filter.filter(key => value).first.join("=")
45
- else
46
- part
47
- end
40
+ uri.query.gsub!(FilterParameters::PAIR_RE) do
41
+ request.parameter_filter.filter($1 => $2).first.join("=")
48
42
  end
49
- uri.query = filtered_parts.join("")
50
43
  end
51
44
  uri.to_s
52
45
  rescue URI::Error
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ class ParamBuilder
5
+ def self.make_default(param_depth_limit)
6
+ new param_depth_limit
7
+ end
8
+
9
+ attr_reader :param_depth_limit
10
+
11
+ def initialize(param_depth_limit)
12
+ @param_depth_limit = param_depth_limit
13
+ end
14
+
15
+ cattr_accessor :default
16
+ self.default = make_default(100)
17
+
18
+ class << self
19
+ delegate :from_query_string, :from_pairs, :from_hash, to: :default
20
+ end
21
+
22
+ def from_query_string(qs, separator: nil, encoding_template: nil)
23
+ from_pairs QueryParser.each_pair(qs, separator), encoding_template: encoding_template
24
+ end
25
+
26
+ def from_pairs(pairs, encoding_template: nil)
27
+ params = make_params
28
+
29
+ pairs.each do |k, v|
30
+ if Hash === v
31
+ v = ActionDispatch::Http::UploadedFile.new(v)
32
+ end
33
+
34
+ store_nested_param(params, k, v, 0, encoding_template)
35
+ end
36
+
37
+ params
38
+ rescue ArgumentError => e
39
+ raise InvalidParameterError, e.message, e.backtrace
40
+ end
41
+
42
+ def from_hash(hash, encoding_template: nil)
43
+ # Force encodings from encoding template
44
+ hash = Request::Utils::CustomParamEncoder.encode_for_template(hash, encoding_template)
45
+
46
+ # Assert valid encoding
47
+ Request::Utils.check_param_encoding(hash)
48
+
49
+ # Convert hashes to HWIA (or UploadedFile), and deep-munge nils
50
+ # out of arrays
51
+ hash = Request::Utils.normalize_encode_params(hash)
52
+
53
+ hash
54
+ end
55
+
56
+ private
57
+ def store_nested_param(params, name, v, depth, encoding_template = nil)
58
+ raise ParamsTooDeepError if depth >= param_depth_limit
59
+
60
+ if !name
61
+ # nil name, treat same as empty string (required by tests)
62
+ k = after = ""
63
+ elsif depth == 0
64
+ # Start of parsing, don't treat [] or [ at start of string specially
65
+ if start = name.index("[", 1)
66
+ # Start of parameter nesting, use part before brackets as key
67
+ k = name[0, start]
68
+ after = name[start, name.length]
69
+ else
70
+ # Plain parameter with no nesting
71
+ k = name
72
+ after = ""
73
+ end
74
+ elsif name.start_with?("[]")
75
+ # Array nesting
76
+ k = "[]"
77
+ after = name[2, name.length]
78
+ elsif name.start_with?("[") && (start = name.index("]", 1))
79
+ # Hash nesting, use the part inside brackets as the key
80
+ k = name[1, start - 1]
81
+ after = name[start + 1, name.length]
82
+ else
83
+ # Probably malformed input, nested but not starting with [
84
+ # treat full name as key for backwards compatibility.
85
+ k = name
86
+ after = ""
87
+ end
88
+
89
+ return if k.empty?
90
+
91
+ if depth == 0 && String === v
92
+ # We have to wait until we've found the top part of the name,
93
+ # because that's what the encoding template is configured with
94
+ if encoding_template && (designated_encoding = encoding_template[k]) && !v.frozen?
95
+ v.force_encoding(designated_encoding)
96
+ end
97
+
98
+ # ... and we can't validate the encoding until after we've
99
+ # applied any template override
100
+ unless v.valid_encoding?
101
+ raise InvalidParameterError, "Invalid encoding for parameter: #{v.scrub}"
102
+ end
103
+ end
104
+
105
+ if after == ""
106
+ if k == "[]" && depth != 0
107
+ return (v || !ActionDispatch::Request::Utils.perform_deep_munge) ? [v] : []
108
+ else
109
+ params[k] = v
110
+ end
111
+ elsif after == "["
112
+ params[name] = v
113
+ elsif after == "[]"
114
+ params[k] ||= []
115
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
116
+ params[k] << v if v || !ActionDispatch::Request::Utils.perform_deep_munge
117
+ elsif after.start_with?("[]")
118
+ # Recognize x[][y] (hash inside array) parameters
119
+ unless after[2] == "[" && after.end_with?("]") && (child_key = after[3, after.length - 4]) && !child_key.empty? && !child_key.index("[") && !child_key.index("]")
120
+ # Handle other nested array parameters
121
+ child_key = after[2, after.length]
122
+ end
123
+ params[k] ||= []
124
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
125
+ if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
126
+ store_nested_param(params[k].last, child_key, v, depth + 1)
127
+ else
128
+ params[k] << store_nested_param(make_params, child_key, v, depth + 1)
129
+ end
130
+ else
131
+ params[k] ||= make_params
132
+ raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
133
+ params[k] = store_nested_param(params[k], after, v, depth + 1)
134
+ end
135
+
136
+ params
137
+ end
138
+
139
+ def make_params
140
+ ActiveSupport::HashWithIndifferentAccess.new
141
+ end
142
+
143
+ def new_depth_limit(param_depth_limit)
144
+ self.class.new @params_class, param_depth_limit
145
+ end
146
+
147
+ def params_hash_type?(obj)
148
+ Hash === obj
149
+ end
150
+
151
+ def params_hash_has_key?(hash, key)
152
+ return false if key.include?("[]")
153
+
154
+ key.split(/[\[\]]+/).inject(hash) do |h, part|
155
+ next h if part == ""
156
+ return false unless params_hash_type?(h) && h.key?(part)
157
+ h[part]
158
+ end
159
+
160
+ true
161
+ end
162
+ end
163
+ 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,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module ActionDispatch
6
+ class QueryParser
7
+ DEFAULT_SEP = /& */n
8
+ COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n }
9
+
10
+ #--
11
+ # Note this departs from WHATWG's specified parsing algorithm by
12
+ # giving a nil value for keys that do not use '='. Callers that need
13
+ # the standard's interpretation can use `v.to_s`.
14
+ def self.each_pair(s, separator = nil)
15
+ return enum_for(:each_pair, s, separator) unless block_given?
16
+
17
+ (s || "").split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |part|
18
+ next if part.empty?
19
+
20
+ k, v = part.split("=", 2)
21
+
22
+ k = URI.decode_www_form_component(k)
23
+ v &&= URI.decode_www_form_component(v)
24
+
25
+ yield k, v
26
+ end
27
+
28
+ nil
29
+ end
30
+ end
31
+ 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