actionpack 7.2.2.1 → 8.1.2

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +408 -95
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/asset_paths.rb +4 -2
  5. data/lib/abstract_controller/base.rb +12 -17
  6. data/lib/abstract_controller/caching.rb +6 -3
  7. data/lib/abstract_controller/callbacks.rb +6 -0
  8. data/lib/abstract_controller/collector.rb +1 -1
  9. data/lib/abstract_controller/helpers.rb +1 -1
  10. data/lib/abstract_controller/logger.rb +2 -1
  11. data/lib/abstract_controller/rendering.rb +0 -1
  12. data/lib/action_controller/api.rb +1 -0
  13. data/lib/action_controller/base.rb +3 -2
  14. data/lib/action_controller/caching.rb +1 -2
  15. data/lib/action_controller/form_builder.rb +4 -4
  16. data/lib/action_controller/log_subscriber.rb +22 -3
  17. data/lib/action_controller/metal/allow_browser.rb +12 -2
  18. data/lib/action_controller/metal/conditional_get.rb +30 -1
  19. data/lib/action_controller/metal/data_streaming.rb +5 -5
  20. data/lib/action_controller/metal/exceptions.rb +5 -0
  21. data/lib/action_controller/metal/flash.rb +1 -4
  22. data/lib/action_controller/metal/head.rb +3 -1
  23. data/lib/action_controller/metal/instrumentation.rb +1 -2
  24. data/lib/action_controller/metal/live.rb +66 -26
  25. data/lib/action_controller/metal/params_wrapper.rb +3 -3
  26. data/lib/action_controller/metal/permissions_policy.rb +9 -0
  27. data/lib/action_controller/metal/rate_limiting.rb +39 -9
  28. data/lib/action_controller/metal/redirecting.rb +109 -16
  29. data/lib/action_controller/metal/renderers.rb +29 -9
  30. data/lib/action_controller/metal/rendering.rb +8 -2
  31. data/lib/action_controller/metal/request_forgery_protection.rb +21 -11
  32. data/lib/action_controller/metal/rescue.rb +9 -0
  33. data/lib/action_controller/metal/streaming.rb +5 -84
  34. data/lib/action_controller/metal/strong_parameters.rb +277 -92
  35. data/lib/action_controller/railtie.rb +33 -15
  36. data/lib/action_controller/renderer.rb +0 -1
  37. data/lib/action_controller/structured_event_subscriber.rb +116 -0
  38. data/lib/action_controller/test_case.rb +12 -2
  39. data/lib/action_dispatch/constants.rb +6 -0
  40. data/lib/action_dispatch/http/cache.rb +138 -11
  41. data/lib/action_dispatch/http/content_security_policy.rb +14 -1
  42. data/lib/action_dispatch/http/filter_parameters.rb +5 -3
  43. data/lib/action_dispatch/http/mime_negotiation.rb +63 -4
  44. data/lib/action_dispatch/http/mime_types.rb +1 -0
  45. data/lib/action_dispatch/http/param_builder.rb +187 -0
  46. data/lib/action_dispatch/http/param_error.rb +26 -0
  47. data/lib/action_dispatch/http/parameters.rb +3 -3
  48. data/lib/action_dispatch/http/permissions_policy.rb +6 -0
  49. data/lib/action_dispatch/http/query_parser.rb +55 -0
  50. data/lib/action_dispatch/http/request.rb +73 -23
  51. data/lib/action_dispatch/http/response.rb +65 -17
  52. data/lib/action_dispatch/http/url.rb +112 -16
  53. data/lib/action_dispatch/journey/formatter.rb +8 -3
  54. data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
  55. data/lib/action_dispatch/journey/gtg/transition_table.rb +37 -45
  56. data/lib/action_dispatch/journey/nodes/node.rb +2 -1
  57. data/lib/action_dispatch/journey/parser.rb +99 -196
  58. data/lib/action_dispatch/journey/route.rb +45 -31
  59. data/lib/action_dispatch/journey/router/utils.rb +8 -14
  60. data/lib/action_dispatch/journey/router.rb +59 -81
  61. data/lib/action_dispatch/journey/routes.rb +7 -0
  62. data/lib/action_dispatch/journey/scanner.rb +44 -42
  63. data/lib/action_dispatch/journey/visitors.rb +55 -23
  64. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  65. data/lib/action_dispatch/log_subscriber.rb +7 -3
  66. data/lib/action_dispatch/middleware/cookies.rb +8 -4
  67. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -5
  68. data/lib/action_dispatch/middleware/debug_view.rb +11 -5
  69. data/lib/action_dispatch/middleware/exception_wrapper.rb +14 -14
  70. data/lib/action_dispatch/middleware/executor.rb +17 -4
  71. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -6
  72. data/lib/action_dispatch/middleware/remote_ip.rb +11 -5
  73. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  74. data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
  75. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  76. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  77. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
  78. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  79. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  80. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
  82. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
  83. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  84. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  89. data/lib/action_dispatch/railtie.rb +21 -0
  90. data/lib/action_dispatch/request/session.rb +1 -0
  91. data/lib/action_dispatch/request/utils.rb +9 -3
  92. data/lib/action_dispatch/routing/inspector.rb +80 -57
  93. data/lib/action_dispatch/routing/mapper.rb +409 -228
  94. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  95. data/lib/action_dispatch/routing/redirection.rb +10 -7
  96. data/lib/action_dispatch/routing/route_set.rb +21 -12
  97. data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
  98. data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
  99. data/lib/action_dispatch/system_test_case.rb +3 -3
  100. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  101. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
  102. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  103. data/lib/action_dispatch/testing/assertions/response.rb +26 -2
  104. data/lib/action_dispatch/testing/assertions/routing.rb +27 -15
  105. data/lib/action_dispatch/testing/integration.rb +16 -7
  106. data/lib/action_dispatch/testing/request_encoder.rb +9 -9
  107. data/lib/action_dispatch/testing/test_process.rb +1 -2
  108. data/lib/action_dispatch.rb +14 -4
  109. data/lib/action_pack/gem_version.rb +3 -3
  110. metadata +19 -38
  111. data/lib/action_dispatch/journey/parser.y +0 -50
  112. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -0,0 +1,187 @@
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 :default
20
+ self.default = make_default(100)
21
+
22
+ class << self
23
+ delegate :from_query_string, :from_pairs, :from_hash, to: :default
24
+
25
+ def ignore_leading_brackets
26
+ ActionDispatch.deprecator.warn <<~MSG
27
+ ActionDispatch::ParamBuilder.ignore_leading_brackets is deprecated and have no effect and will be removed in Rails 8.2.
28
+ MSG
29
+
30
+ @ignore_leading_brackets
31
+ end
32
+
33
+ def ignore_leading_brackets=(value)
34
+ ActionDispatch.deprecator.warn <<~MSG
35
+ ActionDispatch::ParamBuilder.ignore_leading_brackets is deprecated and have no effect and will be removed in Rails 8.2.
36
+ MSG
37
+
38
+ @ignore_leading_brackets = value
39
+ end
40
+ end
41
+
42
+ def from_query_string(qs, separator: nil, encoding_template: nil)
43
+ from_pairs QueryParser.each_pair(qs, separator), encoding_template: encoding_template
44
+ end
45
+
46
+ def from_pairs(pairs, encoding_template: nil)
47
+ params = make_params
48
+
49
+ pairs.each do |k, v|
50
+ if Hash === v
51
+ v = ActionDispatch::Http::UploadedFile.new(v)
52
+ end
53
+
54
+ store_nested_param(params, k, v, 0, encoding_template)
55
+ end
56
+
57
+ params
58
+ rescue ArgumentError => e
59
+ raise InvalidParameterError, e.message, e.backtrace
60
+ end
61
+
62
+ def from_hash(hash, encoding_template: nil)
63
+ # Force encodings from encoding template
64
+ hash = Request::Utils::CustomParamEncoder.encode_for_template(hash, encoding_template)
65
+
66
+ # Assert valid encoding
67
+ Request::Utils.check_param_encoding(hash)
68
+
69
+ # Convert hashes to HWIA (or UploadedFile), and deep-munge nils
70
+ # out of arrays
71
+ hash = Request::Utils.normalize_encode_params(hash)
72
+
73
+ hash
74
+ end
75
+
76
+ private
77
+ def store_nested_param(params, name, v, depth, encoding_template = nil)
78
+ raise ParamsTooDeepError if depth >= param_depth_limit
79
+
80
+ if !name
81
+ # nil name, treat same as empty string (required by tests)
82
+ k = after = ""
83
+ elsif depth == 0
84
+ # Start of parsing, don't treat [] or [ at start of string specially
85
+ if start = name.index("[", 1)
86
+ # Start of parameter nesting, use part before brackets as key
87
+ k = name[0, start]
88
+ after = name[start, name.length]
89
+ else
90
+ # Plain parameter with no nesting
91
+ k = name
92
+ after = ""
93
+ end
94
+ elsif name.start_with?("[]")
95
+ # Array nesting
96
+ k = "[]"
97
+ after = name[2, name.length]
98
+ elsif name.start_with?("[") && (start = name.index("]", 1))
99
+ # Hash nesting, use the part inside brackets as the key
100
+ k = name[1, start - 1]
101
+ after = name[start + 1, name.length]
102
+ else
103
+ # Probably malformed input, nested but not starting with [
104
+ # treat full name as key for backwards compatibility.
105
+ k = name
106
+ after = ""
107
+ end
108
+
109
+ return if k.empty?
110
+
111
+ unless k.valid_encoding?
112
+ raise InvalidParameterError, "Invalid encoding for parameter: #{k}"
113
+ end
114
+
115
+ if depth == 0 && String === v
116
+ # We have to wait until we've found the top part of the name,
117
+ # because that's what the encoding template is configured with
118
+ if encoding_template && (designated_encoding = encoding_template[k]) && !v.frozen?
119
+ v.force_encoding(designated_encoding)
120
+ end
121
+
122
+ # ... and we can't validate the encoding until after we've
123
+ # applied any template override
124
+ unless v.valid_encoding?
125
+ raise InvalidParameterError, "Invalid encoding for parameter: #{v.scrub}"
126
+ end
127
+ end
128
+
129
+ if after == ""
130
+ if k == "[]" && depth != 0
131
+ return (v || !ActionDispatch::Request::Utils.perform_deep_munge) ? [v] : []
132
+ else
133
+ params[k] = v
134
+ end
135
+ elsif after == "["
136
+ params[name] = v
137
+ elsif after == "[]"
138
+ params[k] ||= []
139
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
140
+ params[k] << v if v || !ActionDispatch::Request::Utils.perform_deep_munge
141
+ elsif after.start_with?("[]")
142
+ # Recognize x[][y] (hash inside array) parameters
143
+ unless after[2] == "[" && after.end_with?("]") && (child_key = after[3, after.length - 4]) && !child_key.empty? && !child_key.index("[") && !child_key.index("]")
144
+ # Handle other nested array parameters
145
+ child_key = after[2, after.length]
146
+ end
147
+ params[k] ||= []
148
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
149
+ if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
150
+ store_nested_param(params[k].last, child_key, v, depth + 1)
151
+ else
152
+ params[k] << store_nested_param(make_params, child_key, v, depth + 1)
153
+ end
154
+ else
155
+ params[k] ||= make_params
156
+ raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
157
+ params[k] = store_nested_param(params[k], after, v, depth + 1)
158
+ end
159
+
160
+ params
161
+ end
162
+
163
+ def make_params
164
+ ActiveSupport::HashWithIndifferentAccess.new
165
+ end
166
+
167
+ def new_depth_limit(param_depth_limit)
168
+ self.class.new @params_class, param_depth_limit
169
+ end
170
+
171
+ def params_hash_type?(obj)
172
+ Hash === obj
173
+ end
174
+
175
+ def params_hash_has_key?(hash, key)
176
+ return false if key.include?("[]")
177
+
178
+ key.split(/[\[\]]+/).inject(hash) do |h, part|
179
+ next h if part == ""
180
+ return false unless params_hash_type?(h) && h.key?(part)
181
+ h[part]
182
+ end
183
+
184
+ true
185
+ end
186
+ end
187
+ 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
@@ -65,14 +65,14 @@ module ActionDispatch
65
65
  alias :params :parameters
66
66
 
67
67
  def path_parameters=(parameters) # :nodoc:
68
- delete_header("action_dispatch.request.parameters")
68
+ @env.delete("action_dispatch.request.parameters")
69
69
 
70
70
  parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action])
71
71
  # If any of the path parameters has an invalid encoding then raise since it's
72
72
  # likely to trigger errors further on.
73
73
  Request::Utils.check_param_encoding(parameters)
74
74
 
75
- set_header PARAMETERS_KEY, parameters
75
+ @env[PARAMETERS_KEY] = parameters
76
76
  rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
77
77
  raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}")
78
78
  end
@@ -82,7 +82,7 @@ module ActionDispatch
82
82
  #
83
83
  # { action: "my_action", controller: "my_controller" }
84
84
  def path_parameters
85
- get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {})
85
+ @env[PARAMETERS_KEY] ||= {}
86
86
  end
87
87
 
88
88
  private
@@ -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",
@@ -184,4 +186,8 @@ module ActionDispatch # :nodoc:
184
186
  end
185
187
  end
186
188
  end
189
+
190
+ ActiveSupport.on_load(:action_dispatch_request) do
191
+ include ActionDispatch::PermissionsPolicy::Request
192
+ end
187
193
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "rack"
5
+
6
+ module ActionDispatch
7
+ class QueryParser
8
+ DEFAULT_SEP = /& */n
9
+ COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n, "&;" => /[&;] */n }
10
+
11
+ def self.strict_query_string_separator
12
+ ActionDispatch.deprecator.warn <<~MSG
13
+ The `strict_query_string_separator` configuration is deprecated have no effect and will be removed in Rails 8.2.
14
+ MSG
15
+ @strict_query_string_separator
16
+ end
17
+
18
+ def self.strict_query_string_separator=(value)
19
+ ActionDispatch.deprecator.warn <<~MSG
20
+ The `strict_query_string_separator` configuration is deprecated have no effect and will be removed in Rails 8.2.
21
+ MSG
22
+ @strict_query_string_separator = value
23
+ end
24
+
25
+ #--
26
+ # Note this departs from WHATWG's specified parsing algorithm by
27
+ # giving a nil value for keys that do not use '='. Callers that need
28
+ # the standard's interpretation can use `v.to_s`.
29
+ def self.each_pair(s, separator = nil)
30
+ return enum_for(:each_pair, s, separator) unless block_given?
31
+
32
+ s ||= ""
33
+
34
+ splitter =
35
+ if separator
36
+ COMMON_SEP[separator] || /[#{separator}] */n
37
+ else
38
+ DEFAULT_SEP
39
+ end
40
+
41
+ s.split(splitter).each do |part|
42
+ next if part.empty?
43
+
44
+ k, v = part.split("=", 2)
45
+
46
+ k = URI.decode_www_form_component(k)
47
+ v &&= URI.decode_www_form_component(v)
48
+
49
+ yield k, v
50
+ end
51
+
52
+ nil
53
+ end
54
+ end
55
+ end
@@ -25,7 +25,6 @@ module ActionDispatch
25
25
  include ActionDispatch::Http::FilterParameters
26
26
  include ActionDispatch::Http::URL
27
27
  include ActionDispatch::ContentSecurityPolicy::Request
28
- include ActionDispatch::PermissionsPolicy::Request
29
28
  include Rack::Request::Env
30
29
 
31
30
  autoload :Session, "action_dispatch/request/session"
@@ -55,12 +54,17 @@ module ActionDispatch
55
54
  METHOD
56
55
  end
57
56
 
57
+ TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc:
58
+
58
59
  def self.empty
59
60
  new({})
60
61
  end
61
62
 
62
63
  def initialize(env)
63
64
  super
65
+
66
+ @rack_request = Rack::Request.new(env)
67
+
64
68
  @method = nil
65
69
  @request_method = nil
66
70
  @remote_ip = nil
@@ -69,6 +73,8 @@ module ActionDispatch
69
73
  @ip = nil
70
74
  end
71
75
 
76
+ attr_reader :rack_request
77
+
72
78
  def commit_cookie_jar! # :nodoc:
73
79
  end
74
80
 
@@ -132,7 +138,7 @@ module ActionDispatch
132
138
 
133
139
  # Populate the HTTP method lookup cache.
134
140
  HTTP_METHODS.each { |method|
135
- HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym
141
+ HTTP_METHOD_LOOKUP[method] = method.downcase.tap { |m| m.tr!("-", "_") }.to_sym
136
142
  }
137
143
 
138
144
  alias raw_request_method request_method # :nodoc:
@@ -151,11 +157,17 @@ module ActionDispatch
151
157
  #
152
158
  # request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
153
159
  def route_uri_pattern
154
- get_header("action_dispatch.route_uri_pattern")
160
+ unless pattern = get_header("action_dispatch.route_uri_pattern")
161
+ route = get_header("action_dispatch.route")
162
+ return if route.nil?
163
+ pattern = route.path.spec.to_s
164
+ set_header("action_dispatch.route_uri_pattern", pattern)
165
+ end
166
+ pattern
155
167
  end
156
168
 
157
- def route_uri_pattern=(pattern) # :nodoc:
158
- set_header("action_dispatch.route_uri_pattern", pattern)
169
+ def route=(route) # :nodoc:
170
+ @env["action_dispatch.route"] = route
159
171
  end
160
172
 
161
173
  def routes # :nodoc:
@@ -236,8 +248,9 @@ module ActionDispatch
236
248
  #
237
249
  # send_early_hints("link" => "</style.css>; rel=preload; as=style,</script.js>; rel=preload")
238
250
  #
239
- # If you are using `javascript_include_tag` or `stylesheet_link_tag` the Early
240
- # Hints headers are included by default if supported.
251
+ # If you are using {javascript_include_tag}[rdoc-ref:ActionView::Helpers::AssetTagHelper#javascript_include_tag]
252
+ # or {stylesheet_link_tag}[rdoc-ref:ActionView::Helpers::AssetTagHelper#stylesheet_link_tag]
253
+ # the Early Hints headers are included by default if supported.
241
254
  def send_early_hints(links)
242
255
  env["rack.early_hints"]&.call(links)
243
256
  end
@@ -282,7 +295,7 @@ module ActionDispatch
282
295
 
283
296
  # Returns the content length of the request as an integer.
284
297
  def content_length
285
- return raw_post.bytesize if headers.key?("Transfer-Encoding")
298
+ return raw_post.bytesize if has_header?(TRANSFER_ENCODING)
286
299
  super.to_i
287
300
  end
288
301
 
@@ -386,15 +399,12 @@ module ActionDispatch
386
399
  # Override Rack's GET method to support indifferent access.
387
400
  def GET
388
401
  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)
402
+ encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action])
403
+ rack_query_params = ActionDispatch::ParamBuilder.from_query_string(rack_request.query_string, encoding_template: encoding_template)
404
+
405
+ set_header k, rack_query_params
396
406
  end
397
- rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError, Rack::QueryParser::ParamsTooDeepError => e
407
+ rescue ActionDispatch::ParamError => e
398
408
  raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}")
399
409
  end
400
410
  alias :query_parameters :GET
@@ -402,18 +412,54 @@ module ActionDispatch
402
412
  # Override Rack's POST method to support indifferent access.
403
413
  def POST
404
414
  fetch_header("action_dispatch.request.request_parameters") do
405
- pr = parse_formatted_parameters(params_parsers) do |params|
406
- super || {}
415
+ encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action])
416
+
417
+ param_list = nil
418
+ pr = parse_formatted_parameters(params_parsers) do
419
+ if param_list = request_parameters_list
420
+ ActionDispatch::ParamBuilder.from_pairs(param_list, encoding_template: encoding_template)
421
+ else
422
+ # We're not using a version of Rack that provides raw form
423
+ # pairs; we must use its hash (and thus post-process it below).
424
+ fallback_request_parameters
425
+ end
407
426
  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)
427
+
428
+ # If the request body was parsed by a custom parser like JSON
429
+ # (and thus the above block was not run), we need to
430
+ # post-process the result hash.
431
+ if param_list.nil?
432
+ pr = ActionDispatch::ParamBuilder.from_hash(pr, encoding_template: encoding_template)
433
+ end
434
+
435
+ self.request_parameters = pr
411
436
  end
412
- rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError, Rack::QueryParser::ParamsTooDeepError, EOFError => e
437
+ rescue ActionDispatch::ParamError, EOFError => e
413
438
  raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
414
439
  end
415
440
  alias :request_parameters :POST
416
441
 
442
+ def request_parameters_list
443
+ # We don't use Rack's parse result, but we must call it so Rack
444
+ # can populate the rack.request.* keys we need.
445
+ rack_post = rack_request.POST
446
+
447
+ if form_pairs = get_header("rack.request.form_pairs")
448
+ # Multipart
449
+ form_pairs
450
+ elsif form_vars = get_header("rack.request.form_vars")
451
+ # URL-encoded
452
+ ActionDispatch::QueryParser.each_pair(form_vars)
453
+ elsif rack_post && !rack_post.empty?
454
+ # It was multipart, but Rack did not preserve a pair list
455
+ # (probably too old). Flat parameter list is not available.
456
+ nil
457
+ else
458
+ # No request body, or not a format Rack knows
459
+ []
460
+ end
461
+ end
462
+
417
463
  # Returns the authorization header regardless of whether it was specified
418
464
  # directly or through one of the proxy alternatives.
419
465
  def authorization
@@ -468,7 +514,7 @@ module ActionDispatch
468
514
  def read_body_stream
469
515
  if body_stream
470
516
  reset_stream(body_stream) do
471
- if headers.key?("Transfer-Encoding")
517
+ if has_header?(TRANSFER_ENCODING)
472
518
  body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
473
519
  else
474
520
  body_stream.read(content_length)
@@ -490,6 +536,10 @@ module ActionDispatch
490
536
  yield
491
537
  end
492
538
  end
539
+
540
+ def fallback_request_parameters
541
+ rack_request.POST
542
+ end
493
543
  end
494
544
  end
495
545
 
@@ -46,6 +46,20 @@ module ActionDispatch # :nodoc:
46
46
  Headers = ::Rack::Utils::HeaderHash
47
47
  end
48
48
 
49
+ class << self
50
+ if ActionDispatch::Constants::UNPROCESSABLE_CONTENT == :unprocessable_content
51
+ def rack_status_code(status) # :nodoc:
52
+ status = :unprocessable_content if status == :unprocessable_entity
53
+ Rack::Utils.status_code(status)
54
+ end
55
+ else
56
+ def rack_status_code(status) # :nodoc:
57
+ status = :unprocessable_entity if status == :unprocessable_content
58
+ Rack::Utils.status_code(status)
59
+ end
60
+ end
61
+ end
62
+
49
63
  # To be deprecated:
50
64
  Header = Headers
51
65
 
@@ -105,10 +119,22 @@ module ActionDispatch # :nodoc:
105
119
  @str_body = nil
106
120
  end
107
121
 
122
+ BODY_METHODS = { to_ary: true }
123
+
124
+ def respond_to?(method, include_private = false)
125
+ if BODY_METHODS.key?(method)
126
+ @buf.respond_to?(method)
127
+ else
128
+ super
129
+ end
130
+ end
131
+
108
132
  def to_ary
109
- @buf.respond_to?(:to_ary) ?
110
- @buf.to_ary :
111
- @buf.each
133
+ if @str_body
134
+ [body]
135
+ else
136
+ @buf = @buf.to_ary
137
+ end
112
138
  end
113
139
 
114
140
  def body
@@ -245,20 +271,33 @@ module ActionDispatch # :nodoc:
245
271
 
246
272
  # Sets the HTTP status code.
247
273
  def status=(status)
248
- @status = Rack::Utils.status_code(status)
274
+ @status = Response.rack_status_code(status)
249
275
  end
250
276
 
251
277
  # Sets the HTTP response's content MIME type. For example, in the controller you
252
278
  # could write this:
253
279
  #
254
- # response.content_type = "text/plain"
280
+ # response.content_type = "text/html"
281
+ #
282
+ # This method also accepts a symbol with the extension of the MIME type:
283
+ #
284
+ # response.content_type = :html
255
285
  #
256
286
  # If a character set has been defined for this response (see #charset=) then the
257
287
  # character set information will also be included in the content type
258
288
  # information.
259
289
  def content_type=(content_type)
260
- return unless content_type
261
- new_header_info = parse_content_type(content_type.to_s)
290
+ case content_type
291
+ when NilClass
292
+ return
293
+ when Symbol
294
+ mime_type = Mime[content_type]
295
+ raise ArgumentError, "Unknown MIME type #{content_type}" unless mime_type
296
+ new_header_info = ContentTypeHeader.new(mime_type.to_s)
297
+ else
298
+ new_header_info = parse_content_type(content_type.to_s)
299
+ end
300
+
262
301
  prev_header_info = parsed_content_type_header
263
302
  charset = new_header_info.charset || prev_header_info.charset
264
303
  charset ||= self.class.default_charset unless prev_header_info.mime_type
@@ -328,7 +367,13 @@ module ActionDispatch # :nodoc:
328
367
  # Returns the content of the response as a string. This contains the contents of
329
368
  # any calls to `render`.
330
369
  def body
331
- @stream.body
370
+ if @stream.respond_to?(:to_ary)
371
+ @stream.to_ary.join
372
+ elsif @stream.respond_to?(:body)
373
+ @stream.body
374
+ else
375
+ @stream
376
+ end
332
377
  end
333
378
 
334
379
  def write(string)
@@ -337,11 +382,16 @@ module ActionDispatch # :nodoc:
337
382
 
338
383
  # Allows you to manually set or override the response body.
339
384
  def body=(body)
340
- if body.respond_to?(:to_path)
341
- @stream = body
342
- else
343
- synchronize do
344
- @stream = build_buffer self, munge_body_object(body)
385
+ # Prevent ActionController::Metal::Live::Response from committing the response prematurely.
386
+ synchronize do
387
+ if body.respond_to?(:to_str)
388
+ @stream = build_buffer(self, [body])
389
+ elsif body.respond_to?(:to_path)
390
+ @stream = body
391
+ elsif body.respond_to?(:to_ary)
392
+ @stream = build_buffer(self, body)
393
+ else
394
+ @stream = body
345
395
  end
346
396
  end
347
397
  end
@@ -482,10 +532,6 @@ module ActionDispatch # :nodoc:
482
532
  Buffer.new response, body
483
533
  end
484
534
 
485
- def munge_body_object(body)
486
- body.respond_to?(:each) ? body : [body]
487
- end
488
-
489
535
  def assign_default_content_type_and_charset!
490
536
  return if media_type
491
537
 
@@ -499,6 +545,8 @@ module ActionDispatch # :nodoc:
499
545
  @response = response
500
546
  end
501
547
 
548
+ attr :response
549
+
502
550
  def close
503
551
  # Rack "close" maps to Response#abort, and **not** Response#close (which is used
504
552
  # when the controller's finished writing)