actionpack 7.2.1.1 → 8.0.0.rc1

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.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

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